You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by so...@apache.org on 2020/06/03 19:13:54 UTC

[lucene-solr] 25/47: SOLR-14474: Fix remaining auxilliary class warnings in Solr

This is an automated email from the ASF dual-hosted git repository.

sokolov pushed a commit to branch jira/lucene-8962
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git

commit 09fa2a183c11c23f7dc1b1428c88faa5de3cdce8
Author: Erick Erickson <Er...@gmail.com>
AuthorDate: Wed May 27 12:06:29 2020 -0400

    SOLR-14474: Fix remaining auxilliary class warnings in Solr
---
 solr/CHANGES.txt                                   |   2 +
 .../org/apache/solr/cloud/ElectionContext.java     | 709 ---------------------
 .../apache/solr/cloud/OverseerElectionContext.java | 110 ++++
 ...ontext.java => ShardLeaderElectionContext.java} | 423 ++----------
 .../solr/cloud/ShardLeaderElectionContextBase.java | 194 ++++++
 .../handler/component/PivotFacetProcessor.java     |   4 +-
 .../solr/handler/component/StatsComponent.java     | 110 +---
 .../apache/solr/handler/component/StatsInfo.java   | 108 ++++
 .../export/{DoubleCmp.java => DoubleComp.java}     |  30 +-
 .../apache/solr/handler/export/ExportWriter.java   |  28 +-
 .../export/{FloatCmp.java => FloatComp.java}       |  30 +-
 .../org/apache/solr/handler/export/IntComp.java    |  32 +-
 .../handler/export/{LongCmp.java => LongComp.java} |  31 +-
 .../response/transform/ShardAugmenterFactory.java  |   2 +-
 .../response/transform/ValueAugmenterFactory.java  |  78 ++-
 .../org/apache/solr/search/facet/FacetParser.java  | 235 ++++++-
 .../org/apache/solr/search/facet/FacetRequest.java | 223 +------
 ...TermsCollector.java => GraphEdgeCollector.java} | 149 ++---
 .../org/apache/solr/search/join/GraphQuery.java    |   2 +-
 .../org/apache/solr/update/TransactionLog.java     | 160 ++---
 .../processor/DistributedZkUpdateProcessor.java    |   2 +-
 .../processor/RunUpdateProcessorFactory.java       | 123 ++--
 .../solr/search/facet/TestJsonFacetRefinement.java |   2 +-
 .../UpdateRequestProcessorFactoryTest.java         |   2 +-
 solr/solrj/src/java/org/noggit/CharArr.java        | 262 ++++----
 solr/solrj/src/java/org/noggit/JSONParser.java     |   2 +-
 26 files changed, 1190 insertions(+), 1863 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 0e09e49..d163d11 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -263,6 +263,8 @@ Other Changes
 
 * SOLR-14280: Improve error reporting in SolrConfig (Andras Salamon via Jason Gerlowski)
 
+* SOLR-14474: Fix remaining auxilliary class warnings in Solr (Erick Erickson)
+
 ==================  8.5.1 ==================
 
 Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release.
diff --git a/solr/core/src/java/org/apache/solr/cloud/ElectionContext.java b/solr/core/src/java/org/apache/solr/cloud/ElectionContext.java
index 9ba4900..1398570 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ElectionContext.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ElectionContext.java
@@ -19,50 +19,13 @@ package org.apache.solr.cloud;
 import java.io.Closeable;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-import org.apache.hadoop.fs.Path;
-import org.apache.lucene.search.MatchAllDocsQuery;
-import org.apache.solr.cloud.overseer.OverseerAction;
-import org.apache.solr.common.SolrException;
-import org.apache.solr.common.SolrException.ErrorCode;
-import org.apache.solr.common.cloud.ClusterState;
-import org.apache.solr.common.cloud.DocCollection;
-import org.apache.solr.common.cloud.Replica;
-import org.apache.solr.common.cloud.Slice;
 import org.apache.solr.common.cloud.SolrZkClient;
-import org.apache.solr.common.cloud.ZkCmdExecutor;
-import org.apache.solr.common.cloud.ZkCoreNodeProps;
 import org.apache.solr.common.cloud.ZkNodeProps;
-import org.apache.solr.common.cloud.ZkStateReader;
-import org.apache.solr.common.util.RetryUtil;
-import org.apache.solr.common.util.Utils;
-import org.apache.solr.core.CoreContainer;
-import org.apache.solr.core.SolrCore;
-import org.apache.solr.logging.MDCLoggingContext;
-import org.apache.solr.search.SolrIndexSearcher;
-import org.apache.solr.update.PeerSync;
-import org.apache.solr.update.UpdateLog;
-import org.apache.solr.util.RefCounted;
-import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.KeeperException.NoNodeException;
-import org.apache.zookeeper.KeeperException.NodeExistsException;
-import org.apache.zookeeper.KeeperException.SessionExpiredException;
-import org.apache.zookeeper.Op;
-import org.apache.zookeeper.OpResult;
-import org.apache.zookeeper.OpResult.SetDataResult;
-import org.apache.zookeeper.ZooDefs;
-import org.apache.zookeeper.data.Stat;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import static org.apache.solr.common.params.CommonParams.ID;
-
 public abstract class ElectionContext implements Closeable {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
   final String electionPath;
@@ -111,676 +74,4 @@ public abstract class ElectionContext implements Closeable {
   }
 }
 
-class ShardLeaderElectionContextBase extends ElectionContext {
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-  protected final SolrZkClient zkClient;
-  protected String shardId;
-  protected String collection;
-  protected LeaderElector leaderElector;
-  protected ZkStateReader zkStateReader;
-  protected ZkController zkController;
-  private Integer leaderZkNodeParentVersion;
-
-  // Prevents a race between cancelling and becoming leader.
-  private final Object lock = new Object();
-
-  public ShardLeaderElectionContextBase(LeaderElector leaderElector,
-      final String shardId, final String collection, final String coreNodeName,
-      ZkNodeProps props, ZkController zkController) {
-    super(coreNodeName, ZkStateReader.COLLECTIONS_ZKNODE + "/" + collection
-        + "/leader_elect/" + shardId, ZkStateReader.getShardLeadersPath(
-        collection, shardId), props, zkController.getZkClient());
-    this.leaderElector = leaderElector;
-    this.zkStateReader = zkController.getZkStateReader();
-    this.zkClient = zkStateReader.getZkClient();
-    this.zkController = zkController;
-    this.shardId = shardId;
-    this.collection = collection;
-    
-    String parent = new Path(leaderPath).getParent().toString();
-    ZkCmdExecutor zcmd = new ZkCmdExecutor(30000);
-    // only if /collections/{collection} exists already do we succeed in creating this path
-    log.info("make sure parent is created {}", parent);
-    try {
-      zcmd.ensureExists(parent, (byte[])null, CreateMode.PERSISTENT, zkClient, 2);
-    } catch (KeeperException e) {
-      throw new RuntimeException(e);
-    } catch (InterruptedException e) {
-      Thread.currentThread().interrupt();
-      throw new RuntimeException(e);
-    }
-  }
-  
-  @Override
-  public void cancelElection() throws InterruptedException, KeeperException {
-    super.cancelElection();
-    synchronized (lock) {
-      if (leaderZkNodeParentVersion != null) {
-        try {
-          // We need to be careful and make sure we *only* delete our own leader registration node.
-          // We do this by using a multi and ensuring the parent znode of the leader registration node
-          // matches the version we expect - there is a setData call that increments the parent's znode
-          // version whenever a leader registers.
-          log.debug("Removing leader registration node on cancel: {} {}", leaderPath, leaderZkNodeParentVersion);
-          List<Op> ops = new ArrayList<>(2);
-          ops.add(Op.check(new Path(leaderPath).getParent().toString(), leaderZkNodeParentVersion));
-          ops.add(Op.delete(leaderPath, -1));
-          zkClient.multi(ops, true);
-        } catch (KeeperException.NoNodeException nne) {
-          // no problem
-          log.debug("No leader registration node found to remove: {}", leaderPath);
-        } catch (KeeperException.BadVersionException bve) {
-          log.info("Cannot remove leader registration node because the current registered node is not ours: {}", leaderPath);
-          // no problem
-        } catch (InterruptedException e) {
-          throw e;
-        } catch (Exception e) {
-          SolrException.log(log, e);
-        }
-        leaderZkNodeParentVersion = null;
-      } else {
-        log.info("No version found for ephemeral leader parent node, won't remove previous leader registration.");
-      }
-    }
-  }
-  
-  @Override
-  void runLeaderProcess(boolean weAreReplacement, int pauseBeforeStartMs)
-      throws KeeperException, InterruptedException, IOException {
-    // register as leader - if an ephemeral is already there, wait to see if it goes away
-
-    String parent = new Path(leaderPath).getParent().toString();
-    try {
-      RetryUtil.retryOnThrowable(NodeExistsException.class, 60000, 5000, () -> {
-        synchronized (lock) {
-          log.info("Creating leader registration node {} after winning as {}", leaderPath, leaderSeqPath);
-          List<Op> ops = new ArrayList<>(2);
-
-          // We use a multi operation to get the parent nodes version, which will
-          // be used to make sure we only remove our own leader registration node.
-          // The setData call used to get the parent version is also the trigger to
-          // increment the version. We also do a sanity check that our leaderSeqPath exists.
-
-          ops.add(Op.check(leaderSeqPath, -1));
-          ops.add(Op.create(leaderPath, Utils.toJSON(leaderProps), zkClient.getZkACLProvider().getACLsToAdd(leaderPath), CreateMode.EPHEMERAL));
-          ops.add(Op.setData(parent, null, -1));
-          List<OpResult> results;
-
-          results = zkClient.multi(ops, true);
-          for (OpResult result : results) {
-            if (result.getType() == ZooDefs.OpCode.setData) {
-              SetDataResult dresult = (SetDataResult) result;
-              Stat stat = dresult.getStat();
-              leaderZkNodeParentVersion = stat.getVersion();
-              return;
-            }
-          }
-          assert leaderZkNodeParentVersion != null;
-        }
-      });
-    } catch (NoNodeException e) {
-      log.info("Will not register as leader because it seems the election is no longer taking place.");
-      return;
-    } catch (Throwable t) {
-      if (t instanceof OutOfMemoryError) {
-        throw (OutOfMemoryError) t;
-      }
-      throw new SolrException(ErrorCode.SERVER_ERROR, "Could not register as the leader because creating the ephemeral registration node in ZooKeeper failed", t);
-    } 
-    
-    assert shardId != null;
-    boolean isAlreadyLeader = false;
-    if (zkStateReader.getClusterState() != null &&
-        zkStateReader.getClusterState().getCollection(collection).getSlice(shardId).getReplicas().size() < 2) {
-      Replica leader = zkStateReader.getLeader(collection, shardId);
-      if (leader != null
-          && leader.getBaseUrl().equals(leaderProps.get(ZkStateReader.BASE_URL_PROP))
-          && leader.getCoreName().equals(leaderProps.get(ZkStateReader.CORE_NAME_PROP))) {
-        isAlreadyLeader = true;
-      }
-    }
-    if (!isAlreadyLeader) {
-      ZkNodeProps m = ZkNodeProps.fromKeyVals(Overseer.QUEUE_OPERATION, OverseerAction.LEADER.toLower(),
-          ZkStateReader.SHARD_ID_PROP, shardId,
-          ZkStateReader.COLLECTION_PROP, collection,
-          ZkStateReader.BASE_URL_PROP, leaderProps.get(ZkStateReader.BASE_URL_PROP),
-          ZkStateReader.CORE_NAME_PROP, leaderProps.get(ZkStateReader.CORE_NAME_PROP),
-          ZkStateReader.STATE_PROP, Replica.State.ACTIVE.toString());
-      assert zkController != null;
-      assert zkController.getOverseer() != null;
-      zkController.getOverseer().offerStateUpdate(Utils.toJSON(m));
-    }
-  }
-
-  public LeaderElector getLeaderElector() {
-    return leaderElector;
-  }
-
-  Integer getLeaderZkNodeParentVersion() {
-    synchronized (lock) {
-      return leaderZkNodeParentVersion;
-    }
-  }
-}
-
-// add core container and stop passing core around...
-final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-  
-  private final CoreContainer cc;
-  private final SyncStrategy syncStrategy;
-
-  private volatile boolean isClosed = false;
-  
-  public ShardLeaderElectionContext(LeaderElector leaderElector, 
-      final String shardId, final String collection,
-      final String coreNodeName, ZkNodeProps props, ZkController zkController, CoreContainer cc) {
-    super(leaderElector, shardId, collection, coreNodeName, props,
-        zkController);
-    this.cc = cc;
-    syncStrategy = new SyncStrategy(cc);
-  }
-  
-  @Override
-  public void close() {
-    super.close();
-    this.isClosed  = true;
-    syncStrategy.close();
-  }
-  
-  @Override
-  public void cancelElection() throws InterruptedException, KeeperException {
-    String coreName = leaderProps.getStr(ZkStateReader.CORE_NAME_PROP);
-    try (SolrCore core = cc.getCore(coreName)) {
-      if (core != null) {
-        core.getCoreDescriptor().getCloudDescriptor().setLeader(false);
-      }
-    }
-    
-    super.cancelElection();
-  }
-  
-  @Override
-  public ElectionContext copy() {
-    return new ShardLeaderElectionContext(leaderElector, shardId, collection, id, leaderProps, zkController, cc);
-  }
-  
-  /* 
-   * weAreReplacement: has someone else been the leader already?
-   */
-  @Override
-  void runLeaderProcess(boolean weAreReplacement, int pauseBeforeStart) throws KeeperException,
- InterruptedException, IOException {
-    String coreName = leaderProps.getStr(ZkStateReader.CORE_NAME_PROP);
-    ActionThrottle lt;
-    try (SolrCore core = cc.getCore(coreName)) {
-      if (core == null ) {
-        // shutdown or removed
-        return;
-      }
-      MDCLoggingContext.setCore(core);
-      lt = core.getUpdateHandler().getSolrCoreState().getLeaderThrottle();
-    }
-
-    try {
-      lt.minimumWaitBetweenActions();
-      lt.markAttemptingAction();
-      
-      
-      int leaderVoteWait = cc.getZkController().getLeaderVoteWait();
-      
-      log.debug("Running the leader process for shard={} and weAreReplacement={} and leaderVoteWait={}", shardId, weAreReplacement, leaderVoteWait);
-      if (zkController.getClusterState().getCollection(collection).getSlice(shardId).getReplicas().size() > 1) {
-        // Clear the leader in clusterstate. We only need to worry about this if there is actually more than one replica.
-        ZkNodeProps m = new ZkNodeProps(Overseer.QUEUE_OPERATION, OverseerAction.LEADER.toLower(),
-            ZkStateReader.SHARD_ID_PROP, shardId, ZkStateReader.COLLECTION_PROP, collection);
-        zkController.getOverseer().getStateUpdateQueue().offer(Utils.toJSON(m));
-      }
-
-      boolean allReplicasInLine = false;
-      if (!weAreReplacement) {
-        allReplicasInLine = waitForReplicasToComeUp(leaderVoteWait);
-      } else {
-        allReplicasInLine = areAllReplicasParticipating();
-      }
-      
-      if (isClosed) {
-        // Solr is shutting down or the ZooKeeper session expired while waiting for replicas. If the later, 
-        // we cannot be sure we are still the leader, so we should bail out. The OnReconnect handler will 
-        // re-register the cores and handle a new leadership election.
-        return;
-      }
-      
-      Replica.Type replicaType;
-      String coreNodeName;
-      boolean setTermToMax = false;
-      try (SolrCore core = cc.getCore(coreName)) {
-        
-        if (core == null) {
-          return;
-        }
-        
-        replicaType = core.getCoreDescriptor().getCloudDescriptor().getReplicaType();
-        coreNodeName = core.getCoreDescriptor().getCloudDescriptor().getCoreNodeName();
-        // should I be leader?
-        ZkShardTerms zkShardTerms = zkController.getShardTerms(collection, shardId);
-        if (zkShardTerms.registered(coreNodeName) && !zkShardTerms.canBecomeLeader(coreNodeName)) {
-          if (!waitForEligibleBecomeLeaderAfterTimeout(zkShardTerms, coreNodeName, leaderVoteWait)) {
-            rejoinLeaderElection(core);
-            return;
-          } else {
-            // only log an error if this replica win the election
-            setTermToMax = true;
-          }
-        }
-
-        if (isClosed) {
-          return;
-        }
-        
-        log.info("I may be the new leader - try and sync");
-        
-        // we are going to attempt to be the leader
-        // first cancel any current recovery
-        core.getUpdateHandler().getSolrCoreState().cancelRecovery();
-        
-        if (weAreReplacement) {
-          // wait a moment for any floating updates to finish
-          try {
-            Thread.sleep(2500);
-          } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            throw new SolrException(ErrorCode.SERVICE_UNAVAILABLE, e);
-          }
-        }
-
-        PeerSync.PeerSyncResult result = null;
-        boolean success = false;
-        try {
-          result = syncStrategy.sync(zkController, core, leaderProps, weAreReplacement);
-          success = result.isSuccess();
-        } catch (Exception e) {
-          SolrException.log(log, "Exception while trying to sync", e);
-          result = PeerSync.PeerSyncResult.failure();
-        }
-        
-        UpdateLog ulog = core.getUpdateHandler().getUpdateLog();
-        
-        if (!success) {
-          boolean hasRecentUpdates = false;
-          if (ulog != null) {
-            // TODO: we could optimize this if necessary
-            try (UpdateLog.RecentUpdates recentUpdates = ulog.getRecentUpdates()) {
-              hasRecentUpdates = !recentUpdates.getVersions(1).isEmpty();
-            }
-          }
-          
-          if (!hasRecentUpdates) {
-            // we failed sync, but we have no versions - we can't sync in that case
-            // - we were active
-            // before, so become leader anyway if no one else has any versions either
-            if (result.getOtherHasVersions().orElse(false))  {
-              log.info("We failed sync, but we have no versions - we can't sync in that case. But others have some versions, so we should not become leader");
-              success = false;
-            } else  {
-              log.info(
-                  "We failed sync, but we have no versions - we can't sync in that case - we were active before, so become leader anyway");
-              success = true;
-            }
-          }
-        }
-        
-        // solrcloud_debug
-        if (log.isDebugEnabled()) {
-          try {
-            RefCounted<SolrIndexSearcher> searchHolder = core.getNewestSearcher(false);
-            SolrIndexSearcher searcher = searchHolder.get();
-            try {
-              if (log.isDebugEnabled()) {
-                log.debug("{} synched {}", core.getCoreContainer().getZkController().getNodeName()
-                    , searcher.count(new MatchAllDocsQuery()));
-              }
-            } finally {
-              searchHolder.decref();
-            }
-          } catch (Exception e) {
-            log.error("Error in solrcloud_debug block", e);
-          }
-        }
-        if (!success) {
-          rejoinLeaderElection(core);
-          return;
-        }
-        
-      }
-      
-      boolean isLeader = true;
-      if (!isClosed) {
-        try {
-          if (replicaType == Replica.Type.TLOG) {
-            // stop replicate from old leader
-            zkController.stopReplicationFromLeader(coreName);
-            if (weAreReplacement) {
-              try (SolrCore core = cc.getCore(coreName)) {
-                Future<UpdateLog.RecoveryInfo> future = core.getUpdateHandler().getUpdateLog().recoverFromCurrentLog();
-                if (future != null) {
-                  log.info("Replaying tlog before become new leader");
-                  future.get();
-                } else {
-                  log.info("New leader does not have old tlog to replay");
-                }
-              }
-            }
-          }
-          // in case of leaderVoteWait timeout, a replica with lower term can win the election
-          if (setTermToMax) {
-            log.error("WARNING: Potential data loss -- Replica {} became leader after timeout (leaderVoteWait) {}"
-                , "without being up-to-date with the previous leader", coreNodeName);
-            zkController.getShardTerms(collection, shardId).setTermEqualsToLeader(coreNodeName);
-          }
-          super.runLeaderProcess(weAreReplacement, 0);
-          try (SolrCore core = cc.getCore(coreName)) {
-            if (core != null) {
-              core.getCoreDescriptor().getCloudDescriptor().setLeader(true);
-              publishActiveIfRegisteredAndNotActive(core);
-            } else {
-              return;
-            }
-          }
-          if (log.isInfoEnabled()) {
-            log.info("I am the new leader: {} {}", ZkCoreNodeProps.getCoreUrl(leaderProps), shardId);
-          }
-          
-          // we made it as leader - send any recovery requests we need to
-          syncStrategy.requestRecoveries();
-
-        } catch (SessionExpiredException e) {
-          throw new SolrException(ErrorCode.SERVER_ERROR,
-              "ZK session expired - cancelling election for " + collection + " " + shardId);
-        } catch (Exception e) {
-          isLeader = false;
-          SolrException.log(log, "There was a problem trying to register as the leader", e);
-          
-          try (SolrCore core = cc.getCore(coreName)) {
-            
-            if (core == null) {
-              if (log.isDebugEnabled()) {
-                log.debug("SolrCore not found: {} in {}", coreName, cc.getLoadedCoreNames());
-              }
-              return;
-            }
-            
-            core.getCoreDescriptor().getCloudDescriptor().setLeader(false);
-            
-            // we could not publish ourselves as leader - try and rejoin election
-            try {
-              rejoinLeaderElection(core);
-            } catch (SessionExpiredException exc) {
-              throw new SolrException(ErrorCode.SERVER_ERROR,
-                  "ZK session expired - cancelling election for " + collection + " " + shardId);
-            }
-          }
-        }
-      } else {
-        cancelElection();
-      }
-    } finally {
-      MDCLoggingContext.clear();
-    }
-  }
-
-  /**
-   * Wait for other replicas with higher terms participate in the electioon
-   * @return true if after {@code timeout} there are no other replicas with higher term participate in the election,
-   * false if otherwise
-   */
-  private boolean waitForEligibleBecomeLeaderAfterTimeout(ZkShardTerms zkShardTerms, String coreNodeName, int timeout) throws InterruptedException {
-    long timeoutAt = System.nanoTime() + TimeUnit.NANOSECONDS.convert(timeout, TimeUnit.MILLISECONDS);
-    while (!isClosed && !cc.isShutDown()) {
-      if (System.nanoTime() > timeoutAt) {
-        log.warn("After waiting for {}ms, no other potential leader was found, {} try to become leader anyway (core_term:{}, highest_term:{})",
-            timeout, coreNodeName, zkShardTerms.getTerm(coreNodeName), zkShardTerms.getHighestTerm());
-        return true;
-      }
-      if (replicasWithHigherTermParticipated(zkShardTerms, coreNodeName)) {
-        log.info("Can't become leader, other replicas with higher term participated in leader election");
-        return false;
-      }
-      Thread.sleep(500L);
-    }
-    return false;
-  }
-
-  /**
-   * Do other replicas with higher term participated in the election
-   * @return true if other replicas with higher term participated in the election, false if otherwise
-   */
-  private boolean replicasWithHigherTermParticipated(ZkShardTerms zkShardTerms, String coreNodeName) {
-    ClusterState clusterState = zkController.getClusterState();
-    DocCollection docCollection = clusterState.getCollectionOrNull(collection);
-    Slice slices = (docCollection == null) ? null : docCollection.getSlice(shardId);
-    if (slices == null) return false;
-
-    long replicaTerm = zkShardTerms.getTerm(coreNodeName);
-    boolean isRecovering = zkShardTerms.isRecovering(coreNodeName);
-
-    for (Replica replica : slices.getReplicas()) {
-      if (replica.getName().equals(coreNodeName)) continue;
-
-      if (clusterState.getLiveNodes().contains(replica.getNodeName())) {
-        long otherTerm = zkShardTerms.getTerm(replica.getName());
-        boolean isOtherReplicaRecovering = zkShardTerms.isRecovering(replica.getName());
-
-        if (isRecovering && !isOtherReplicaRecovering) return true;
-        if (otherTerm > replicaTerm) return true;
-      }
-    }
-    return false;
-  }
-
-  public void publishActiveIfRegisteredAndNotActive(SolrCore core) throws Exception {
-      if (core.getCoreDescriptor().getCloudDescriptor().hasRegistered()) {
-        ZkStateReader zkStateReader = zkController.getZkStateReader();
-        zkStateReader.forceUpdateCollection(collection);
-        ClusterState clusterState = zkStateReader.getClusterState();
-        Replica rep = getReplica(clusterState, collection, leaderProps.getStr(ZkStateReader.CORE_NODE_NAME_PROP));
-        if (rep == null) return;
-        if (rep.getState() != Replica.State.ACTIVE || core.getCoreDescriptor().getCloudDescriptor().getLastPublished() != Replica.State.ACTIVE) {
-          log.debug("We have become the leader after core registration but are not in an ACTIVE state - publishing ACTIVE");
-          zkController.publish(core.getCoreDescriptor(), Replica.State.ACTIVE);
-        }
-      }
-  }
-  
-  private Replica getReplica(ClusterState clusterState, String collectionName, String replicaName) {
-    if (clusterState == null) return null;
-    final DocCollection docCollection = clusterState.getCollectionOrNull(collectionName);
-    if (docCollection == null) return null;
-    return docCollection.getReplica(replicaName);
-  }
-
-  // returns true if all replicas are found to be up, false if not
-  private boolean waitForReplicasToComeUp(int timeoutms) throws InterruptedException {
-    long timeoutAt = System.nanoTime() + TimeUnit.NANOSECONDS.convert(timeoutms, TimeUnit.MILLISECONDS);
-    final String shardsElectZkPath = electionPath + LeaderElector.ELECTION_NODE;
-    
-    DocCollection docCollection = zkController.getClusterState().getCollectionOrNull(collection);
-    Slice slices = (docCollection == null) ? null : docCollection.getSlice(shardId);
-    int cnt = 0;
-    while (!isClosed && !cc.isShutDown()) {
-      // wait for everyone to be up
-      if (slices != null) {
-        int found = 0;
-        try {
-          found = zkClient.getChildren(shardsElectZkPath, null, true).size();
-        } catch (KeeperException e) {
-          if (e instanceof KeeperException.SessionExpiredException) {
-            // if the session has expired, then another election will be launched, so
-            // quit here
-            throw new SolrException(ErrorCode.SERVER_ERROR,
-                                    "ZK session expired - cancelling election for " + collection + " " + shardId);
-          }
-          SolrException.log(log,
-              "Error checking for the number of election participants", e);
-        }
-        
-        // on startup and after connection timeout, wait for all known shards
-        if (found >= slices.getReplicas(EnumSet.of(Replica.Type.TLOG, Replica.Type.NRT)).size()) {
-          log.info("Enough replicas found to continue.");
-          return true;
-        } else {
-          if (cnt % 40 == 0) {
-            if (log.isInfoEnabled()) {
-              log.info("Waiting until we see more replicas up for shard {}: total={} found={} timeoute in={}ms"
-                  , shardId, slices.getReplicas(EnumSet.of(Replica.Type.TLOG, Replica.Type.NRT)).size(), found,
-                  TimeUnit.MILLISECONDS.convert(timeoutAt - System.nanoTime(), TimeUnit.NANOSECONDS));
-            }
-          }
-        }
-        
-        if (System.nanoTime() > timeoutAt) {
-          log.info("Was waiting for replicas to come up, but they are taking too long - assuming they won't come back till later");
-          return false;
-        }
-      } else {
-        log.warn("Shard not found: {} for collection {}", shardId, collection);
-
-        return false;
-
-      }
-      
-      Thread.sleep(500);
-      docCollection = zkController.getClusterState().getCollectionOrNull(collection);
-      slices = (docCollection == null) ? null : docCollection.getSlice(shardId);
-      cnt++;
-    }
-    return false;
-  }
-  
-  // returns true if all replicas are found to be up, false if not
-  private boolean areAllReplicasParticipating() throws InterruptedException {
-    final String shardsElectZkPath = electionPath + LeaderElector.ELECTION_NODE;
-    final DocCollection docCollection = zkController.getClusterState().getCollectionOrNull(collection);
-    
-    if (docCollection != null && docCollection.getSlice(shardId) != null) {
-      final Slice slices = docCollection.getSlice(shardId);
-      int found = 0;
-      try {
-        found = zkClient.getChildren(shardsElectZkPath, null, true).size();
-      } catch (KeeperException e) {
-        if (e instanceof KeeperException.SessionExpiredException) {
-          // if the session has expired, then another election will be launched, so
-          // quit here
-          throw new SolrException(ErrorCode.SERVER_ERROR,
-              "ZK session expired - cancelling election for " + collection + " " + shardId);
-        }
-        SolrException.log(log, "Error checking for the number of election participants", e);
-      }
-      
-      if (found >= slices.getReplicasMap().size()) {
-        log.debug("All replicas are ready to participate in election.");
-        return true;
-      }
-    } else {
-      log.warn("Shard not found: {} for collection {}", shardId, collection);
-      return false;
-    }
-    return false;
-  }
-
-  private void rejoinLeaderElection(SolrCore core)
-      throws InterruptedException, KeeperException, IOException {
-    // remove our ephemeral and re join the election
-    if (cc.isShutDown()) {
-      log.debug("Not rejoining election because CoreContainer is closed");
-      return;
-    }
-    
-    log.info("There may be a better leader candidate than us - going back into recovery");
-    
-    cancelElection();
-    
-    core.getUpdateHandler().getSolrCoreState().doRecovery(cc, core.getCoreDescriptor());
-    
-    leaderElector.joinElection(this, true);
-  }
-
-}
-
-final class OverseerElectionContext extends ElectionContext {
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-  private final SolrZkClient zkClient;
-  private final Overseer overseer;
-  private volatile boolean isClosed = false;
-
-  public OverseerElectionContext(SolrZkClient zkClient, Overseer overseer, final String zkNodeName) {
-    super(zkNodeName, Overseer.OVERSEER_ELECT, Overseer.OVERSEER_ELECT + "/leader", null, zkClient);
-    this.overseer = overseer;
-    this.zkClient = zkClient;
-    try {
-      new ZkCmdExecutor(zkClient.getZkClientTimeout()).ensureExists(Overseer.OVERSEER_ELECT, zkClient);
-    } catch (KeeperException e) {
-      throw new SolrException(ErrorCode.SERVER_ERROR, e);
-    } catch (InterruptedException e) {
-      Thread.currentThread().interrupt();
-      throw new SolrException(ErrorCode.SERVER_ERROR, e);
-    }
-  }
-
-  @Override
-  void runLeaderProcess(boolean weAreReplacement, int pauseBeforeStartMs) throws KeeperException,
-      InterruptedException {
-    if (isClosed) {
-      return;
-    }
-    log.info("I am going to be the leader {}", id);
-    final String id = leaderSeqPath
-        .substring(leaderSeqPath.lastIndexOf("/") + 1);
-    ZkNodeProps myProps = new ZkNodeProps(ID, id);
-
-    zkClient.makePath(leaderPath, Utils.toJSON(myProps),
-        CreateMode.EPHEMERAL, true);
-    if(pauseBeforeStartMs >0){
-      try {
-        Thread.sleep(pauseBeforeStartMs);
-      } catch (InterruptedException e) {
-        Thread.interrupted();
-        log.warn("Wait interrupted ", e);
-      }
-    }
-    synchronized (this) {
-      if (!this.isClosed && !overseer.getZkController().getCoreContainer().isShutDown()) {
-        overseer.start(id);
-      }
-    }
-  }
-  
-  @Override
-  public void cancelElection() throws InterruptedException, KeeperException {
-    super.cancelElection();
-    overseer.close();
-  }
-  
-  @Override
-  public synchronized void close() {
-    this.isClosed  = true;
-    overseer.close();
-  }
-
-  @Override
-  public ElectionContext copy() {
-    return new OverseerElectionContext(zkClient, overseer ,id);
-  }
-  
-  @Override
-  public void joinedElectionFired() {
-    overseer.close();
-  }
-  
-  @Override
-  public void checkIfIamLeaderFired() {
-    // leader changed - close the overseer
-    overseer.close();
-  }
 
-}
diff --git a/solr/core/src/java/org/apache/solr/cloud/OverseerElectionContext.java b/solr/core/src/java/org/apache/solr/cloud/OverseerElectionContext.java
new file mode 100644
index 0000000..e25befa
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/cloud/OverseerElectionContext.java
@@ -0,0 +1,110 @@
+/*
+ * 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.solr.cloud;
+
+import java.lang.invoke.MethodHandles;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.common.cloud.SolrZkClient;
+import org.apache.solr.common.cloud.ZkCmdExecutor;
+import org.apache.solr.common.cloud.ZkNodeProps;
+import org.apache.solr.common.util.Utils;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.KeeperException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.solr.common.params.CommonParams.ID;
+
+final class OverseerElectionContext extends ElectionContext {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+  private final SolrZkClient zkClient;
+  private final Overseer overseer;
+  private volatile boolean isClosed = false;
+
+  public OverseerElectionContext(SolrZkClient zkClient, Overseer overseer, final String zkNodeName) {
+    super(zkNodeName, Overseer.OVERSEER_ELECT, Overseer.OVERSEER_ELECT + "/leader", null, zkClient);
+    this.overseer = overseer;
+    this.zkClient = zkClient;
+    try {
+      new ZkCmdExecutor(zkClient.getZkClientTimeout()).ensureExists(Overseer.OVERSEER_ELECT, zkClient);
+    } catch (KeeperException e) {
+      throw new SolrException(ErrorCode.SERVER_ERROR, e);
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+      throw new SolrException(ErrorCode.SERVER_ERROR, e);
+    }
+  }
+
+  @Override
+  void runLeaderProcess(boolean weAreReplacement, int pauseBeforeStartMs) throws KeeperException,
+      InterruptedException {
+    if (isClosed) {
+      return;
+    }
+    log.info("I am going to be the leader {}", id);
+    final String id = leaderSeqPath
+        .substring(leaderSeqPath.lastIndexOf("/") + 1);
+    ZkNodeProps myProps = new ZkNodeProps(ID, id);
+
+    zkClient.makePath(leaderPath, Utils.toJSON(myProps),
+        CreateMode.EPHEMERAL, true);
+    if (pauseBeforeStartMs > 0) {
+      try {
+        Thread.sleep(pauseBeforeStartMs);
+      } catch (InterruptedException e) {
+        Thread.interrupted();
+        log.warn("Wait interrupted ", e);
+      }
+    }
+    synchronized (this) {
+      if (!this.isClosed && !overseer.getZkController().getCoreContainer().isShutDown()) {
+        overseer.start(id);
+      }
+    }
+  }
+
+  @Override
+  public void cancelElection() throws InterruptedException, KeeperException {
+    super.cancelElection();
+    overseer.close();
+  }
+
+  @Override
+  public synchronized void close() {
+    this.isClosed = true;
+    overseer.close();
+  }
+
+  @Override
+  public ElectionContext copy() {
+    return new OverseerElectionContext(zkClient, overseer, id);
+  }
+
+  @Override
+  public void joinedElectionFired() {
+    overseer.close();
+  }
+
+  @Override
+  public void checkIfIamLeaderFired() {
+    // leader changed - close the overseer
+    overseer.close();
+  }
+
+}
diff --git a/solr/core/src/java/org/apache/solr/cloud/ElectionContext.java b/solr/core/src/java/org/apache/solr/cloud/ShardLeaderElectionContext.java
similarity index 58%
copy from solr/core/src/java/org/apache/solr/cloud/ElectionContext.java
copy to solr/core/src/java/org/apache/solr/cloud/ShardLeaderElectionContext.java
index 9ba4900..f6c96ca 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ElectionContext.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ShardLeaderElectionContext.java
@@ -16,16 +16,12 @@
  */
 package org.apache.solr.cloud;
 
-import java.io.Closeable;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
-import java.util.ArrayList;
 import java.util.EnumSet;
-import java.util.List;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 
-import org.apache.hadoop.fs.Path;
 import org.apache.lucene.search.MatchAllDocsQuery;
 import org.apache.solr.cloud.overseer.OverseerAction;
 import org.apache.solr.common.SolrException;
@@ -34,12 +30,9 @@ import org.apache.solr.common.cloud.ClusterState;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.Replica;
 import org.apache.solr.common.cloud.Slice;
-import org.apache.solr.common.cloud.SolrZkClient;
-import org.apache.solr.common.cloud.ZkCmdExecutor;
 import org.apache.solr.common.cloud.ZkCoreNodeProps;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.cloud.ZkStateReader;
-import org.apache.solr.common.util.RetryUtil;
 import org.apache.solr.common.util.Utils;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.SolrCore;
@@ -48,246 +41,36 @@ import org.apache.solr.search.SolrIndexSearcher;
 import org.apache.solr.update.PeerSync;
 import org.apache.solr.update.UpdateLog;
 import org.apache.solr.util.RefCounted;
-import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException;
-import org.apache.zookeeper.KeeperException.NoNodeException;
-import org.apache.zookeeper.KeeperException.NodeExistsException;
 import org.apache.zookeeper.KeeperException.SessionExpiredException;
-import org.apache.zookeeper.Op;
-import org.apache.zookeeper.OpResult;
-import org.apache.zookeeper.OpResult.SetDataResult;
-import org.apache.zookeeper.ZooDefs;
-import org.apache.zookeeper.data.Stat;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import static org.apache.solr.common.params.CommonParams.ID;
-
-public abstract class ElectionContext implements Closeable {
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-  final String electionPath;
-  final ZkNodeProps leaderProps;
-  final String id;
-  final String leaderPath;
-  volatile String leaderSeqPath;
-  private SolrZkClient zkClient;
-
-  public ElectionContext(final String coreNodeName,
-      final String electionPath, final String leaderPath, final ZkNodeProps leaderProps, final SolrZkClient zkClient) {
-    assert zkClient != null;
-    this.id = coreNodeName;
-    this.electionPath = electionPath;
-    this.leaderPath = leaderPath;
-    this.leaderProps = leaderProps;
-    this.zkClient = zkClient;
-  }
-  
-  public void close() {
-
-  }
-  
-  public void cancelElection() throws InterruptedException, KeeperException {
-    if (leaderSeqPath != null) {
-      try {
-        log.debug("Canceling election {}", leaderSeqPath);
-        zkClient.delete(leaderSeqPath, -1, true);
-      } catch (NoNodeException e) {
-        // fine
-        log.debug("cancelElection did not find election node to remove {}", leaderSeqPath);
-      }
-    } else {
-      log.debug("cancelElection skipped as this context has not been initialized");
-    }
-  }
-
-  abstract void runLeaderProcess(boolean weAreReplacement, int pauseBeforeStartMs) throws KeeperException, InterruptedException, IOException;
-
-  public void checkIfIamLeaderFired() {}
-
-  public void joinedElectionFired() {}
-
-  public  ElectionContext copy(){
-    throw new UnsupportedOperationException("copy");
-  }
-}
-
-class ShardLeaderElectionContextBase extends ElectionContext {
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-  protected final SolrZkClient zkClient;
-  protected String shardId;
-  protected String collection;
-  protected LeaderElector leaderElector;
-  protected ZkStateReader zkStateReader;
-  protected ZkController zkController;
-  private Integer leaderZkNodeParentVersion;
-
-  // Prevents a race between cancelling and becoming leader.
-  private final Object lock = new Object();
-
-  public ShardLeaderElectionContextBase(LeaderElector leaderElector,
-      final String shardId, final String collection, final String coreNodeName,
-      ZkNodeProps props, ZkController zkController) {
-    super(coreNodeName, ZkStateReader.COLLECTIONS_ZKNODE + "/" + collection
-        + "/leader_elect/" + shardId, ZkStateReader.getShardLeadersPath(
-        collection, shardId), props, zkController.getZkClient());
-    this.leaderElector = leaderElector;
-    this.zkStateReader = zkController.getZkStateReader();
-    this.zkClient = zkStateReader.getZkClient();
-    this.zkController = zkController;
-    this.shardId = shardId;
-    this.collection = collection;
-    
-    String parent = new Path(leaderPath).getParent().toString();
-    ZkCmdExecutor zcmd = new ZkCmdExecutor(30000);
-    // only if /collections/{collection} exists already do we succeed in creating this path
-    log.info("make sure parent is created {}", parent);
-    try {
-      zcmd.ensureExists(parent, (byte[])null, CreateMode.PERSISTENT, zkClient, 2);
-    } catch (KeeperException e) {
-      throw new RuntimeException(e);
-    } catch (InterruptedException e) {
-      Thread.currentThread().interrupt();
-      throw new RuntimeException(e);
-    }
-  }
-  
-  @Override
-  public void cancelElection() throws InterruptedException, KeeperException {
-    super.cancelElection();
-    synchronized (lock) {
-      if (leaderZkNodeParentVersion != null) {
-        try {
-          // We need to be careful and make sure we *only* delete our own leader registration node.
-          // We do this by using a multi and ensuring the parent znode of the leader registration node
-          // matches the version we expect - there is a setData call that increments the parent's znode
-          // version whenever a leader registers.
-          log.debug("Removing leader registration node on cancel: {} {}", leaderPath, leaderZkNodeParentVersion);
-          List<Op> ops = new ArrayList<>(2);
-          ops.add(Op.check(new Path(leaderPath).getParent().toString(), leaderZkNodeParentVersion));
-          ops.add(Op.delete(leaderPath, -1));
-          zkClient.multi(ops, true);
-        } catch (KeeperException.NoNodeException nne) {
-          // no problem
-          log.debug("No leader registration node found to remove: {}", leaderPath);
-        } catch (KeeperException.BadVersionException bve) {
-          log.info("Cannot remove leader registration node because the current registered node is not ours: {}", leaderPath);
-          // no problem
-        } catch (InterruptedException e) {
-          throw e;
-        } catch (Exception e) {
-          SolrException.log(log, e);
-        }
-        leaderZkNodeParentVersion = null;
-      } else {
-        log.info("No version found for ephemeral leader parent node, won't remove previous leader registration.");
-      }
-    }
-  }
-  
-  @Override
-  void runLeaderProcess(boolean weAreReplacement, int pauseBeforeStartMs)
-      throws KeeperException, InterruptedException, IOException {
-    // register as leader - if an ephemeral is already there, wait to see if it goes away
-
-    String parent = new Path(leaderPath).getParent().toString();
-    try {
-      RetryUtil.retryOnThrowable(NodeExistsException.class, 60000, 5000, () -> {
-        synchronized (lock) {
-          log.info("Creating leader registration node {} after winning as {}", leaderPath, leaderSeqPath);
-          List<Op> ops = new ArrayList<>(2);
-
-          // We use a multi operation to get the parent nodes version, which will
-          // be used to make sure we only remove our own leader registration node.
-          // The setData call used to get the parent version is also the trigger to
-          // increment the version. We also do a sanity check that our leaderSeqPath exists.
-
-          ops.add(Op.check(leaderSeqPath, -1));
-          ops.add(Op.create(leaderPath, Utils.toJSON(leaderProps), zkClient.getZkACLProvider().getACLsToAdd(leaderPath), CreateMode.EPHEMERAL));
-          ops.add(Op.setData(parent, null, -1));
-          List<OpResult> results;
-
-          results = zkClient.multi(ops, true);
-          for (OpResult result : results) {
-            if (result.getType() == ZooDefs.OpCode.setData) {
-              SetDataResult dresult = (SetDataResult) result;
-              Stat stat = dresult.getStat();
-              leaderZkNodeParentVersion = stat.getVersion();
-              return;
-            }
-          }
-          assert leaderZkNodeParentVersion != null;
-        }
-      });
-    } catch (NoNodeException e) {
-      log.info("Will not register as leader because it seems the election is no longer taking place.");
-      return;
-    } catch (Throwable t) {
-      if (t instanceof OutOfMemoryError) {
-        throw (OutOfMemoryError) t;
-      }
-      throw new SolrException(ErrorCode.SERVER_ERROR, "Could not register as the leader because creating the ephemeral registration node in ZooKeeper failed", t);
-    } 
-    
-    assert shardId != null;
-    boolean isAlreadyLeader = false;
-    if (zkStateReader.getClusterState() != null &&
-        zkStateReader.getClusterState().getCollection(collection).getSlice(shardId).getReplicas().size() < 2) {
-      Replica leader = zkStateReader.getLeader(collection, shardId);
-      if (leader != null
-          && leader.getBaseUrl().equals(leaderProps.get(ZkStateReader.BASE_URL_PROP))
-          && leader.getCoreName().equals(leaderProps.get(ZkStateReader.CORE_NAME_PROP))) {
-        isAlreadyLeader = true;
-      }
-    }
-    if (!isAlreadyLeader) {
-      ZkNodeProps m = ZkNodeProps.fromKeyVals(Overseer.QUEUE_OPERATION, OverseerAction.LEADER.toLower(),
-          ZkStateReader.SHARD_ID_PROP, shardId,
-          ZkStateReader.COLLECTION_PROP, collection,
-          ZkStateReader.BASE_URL_PROP, leaderProps.get(ZkStateReader.BASE_URL_PROP),
-          ZkStateReader.CORE_NAME_PROP, leaderProps.get(ZkStateReader.CORE_NAME_PROP),
-          ZkStateReader.STATE_PROP, Replica.State.ACTIVE.toString());
-      assert zkController != null;
-      assert zkController.getOverseer() != null;
-      zkController.getOverseer().offerStateUpdate(Utils.toJSON(m));
-    }
-  }
-
-  public LeaderElector getLeaderElector() {
-    return leaderElector;
-  }
-
-  Integer getLeaderZkNodeParentVersion() {
-    synchronized (lock) {
-      return leaderZkNodeParentVersion;
-    }
-  }
-}
-
 // add core container and stop passing core around...
 final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-  
+
   private final CoreContainer cc;
   private final SyncStrategy syncStrategy;
 
   private volatile boolean isClosed = false;
-  
-  public ShardLeaderElectionContext(LeaderElector leaderElector, 
-      final String shardId, final String collection,
-      final String coreNodeName, ZkNodeProps props, ZkController zkController, CoreContainer cc) {
+
+  public ShardLeaderElectionContext(LeaderElector leaderElector,
+                                    final String shardId, final String collection,
+                                    final String coreNodeName, ZkNodeProps props, ZkController zkController, CoreContainer cc) {
     super(leaderElector, shardId, collection, coreNodeName, props,
         zkController);
     this.cc = cc;
     syncStrategy = new SyncStrategy(cc);
   }
-  
+
   @Override
   public void close() {
     super.close();
-    this.isClosed  = true;
+    this.isClosed = true;
     syncStrategy.close();
   }
-  
+
   @Override
   public void cancelElection() throws InterruptedException, KeeperException {
     String coreName = leaderProps.getStr(ZkStateReader.CORE_NAME_PROP);
@@ -296,25 +79,25 @@ final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
         core.getCoreDescriptor().getCloudDescriptor().setLeader(false);
       }
     }
-    
+
     super.cancelElection();
   }
-  
+
   @Override
   public ElectionContext copy() {
     return new ShardLeaderElectionContext(leaderElector, shardId, collection, id, leaderProps, zkController, cc);
   }
-  
-  /* 
+
+  /*
    * weAreReplacement: has someone else been the leader already?
    */
   @Override
   void runLeaderProcess(boolean weAreReplacement, int pauseBeforeStart) throws KeeperException,
- InterruptedException, IOException {
+      InterruptedException, IOException {
     String coreName = leaderProps.getStr(ZkStateReader.CORE_NAME_PROP);
     ActionThrottle lt;
     try (SolrCore core = cc.getCore(coreName)) {
-      if (core == null ) {
+      if (core == null) {
         // shutdown or removed
         return;
       }
@@ -325,10 +108,10 @@ final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
     try {
       lt.minimumWaitBetweenActions();
       lt.markAttemptingAction();
-      
-      
+
+
       int leaderVoteWait = cc.getZkController().getLeaderVoteWait();
-      
+
       log.debug("Running the leader process for shard={} and weAreReplacement={} and leaderVoteWait={}", shardId, weAreReplacement, leaderVoteWait);
       if (zkController.getClusterState().getCollection(collection).getSlice(shardId).getReplicas().size() > 1) {
         // Clear the leader in clusterstate. We only need to worry about this if there is actually more than one replica.
@@ -343,23 +126,23 @@ final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
       } else {
         allReplicasInLine = areAllReplicasParticipating();
       }
-      
+
       if (isClosed) {
-        // Solr is shutting down or the ZooKeeper session expired while waiting for replicas. If the later, 
-        // we cannot be sure we are still the leader, so we should bail out. The OnReconnect handler will 
+        // Solr is shutting down or the ZooKeeper session expired while waiting for replicas. If the later,
+        // we cannot be sure we are still the leader, so we should bail out. The OnReconnect handler will
         // re-register the cores and handle a new leadership election.
         return;
       }
-      
+
       Replica.Type replicaType;
       String coreNodeName;
       boolean setTermToMax = false;
       try (SolrCore core = cc.getCore(coreName)) {
-        
+
         if (core == null) {
           return;
         }
-        
+
         replicaType = core.getCoreDescriptor().getCloudDescriptor().getReplicaType();
         coreNodeName = core.getCoreDescriptor().getCloudDescriptor().getCoreNodeName();
         // should I be leader?
@@ -377,20 +160,20 @@ final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
         if (isClosed) {
           return;
         }
-        
+
         log.info("I may be the new leader - try and sync");
-        
+
         // we are going to attempt to be the leader
         // first cancel any current recovery
         core.getUpdateHandler().getSolrCoreState().cancelRecovery();
-        
+
         if (weAreReplacement) {
           // wait a moment for any floating updates to finish
           try {
             Thread.sleep(2500);
           } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
-            throw new SolrException(ErrorCode.SERVICE_UNAVAILABLE, e);
+            throw new SolrException(SolrException.ErrorCode.SERVICE_UNAVAILABLE, e);
           }
         }
 
@@ -403,9 +186,9 @@ final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
           SolrException.log(log, "Exception while trying to sync", e);
           result = PeerSync.PeerSyncResult.failure();
         }
-        
+
         UpdateLog ulog = core.getUpdateHandler().getUpdateLog();
-        
+
         if (!success) {
           boolean hasRecentUpdates = false;
           if (ulog != null) {
@@ -414,22 +197,22 @@ final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
               hasRecentUpdates = !recentUpdates.getVersions(1).isEmpty();
             }
           }
-          
+
           if (!hasRecentUpdates) {
             // we failed sync, but we have no versions - we can't sync in that case
             // - we were active
             // before, so become leader anyway if no one else has any versions either
-            if (result.getOtherHasVersions().orElse(false))  {
+            if (result.getOtherHasVersions().orElse(false)) {
               log.info("We failed sync, but we have no versions - we can't sync in that case. But others have some versions, so we should not become leader");
               success = false;
-            } else  {
+            } else {
               log.info(
                   "We failed sync, but we have no versions - we can't sync in that case - we were active before, so become leader anyway");
               success = true;
             }
           }
         }
-        
+
         // solrcloud_debug
         if (log.isDebugEnabled()) {
           try {
@@ -451,9 +234,9 @@ final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
           rejoinLeaderElection(core);
           return;
         }
-        
+
       }
-      
+
       boolean isLeader = true;
       if (!isClosed) {
         try {
@@ -490,7 +273,7 @@ final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
           if (log.isInfoEnabled()) {
             log.info("I am the new leader: {} {}", ZkCoreNodeProps.getCoreUrl(leaderProps), shardId);
           }
-          
+
           // we made it as leader - send any recovery requests we need to
           syncStrategy.requestRecoveries();
 
@@ -500,18 +283,18 @@ final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
         } catch (Exception e) {
           isLeader = false;
           SolrException.log(log, "There was a problem trying to register as the leader", e);
-          
+
           try (SolrCore core = cc.getCore(coreName)) {
-            
+
             if (core == null) {
               if (log.isDebugEnabled()) {
                 log.debug("SolrCore not found: {} in {}", coreName, cc.getLoadedCoreNames());
               }
               return;
             }
-            
+
             core.getCoreDescriptor().getCloudDescriptor().setLeader(false);
-            
+
             // we could not publish ourselves as leader - try and rejoin election
             try {
               rejoinLeaderElection(core);
@@ -531,6 +314,7 @@ final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
 
   /**
    * Wait for other replicas with higher terms participate in the electioon
+   *
    * @return true if after {@code timeout} there are no other replicas with higher term participate in the election,
    * false if otherwise
    */
@@ -553,6 +337,7 @@ final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
 
   /**
    * Do other replicas with higher term participated in the election
+   *
    * @return true if other replicas with higher term participated in the election, false if otherwise
    */
   private boolean replicasWithHigherTermParticipated(ZkShardTerms zkShardTerms, String coreNodeName) {
@@ -579,19 +364,19 @@ final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
   }
 
   public void publishActiveIfRegisteredAndNotActive(SolrCore core) throws Exception {
-      if (core.getCoreDescriptor().getCloudDescriptor().hasRegistered()) {
-        ZkStateReader zkStateReader = zkController.getZkStateReader();
-        zkStateReader.forceUpdateCollection(collection);
-        ClusterState clusterState = zkStateReader.getClusterState();
-        Replica rep = getReplica(clusterState, collection, leaderProps.getStr(ZkStateReader.CORE_NODE_NAME_PROP));
-        if (rep == null) return;
-        if (rep.getState() != Replica.State.ACTIVE || core.getCoreDescriptor().getCloudDescriptor().getLastPublished() != Replica.State.ACTIVE) {
-          log.debug("We have become the leader after core registration but are not in an ACTIVE state - publishing ACTIVE");
-          zkController.publish(core.getCoreDescriptor(), Replica.State.ACTIVE);
-        }
+    if (core.getCoreDescriptor().getCloudDescriptor().hasRegistered()) {
+      ZkStateReader zkStateReader = zkController.getZkStateReader();
+      zkStateReader.forceUpdateCollection(collection);
+      ClusterState clusterState = zkStateReader.getClusterState();
+      Replica rep = getReplica(clusterState, collection, leaderProps.getStr(ZkStateReader.CORE_NODE_NAME_PROP));
+      if (rep == null) return;
+      if (rep.getState() != Replica.State.ACTIVE || core.getCoreDescriptor().getCloudDescriptor().getLastPublished() != Replica.State.ACTIVE) {
+        log.debug("We have become the leader after core registration but are not in an ACTIVE state - publishing ACTIVE");
+        zkController.publish(core.getCoreDescriptor(), Replica.State.ACTIVE);
       }
+    }
   }
-  
+
   private Replica getReplica(ClusterState clusterState, String collectionName, String replicaName) {
     if (clusterState == null) return null;
     final DocCollection docCollection = clusterState.getCollectionOrNull(collectionName);
@@ -603,7 +388,7 @@ final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
   private boolean waitForReplicasToComeUp(int timeoutms) throws InterruptedException {
     long timeoutAt = System.nanoTime() + TimeUnit.NANOSECONDS.convert(timeoutms, TimeUnit.MILLISECONDS);
     final String shardsElectZkPath = electionPath + LeaderElector.ELECTION_NODE;
-    
+
     DocCollection docCollection = zkController.getClusterState().getCollectionOrNull(collection);
     Slice slices = (docCollection == null) ? null : docCollection.getSlice(shardId);
     int cnt = 0;
@@ -618,12 +403,12 @@ final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
             // if the session has expired, then another election will be launched, so
             // quit here
             throw new SolrException(ErrorCode.SERVER_ERROR,
-                                    "ZK session expired - cancelling election for " + collection + " " + shardId);
+                "ZK session expired - cancelling election for " + collection + " " + shardId);
           }
           SolrException.log(log,
               "Error checking for the number of election participants", e);
         }
-        
+
         // on startup and after connection timeout, wait for all known shards
         if (found >= slices.getReplicas(EnumSet.of(Replica.Type.TLOG, Replica.Type.NRT)).size()) {
           log.info("Enough replicas found to continue.");
@@ -637,7 +422,7 @@ final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
             }
           }
         }
-        
+
         if (System.nanoTime() > timeoutAt) {
           log.info("Was waiting for replicas to come up, but they are taking too long - assuming they won't come back till later");
           return false;
@@ -648,7 +433,7 @@ final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
         return false;
 
       }
-      
+
       Thread.sleep(500);
       docCollection = zkController.getClusterState().getCollectionOrNull(collection);
       slices = (docCollection == null) ? null : docCollection.getSlice(shardId);
@@ -656,12 +441,12 @@ final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
     }
     return false;
   }
-  
+
   // returns true if all replicas are found to be up, false if not
   private boolean areAllReplicasParticipating() throws InterruptedException {
     final String shardsElectZkPath = electionPath + LeaderElector.ELECTION_NODE;
     final DocCollection docCollection = zkController.getClusterState().getCollectionOrNull(collection);
-    
+
     if (docCollection != null && docCollection.getSlice(shardId) != null) {
       final Slice slices = docCollection.getSlice(shardId);
       int found = 0;
@@ -676,7 +461,7 @@ final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
         }
         SolrException.log(log, "Error checking for the number of election participants", e);
       }
-      
+
       if (found >= slices.getReplicasMap().size()) {
         log.debug("All replicas are ready to participate in election.");
         return true;
@@ -695,92 +480,14 @@ final class ShardLeaderElectionContext extends ShardLeaderElectionContextBase {
       log.debug("Not rejoining election because CoreContainer is closed");
       return;
     }
-    
-    log.info("There may be a better leader candidate than us - going back into recovery");
-    
-    cancelElection();
-    
-    core.getUpdateHandler().getSolrCoreState().doRecovery(cc, core.getCoreDescriptor());
-    
-    leaderElector.joinElection(this, true);
-  }
 
-}
-
-final class OverseerElectionContext extends ElectionContext {
-  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
-  private final SolrZkClient zkClient;
-  private final Overseer overseer;
-  private volatile boolean isClosed = false;
+    log.info("There may be a better leader candidate than us - going back into recovery");
 
-  public OverseerElectionContext(SolrZkClient zkClient, Overseer overseer, final String zkNodeName) {
-    super(zkNodeName, Overseer.OVERSEER_ELECT, Overseer.OVERSEER_ELECT + "/leader", null, zkClient);
-    this.overseer = overseer;
-    this.zkClient = zkClient;
-    try {
-      new ZkCmdExecutor(zkClient.getZkClientTimeout()).ensureExists(Overseer.OVERSEER_ELECT, zkClient);
-    } catch (KeeperException e) {
-      throw new SolrException(ErrorCode.SERVER_ERROR, e);
-    } catch (InterruptedException e) {
-      Thread.currentThread().interrupt();
-      throw new SolrException(ErrorCode.SERVER_ERROR, e);
-    }
-  }
+    cancelElection();
 
-  @Override
-  void runLeaderProcess(boolean weAreReplacement, int pauseBeforeStartMs) throws KeeperException,
-      InterruptedException {
-    if (isClosed) {
-      return;
-    }
-    log.info("I am going to be the leader {}", id);
-    final String id = leaderSeqPath
-        .substring(leaderSeqPath.lastIndexOf("/") + 1);
-    ZkNodeProps myProps = new ZkNodeProps(ID, id);
-
-    zkClient.makePath(leaderPath, Utils.toJSON(myProps),
-        CreateMode.EPHEMERAL, true);
-    if(pauseBeforeStartMs >0){
-      try {
-        Thread.sleep(pauseBeforeStartMs);
-      } catch (InterruptedException e) {
-        Thread.interrupted();
-        log.warn("Wait interrupted ", e);
-      }
-    }
-    synchronized (this) {
-      if (!this.isClosed && !overseer.getZkController().getCoreContainer().isShutDown()) {
-        overseer.start(id);
-      }
-    }
-  }
-  
-  @Override
-  public void cancelElection() throws InterruptedException, KeeperException {
-    super.cancelElection();
-    overseer.close();
-  }
-  
-  @Override
-  public synchronized void close() {
-    this.isClosed  = true;
-    overseer.close();
-  }
+    core.getUpdateHandler().getSolrCoreState().doRecovery(cc, core.getCoreDescriptor());
 
-  @Override
-  public ElectionContext copy() {
-    return new OverseerElectionContext(zkClient, overseer ,id);
-  }
-  
-  @Override
-  public void joinedElectionFired() {
-    overseer.close();
-  }
-  
-  @Override
-  public void checkIfIamLeaderFired() {
-    // leader changed - close the overseer
-    overseer.close();
+    leaderElector.joinElection(this, true);
   }
 
 }
diff --git a/solr/core/src/java/org/apache/solr/cloud/ShardLeaderElectionContextBase.java b/solr/core/src/java/org/apache/solr/cloud/ShardLeaderElectionContextBase.java
new file mode 100644
index 0000000..a9afc8d
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/cloud/ShardLeaderElectionContextBase.java
@@ -0,0 +1,194 @@
+/*
+ * 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.solr.cloud;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.List;
+import java.util.ArrayList;
+
+import org.apache.hadoop.fs.Path;
+import org.apache.solr.cloud.overseer.OverseerAction;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.common.cloud.Replica;
+import org.apache.solr.common.cloud.SolrZkClient;
+import org.apache.solr.common.cloud.ZkCmdExecutor;
+import org.apache.solr.common.cloud.ZkNodeProps;
+import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.common.util.RetryUtil;
+import org.apache.solr.common.util.Utils;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.KeeperException.NoNodeException;
+import org.apache.zookeeper.KeeperException.NodeExistsException;
+import org.apache.zookeeper.Op;
+import org.apache.zookeeper.OpResult;
+import org.apache.zookeeper.OpResult.SetDataResult;
+import org.apache.zookeeper.ZooDefs;
+import org.apache.zookeeper.data.Stat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class ShardLeaderElectionContextBase extends ElectionContext {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+  protected final SolrZkClient zkClient;
+  protected String shardId;
+  protected String collection;
+  protected LeaderElector leaderElector;
+  protected ZkStateReader zkStateReader;
+  protected ZkController zkController;
+  private Integer leaderZkNodeParentVersion;
+
+  // Prevents a race between cancelling and becoming leader.
+  private final Object lock = new Object();
+
+  public ShardLeaderElectionContextBase(LeaderElector leaderElector,
+                                        final String shardId, final String collection, final String coreNodeName,
+                                        ZkNodeProps props, ZkController zkController) {
+    super(coreNodeName, ZkStateReader.COLLECTIONS_ZKNODE + "/" + collection
+        + "/leader_elect/" + shardId, ZkStateReader.getShardLeadersPath(
+        collection, shardId), props, zkController.getZkClient());
+    this.leaderElector = leaderElector;
+    this.zkStateReader = zkController.getZkStateReader();
+    this.zkClient = zkStateReader.getZkClient();
+    this.zkController = zkController;
+    this.shardId = shardId;
+    this.collection = collection;
+
+    String parent = new Path(leaderPath).getParent().toString();
+    ZkCmdExecutor zcmd = new ZkCmdExecutor(30000);
+    // only if /collections/{collection} exists already do we succeed in creating this path
+    log.info("make sure parent is created {}", parent);
+    try {
+      zcmd.ensureExists(parent, (byte[]) null, CreateMode.PERSISTENT, zkClient, 2);
+    } catch (KeeperException e) {
+      throw new RuntimeException(e);
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public void cancelElection() throws InterruptedException, KeeperException {
+    super.cancelElection();
+    synchronized (lock) {
+      if (leaderZkNodeParentVersion != null) {
+        // no problem
+        // no problem
+        try {
+          // We need to be careful and make sure we *only* delete our own leader registration node.
+          // We do this by using a multi and ensuring the parent znode of the leader registration node
+          // matches the version we expect - there is a setData call that increments the parent's znode
+          // version whenever a leader registers.
+          log.debug("Removing leader registration node on cancel: {} {}", leaderPath, leaderZkNodeParentVersion);
+          List<Op> ops = new ArrayList<>(2);
+          ops.add(Op.check(new Path(leaderPath).getParent().toString(), leaderZkNodeParentVersion));
+          ops.add(Op.delete(leaderPath, -1));
+          zkClient.multi(ops, true);
+        } catch (InterruptedException e) {
+          throw e;
+        } catch (IllegalArgumentException e) {
+          SolrException.log(log, e);
+        }
+        leaderZkNodeParentVersion = null;
+      } else {
+        log.info("No version found for ephemeral leader parent node, won't remove previous leader registration.");
+      }
+    }
+  }
+
+  @Override
+  void runLeaderProcess(boolean weAreReplacement, int pauseBeforeStartMs)
+      throws KeeperException, InterruptedException, IOException {
+    // register as leader - if an ephemeral is already there, wait to see if it goes away
+
+    String parent = new Path(leaderPath).getParent().toString();
+    try {
+      RetryUtil.retryOnThrowable(NodeExistsException.class, 60000, 5000, () -> {
+        synchronized (lock) {
+          log.info("Creating leader registration node {} after winning as {}", leaderPath, leaderSeqPath);
+          List<Op> ops = new ArrayList<>(2);
+
+          // We use a multi operation to get the parent nodes version, which will
+          // be used to make sure we only remove our own leader registration node.
+          // The setData call used to get the parent version is also the trigger to
+          // increment the version. We also do a sanity check that our leaderSeqPath exists.
+
+          ops.add(Op.check(leaderSeqPath, -1));
+          ops.add(Op.create(leaderPath, Utils.toJSON(leaderProps), zkClient.getZkACLProvider().getACLsToAdd(leaderPath), CreateMode.EPHEMERAL));
+          ops.add(Op.setData(parent, null, -1));
+          List<OpResult> results;
+
+          results = zkClient.multi(ops, true);
+          for (OpResult result : results) {
+            if (result.getType() == ZooDefs.OpCode.setData) {
+              SetDataResult dresult = (SetDataResult) result;
+              Stat stat = dresult.getStat();
+              leaderZkNodeParentVersion = stat.getVersion();
+              return;
+            }
+          }
+          assert leaderZkNodeParentVersion != null;
+        }
+      });
+    } catch (NoNodeException e) {
+      log.info("Will not register as leader because it seems the election is no longer taking place.");
+      return;
+    } catch (Throwable t) {
+      if (t instanceof OutOfMemoryError) {
+        throw (OutOfMemoryError) t;
+      }
+      throw new SolrException(ErrorCode.SERVER_ERROR, "Could not register as the leader because creating the ephemeral registration node in ZooKeeper failed", t);
+    }
+
+    assert shardId != null;
+    boolean isAlreadyLeader = false;
+    if (zkStateReader.getClusterState() != null &&
+        zkStateReader.getClusterState().getCollection(collection).getSlice(shardId).getReplicas().size() < 2) {
+      Replica leader = zkStateReader.getLeader(collection, shardId);
+      if (leader != null
+          && leader.getBaseUrl().equals(leaderProps.get(ZkStateReader.BASE_URL_PROP))
+          && leader.getCoreName().equals(leaderProps.get(ZkStateReader.CORE_NAME_PROP))) {
+        isAlreadyLeader = true;
+      }
+    }
+    if (!isAlreadyLeader) {
+      ZkNodeProps m = ZkNodeProps.fromKeyVals(Overseer.QUEUE_OPERATION, OverseerAction.LEADER.toLower(),
+          ZkStateReader.SHARD_ID_PROP, shardId,
+          ZkStateReader.COLLECTION_PROP, collection,
+          ZkStateReader.BASE_URL_PROP, leaderProps.get(ZkStateReader.BASE_URL_PROP),
+          ZkStateReader.CORE_NAME_PROP, leaderProps.get(ZkStateReader.CORE_NAME_PROP),
+          ZkStateReader.STATE_PROP, Replica.State.ACTIVE.toString());
+      assert zkController != null;
+      assert zkController.getOverseer() != null;
+      zkController.getOverseer().offerStateUpdate(Utils.toJSON(m));
+    }
+  }
+
+  public LeaderElector getLeaderElector() {
+    return leaderElector;
+  }
+
+  Integer getLeaderZkNodeParentVersion() {
+    synchronized (lock) {
+      return leaderZkNodeParentVersion;
+    }
+  }
+}
\ No newline at end of file
diff --git a/solr/core/src/java/org/apache/solr/handler/component/PivotFacetProcessor.java b/solr/core/src/java/org/apache/solr/handler/component/PivotFacetProcessor.java
index 011d662..1069c50 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/PivotFacetProcessor.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/PivotFacetProcessor.java
@@ -74,7 +74,7 @@ public class PivotFacetProcessor extends SimpleFacets
     // rb._statsInfo may be null if stats=false, ie: refine requests
     // if that's the case, but we need to refine w/stats, then we'll lazy init our 
     // own instance of StatsInfo
-    StatsInfo statsInfo = rb._statsInfo; 
+    StatsInfo statsInfo = rb._statsInfo;
 
     SimpleOrderedMap<List<NamedList<Object>>> pivotResponse = new SimpleOrderedMap<>();
     for (String pivotList : pivots) {
@@ -237,7 +237,7 @@ public class PivotFacetProcessor extends SimpleFacets
    *
    * @return A list of StatsFields to compute for this pivot, or the empty list if none
    */
-  private static List<StatsField> getTaggedStatsFields(StatsInfo statsInfo, 
+  private static List<StatsField> getTaggedStatsFields(StatsInfo statsInfo,
                                                        String statsLocalParam) {
     if (null == statsLocalParam || null == statsInfo) {
       return Collections.emptyList();
diff --git a/solr/core/src/java/org/apache/solr/handler/component/StatsComponent.java b/solr/core/src/java/org/apache/solr/handler/component/StatsComponent.java
index fc5c29f..4b80dae 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/StatsComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/StatsComponent.java
@@ -17,15 +17,11 @@
 package org.apache.solr.handler.component;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.LinkedHashMap;
-import java.util.List;
 import java.util.Map;
 
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.ShardParams;
-import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.params.StatsParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
@@ -41,13 +37,13 @@ public class StatsComponent extends SearchComponent {
 
   @Override
   public void prepare(ResponseBuilder rb) throws IOException {
-    if (rb.req.getParams().getBool(StatsParams.STATS,false)) {
-      rb.setNeedDocSet( true );
+    if (rb.req.getParams().getBool(StatsParams.STATS, false)) {
+      rb.setNeedDocSet(true);
       rb.doStats = true;
       rb._statsInfo = new StatsInfo(rb);
       for (StatsField statsField : rb._statsInfo.getStatsFields()) {
         if (statsField.getSchemaField() != null && statsField.getSchemaField().getType().isPointField() && !statsField.getSchemaField().hasDocValues()) {
-          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, 
+          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
               "Can't calculate stats on a PointField without docValues");
         }
       }
@@ -63,8 +59,8 @@ public class StatsComponent extends SearchComponent {
       DocSet docs = statsField.computeBaseDocSet();
       statsValues.put(statsField.getOutputKey(), statsField.computeLocalStatsValues(docs));
     }
-    
-    rb.rsp.add( "stats", convertToResponse(statsValues) );
+
+    rb.rsp.add("stats", convertToResponse(statsValues));
   }
 
   @Override
@@ -96,8 +92,8 @@ public class StatsComponent extends SearchComponent {
     for (ShardResponse srsp : sreq.responses) {
       NamedList stats = null;
       try {
-        stats = (NamedList<NamedList<NamedList<?>>>) 
-          srsp.getSolrResponse().getResponse().get("stats");
+        stats = (NamedList<NamedList<NamedList<?>>>)
+            srsp.getSolrResponse().getResponse().get("stats");
       } catch (Exception e) {
         if (ShardParams.getShardsTolerantAsBool(rb.req.getParams())) {
           continue; // looks like a shard did not return anything
@@ -141,17 +137,17 @@ public class StatsComponent extends SearchComponent {
 
   /**
    * Given a map of {@link StatsValues} using the appropriate response key,
-   * builds up the necessary "stats" data structure for including in the response -- 
+   * builds up the necessary "stats" data structure for including in the response --
    * including the esoteric "stats_fields" wrapper.
    */
   public static NamedList<NamedList<NamedList<?>>> convertToResponse
-    (Map<String,StatsValues> statsValues) {
+  (Map<String, StatsValues> statsValues) {
 
     NamedList<NamedList<NamedList<?>>> stats = new SimpleOrderedMap<>();
     NamedList<NamedList<?>> stats_fields = new SimpleOrderedMap<>();
     stats.add("stats_fields", stats_fields);
-    
-    for (Map.Entry<String,StatsValues> entry : statsValues.entrySet()) {
+
+    for (Map.Entry<String, StatsValues> entry : statsValues.entrySet()) {
       String key = entry.getKey();
       NamedList stv = entry.getValue().getStatsValues();
       stats_fields.add(key, stv);
@@ -169,87 +165,3 @@ public class StatsComponent extends SearchComponent {
   }
 }
 
-/**
- * Models all of the information about stats needed for a single request
- * @see StatsField
- */
-class StatsInfo {
-
-  private final ResponseBuilder rb;
-  private final List<StatsField> statsFields = new ArrayList<>(7);
-  private final Map<String, StatsValues> distribStatsValues = new LinkedHashMap<>();
-  private final Map<String, StatsField> statsFieldMap = new LinkedHashMap<>();
-  private final Map<String, List<StatsField>> tagToStatsFields = new LinkedHashMap<>();
-
-  public StatsInfo(ResponseBuilder rb) { 
-    this.rb = rb;
-    SolrParams params = rb.req.getParams();
-    String[] statsParams = params.getParams(StatsParams.STATS_FIELD);
-    if (null == statsParams) {
-      // no stats.field params, nothing to parse.
-      return;
-    }
-    
-    for (String paramValue : statsParams) {
-      StatsField current = new StatsField(rb, paramValue);
-      statsFields.add(current);
-      for (String tag : current.getTagList()) {
-        List<StatsField> fieldList = tagToStatsFields.get(tag);
-        if (fieldList == null) {
-          fieldList = new ArrayList<>();
-        }
-        fieldList.add(current);
-        tagToStatsFields.put(tag, fieldList);
-      }
-      statsFieldMap.put(current.getOutputKey(), current);
-      distribStatsValues.put(current.getOutputKey(), 
-                             StatsValuesFactory.createStatsValues(current));
-    }
-  }
-
-  /**
-   * Returns an immutable list of {@link StatsField} instances
-   * modeling each of the {@link StatsParams#STATS_FIELD} params specified
-   * as part of this request
-   */
-  public List<StatsField> getStatsFields() {
-    return Collections.unmodifiableList(statsFields);
-  }
-
-  /**
-   * Returns the {@link StatsField} associated with the specified (effective) 
-   * outputKey, or null if there was no {@link StatsParams#STATS_FIELD} param
-   * that would corrispond with that key.
-   */
-  public StatsField getStatsField(String outputKey) {
-    return statsFieldMap.get(outputKey);
-  }
-
-  /**
-   * Return immutable list of {@link StatsField} instances by string tag local parameter.
-   *
-   * @param tag tag local parameter
-   * @return list of stats fields
-   */
-  public List<StatsField> getStatsFieldsByTag(String tag) {
-    List<StatsField> raw = tagToStatsFields.get(tag);
-    if (null == raw) {
-      return Collections.emptyList();
-    } else {
-      return Collections.unmodifiableList(raw);
-    }
-  }
-
-  /**
-   * Returns an immutable map of response key =&gt; {@link StatsValues}
-   * instances for the current distributed request.  
-   * Depending on where we are in the process of handling this request, 
-   * these {@link StatsValues} instances may not be complete -- but they 
-   * will never be null.
-   */
-  public Map<String, StatsValues> getAggregateStatsValues() {
-    return Collections.unmodifiableMap(distribStatsValues);
-  }
-
-}
-
diff --git a/solr/core/src/java/org/apache/solr/handler/component/StatsInfo.java b/solr/core/src/java/org/apache/solr/handler/component/StatsInfo.java
new file mode 100644
index 0000000..f3f2871
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/component/StatsInfo.java
@@ -0,0 +1,108 @@
+/*
+ * 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.solr.handler.component;
+
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.params.StatsParams;
+
+import java.util.*;
+
+/**
+ * Models all of the information about stats needed for a single request
+ *
+ * @see StatsField
+ */
+class StatsInfo {
+
+    private final ResponseBuilder rb;
+    private final List<StatsField> statsFields = new ArrayList<>(7);
+    private final Map<String, StatsValues> distribStatsValues = new LinkedHashMap<>();
+    private final Map<String, StatsField> statsFieldMap = new LinkedHashMap<>();
+    private final Map<String, List<StatsField>> tagToStatsFields = new LinkedHashMap<>();
+
+    public StatsInfo(ResponseBuilder rb) {
+        this.rb = rb;
+        SolrParams params = rb.req.getParams();
+        String[] statsParams = params.getParams(StatsParams.STATS_FIELD);
+        if (null == statsParams) {
+            // no stats.field params, nothing to parse.
+            return;
+        }
+
+        for (String paramValue : statsParams) {
+            StatsField current = new StatsField(rb, paramValue);
+            statsFields.add(current);
+            for (String tag : current.getTagList()) {
+                List<StatsField> fieldList = tagToStatsFields.get(tag);
+                if (fieldList == null) {
+                    fieldList = new ArrayList<>();
+                }
+                fieldList.add(current);
+                tagToStatsFields.put(tag, fieldList);
+            }
+            statsFieldMap.put(current.getOutputKey(), current);
+            distribStatsValues.put(current.getOutputKey(),
+                    StatsValuesFactory.createStatsValues(current));
+        }
+    }
+
+    /**
+     * Returns an immutable list of {@link StatsField} instances
+     * modeling each of the {@link StatsParams#STATS_FIELD} params specified
+     * as part of this request
+     */
+    public List<StatsField> getStatsFields() {
+        return Collections.unmodifiableList(statsFields);
+    }
+
+    /**
+     * Returns the {@link StatsField} associated with the specified (effective)
+     * outputKey, or null if there was no {@link StatsParams#STATS_FIELD} param
+     * that would corrispond with that key.
+     */
+    public StatsField getStatsField(String outputKey) {
+        return statsFieldMap.get(outputKey);
+    }
+
+    /**
+     * Return immutable list of {@link StatsField} instances by string tag local parameter.
+     *
+     * @param tag tag local parameter
+     * @return list of stats fields
+     */
+    public List<StatsField> getStatsFieldsByTag(String tag) {
+        List<StatsField> raw = tagToStatsFields.get(tag);
+        if (null == raw) {
+            return Collections.emptyList();
+        } else {
+            return Collections.unmodifiableList(raw);
+        }
+    }
+
+    /**
+     * Returns an immutable map of response key =&gt; {@link StatsValues}
+     * instances for the current distributed request.
+     * Depending on where we are in the process of handling this request,
+     * these {@link StatsValues} instances may not be complete -- but they
+     * will never be null.
+     */
+    public Map<String, StatsValues> getAggregateStatsValues() {
+        return Collections.unmodifiableMap(distribStatsValues);
+    }
+
+}
diff --git a/solr/core/src/java/org/apache/solr/handler/export/DoubleCmp.java b/solr/core/src/java/org/apache/solr/handler/export/DoubleComp.java
similarity index 69%
rename from solr/core/src/java/org/apache/solr/handler/export/DoubleCmp.java
rename to solr/core/src/java/org/apache/solr/handler/export/DoubleComp.java
index 50341fd..6973948 100644
--- a/solr/core/src/java/org/apache/solr/handler/export/DoubleCmp.java
+++ b/solr/core/src/java/org/apache/solr/handler/export/DoubleComp.java
@@ -19,25 +19,27 @@ package org.apache.solr.handler.export;
 
 interface DoubleComp {
   int compare(double a, double b);
+
   double resetValue();
-}
 
-class DoubleAsc implements DoubleComp {
-  public double resetValue() {
-    return Double.MAX_VALUE;
-  }
 
-  public int compare(double a, double b) {
-    return Double.compare(b, a);
-  }
-}
+  static class DoubleAsc implements DoubleComp {
+    public double resetValue() {
+      return Double.MAX_VALUE;
+    }
 
-class DoubleDesc implements DoubleComp {
-  public double resetValue() {
-    return -Double.MAX_VALUE;
+    public int compare(double a, double b) {
+      return Double.compare(b, a);
+    }
   }
 
-  public int compare(double a, double b) {
-    return Double.compare(a, b);
+  static class DoubleDesc implements DoubleComp {
+    public double resetValue() {
+      return -Double.MAX_VALUE;
+    }
+
+    public int compare(double a, double b) {
+      return Double.compare(a, b);
+    }
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/handler/export/ExportWriter.java b/solr/core/src/java/org/apache/solr/handler/export/ExportWriter.java
index e4d6da0..adacd77 100644
--- a/solr/core/src/java/org/apache/solr/handler/export/ExportWriter.java
+++ b/solr/core/src/java/org/apache/solr/handler/export/ExportWriter.java
@@ -408,41 +408,41 @@ public class ExportWriter implements SolrCore.RawWriter, Closeable {
 
       if (ft instanceof IntValueFieldType) {
         if (reverse) {
-          sortValues[i] = new IntValue(field, new IntDesc());
+          sortValues[i] = new IntValue(field, new IntComp.IntDesc());
         } else {
-          sortValues[i] = new IntValue(field, new IntAsc());
+          sortValues[i] = new IntValue(field, new IntComp.IntAsc());
         }
       } else if (ft instanceof FloatValueFieldType) {
         if (reverse) {
-          sortValues[i] = new FloatValue(field, new FloatDesc());
+          sortValues[i] = new FloatValue(field, new FloatComp.FloatDesc());
         } else {
-          sortValues[i] = new FloatValue(field, new FloatAsc());
+          sortValues[i] = new FloatValue(field, new FloatComp.FloatAsc());
         }
       } else if (ft instanceof DoubleValueFieldType) {
         if (reverse) {
-          sortValues[i] = new DoubleValue(field, new DoubleDesc());
+          sortValues[i] = new DoubleValue(field, new DoubleComp.DoubleDesc());
         } else {
-          sortValues[i] = new DoubleValue(field, new DoubleAsc());
+          sortValues[i] = new DoubleValue(field, new DoubleComp.DoubleAsc());
         }
       } else if (ft instanceof LongValueFieldType) {
         if (reverse) {
-          sortValues[i] = new LongValue(field, new LongDesc());
+          sortValues[i] = new LongValue(field, new LongComp.LongDesc());
         } else {
-          sortValues[i] = new LongValue(field, new LongAsc());
+          sortValues[i] = new LongValue(field, new LongComp.LongAsc());
         }
       } else if (ft instanceof StrField || ft instanceof SortableTextField) {
         LeafReader reader = searcher.getSlowAtomicReader();
         SortedDocValues vals = reader.getSortedDocValues(field);
         if (reverse) {
-          sortValues[i] = new StringValue(vals, field, new IntDesc());
+          sortValues[i] = new StringValue(vals, field, new IntComp.IntDesc());
         } else {
-          sortValues[i] = new StringValue(vals, field, new IntAsc());
+          sortValues[i] = new StringValue(vals, field, new IntComp.IntAsc());
         }
       } else if (ft instanceof DateValueFieldType) {
         if (reverse) {
-          sortValues[i] = new LongValue(field, new LongDesc());
+          sortValues[i] = new LongValue(field, new LongComp.LongDesc());
         } else {
-          sortValues[i] = new LongValue(field, new LongAsc());
+          sortValues[i] = new LongValue(field, new LongComp.LongAsc());
         }
       } else if (ft instanceof BoolField) {
         // This is a bit of a hack, but since the boolean field stores ByteRefs, just like Strings
@@ -451,9 +451,9 @@ public class ExportWriter implements SolrCore.RawWriter, Closeable {
         LeafReader reader = searcher.getSlowAtomicReader();
         SortedDocValues vals = reader.getSortedDocValues(field);
         if (reverse) {
-          sortValues[i] = new StringValue(vals, field, new IntDesc());
+          sortValues[i] = new StringValue(vals, field, new IntComp.IntDesc());
         } else {
-          sortValues[i] = new StringValue(vals, field, new IntAsc());
+          sortValues[i] = new StringValue(vals, field, new IntComp.IntAsc());
         }
       } else {
         throw new IOException("Sort fields must be one of the following types: int,float,long,double,string,date,boolean,SortableText");
diff --git a/solr/core/src/java/org/apache/solr/handler/export/FloatCmp.java b/solr/core/src/java/org/apache/solr/handler/export/FloatComp.java
similarity index 70%
rename from solr/core/src/java/org/apache/solr/handler/export/FloatCmp.java
rename to solr/core/src/java/org/apache/solr/handler/export/FloatComp.java
index 7ef078c..1ce6e57 100644
--- a/solr/core/src/java/org/apache/solr/handler/export/FloatCmp.java
+++ b/solr/core/src/java/org/apache/solr/handler/export/FloatComp.java
@@ -19,26 +19,26 @@ package org.apache.solr.handler.export;
 
 interface FloatComp {
   int compare(float a, float b);
+
   float resetValue();
-}
 
-class FloatAsc implements FloatComp {
-  public float resetValue() {
-    return Float.MAX_VALUE;
-  }
+  static class FloatAsc implements FloatComp {
+    public float resetValue() {
+      return Float.MAX_VALUE;
+    }
 
-  public int compare(float a, float b) {
-    return Float.compare(b, a);
+    public int compare(float a, float b) {
+      return Float.compare(b, a);
+    }
   }
-}
 
-class FloatDesc implements FloatComp {
-  public float resetValue() {
-    return -Float.MAX_VALUE;
-  }
+  static class FloatDesc implements FloatComp {
+    public float resetValue() {
+      return -Float.MAX_VALUE;
+    }
 
-  public int compare(float a, float b) {
-    return Float.compare(a, b);
+    public int compare(float a, float b) {
+      return Float.compare(a, b);
+    }
   }
 }
-
diff --git a/solr/core/src/java/org/apache/solr/handler/export/IntComp.java b/solr/core/src/java/org/apache/solr/handler/export/IntComp.java
index ac83d5d..b44ebc8 100644
--- a/solr/core/src/java/org/apache/solr/handler/export/IntComp.java
+++ b/solr/core/src/java/org/apache/solr/handler/export/IntComp.java
@@ -19,27 +19,29 @@ package org.apache.solr.handler.export;
 
 public interface IntComp {
   int compare(int a, int b);
+
   int resetValue();
-}
 
-class IntAsc implements IntComp {
 
-  public int resetValue() {
-    return Integer.MAX_VALUE;
-  }
+  static class IntAsc implements IntComp {
 
-  public int compare(int a, int b) {
-    return Integer.compare(b, a);
+    public int resetValue() {
+      return Integer.MAX_VALUE;
+    }
+
+    public int compare(int a, int b) {
+      return Integer.compare(b, a);
+    }
   }
-}
 
-class IntDesc implements IntComp {
+  static class IntDesc implements IntComp {
 
-  public int resetValue() {
-    return Integer.MIN_VALUE;
-  }
+    public int resetValue() {
+      return Integer.MIN_VALUE;
+    }
 
-  public int compare(int a, int b) {
-    return Integer.compare(a, b);
+    public int compare(int a, int b) {
+      return Integer.compare(a, b);
+    }
   }
-}
+}
\ No newline at end of file
diff --git a/solr/core/src/java/org/apache/solr/handler/export/LongCmp.java b/solr/core/src/java/org/apache/solr/handler/export/LongComp.java
similarity index 70%
rename from solr/core/src/java/org/apache/solr/handler/export/LongCmp.java
rename to solr/core/src/java/org/apache/solr/handler/export/LongComp.java
index 7d997ac..45a522c 100644
--- a/solr/core/src/java/org/apache/solr/handler/export/LongCmp.java
+++ b/solr/core/src/java/org/apache/solr/handler/export/LongComp.java
@@ -19,27 +19,28 @@ package org.apache.solr.handler.export;
 
 interface LongComp {
   int compare(long a, long b);
+
   long resetValue();
-}
 
-class LongAsc implements LongComp {
+  static class LongAsc implements LongComp {
 
-  public long resetValue() {
-    return Long.MAX_VALUE;
-  }
+    public long resetValue() {
+      return Long.MAX_VALUE;
+    }
 
-  public int compare(long a, long b) {
-    return Long.compare(b, a);
+    public int compare(long a, long b) {
+      return Long.compare(b, a);
+    }
   }
-}
 
-class LongDesc implements LongComp {
+  static class LongDesc implements LongComp {
 
-  public long resetValue() {
-    return Long.MIN_VALUE;
-  }
+    public long resetValue() {
+      return Long.MIN_VALUE;
+    }
 
-  public int compare(long a, long b) {
-    return Long.compare(a, b);
+    public int compare(long a, long b) {
+      return Long.compare(a, b);
+    }
   }
-}
+}
\ No newline at end of file
diff --git a/solr/core/src/java/org/apache/solr/response/transform/ShardAugmenterFactory.java b/solr/core/src/java/org/apache/solr/response/transform/ShardAugmenterFactory.java
index e65bb93..e01ba28 100644
--- a/solr/core/src/java/org/apache/solr/response/transform/ShardAugmenterFactory.java
+++ b/solr/core/src/java/org/apache/solr/response/transform/ShardAugmenterFactory.java
@@ -38,7 +38,7 @@ public class ShardAugmenterFactory extends TransformerFactory
         v = "[not a shard request]";
       }
     }
-    return new ValueAugmenter( field, v );
+    return new ValueAugmenterFactory.ValueAugmenter( field, v );
   }
 }
 
diff --git a/solr/core/src/java/org/apache/solr/response/transform/ValueAugmenterFactory.java b/solr/core/src/java/org/apache/solr/response/transform/ValueAugmenterFactory.java
index d85a302..178fae1 100644
--- a/solr/core/src/java/org/apache/solr/response/transform/ValueAugmenterFactory.java
+++ b/solr/core/src/java/org/apache/solr/response/transform/ValueAugmenterFactory.java
@@ -28,31 +28,28 @@ import org.apache.solr.util.DateMathParser;
  *
  * @since solr 4.0
  */
-public class ValueAugmenterFactory extends TransformerFactory
-{
+public class ValueAugmenterFactory extends TransformerFactory {
   protected Object value = null;
   protected Object defaultValue = null;
 
   @Override
   public void init(NamedList args) {
-    value = args.get( "value" );
-    if( value == null ) {
-      defaultValue = args.get( "defaultValue" );
+    value = args.get("value");
+    if (value == null) {
+      defaultValue = args.get("defaultValue");
     }
   }
 
-  public static Object getObjectFrom( String val, String type )
-  {
-    if( type != null ) {
+  public static Object getObjectFrom(String val, String type) {
+    if (type != null) {
       try {
-        if( "int".equals( type ) ) return Integer.valueOf( val );
-        if( "double".equals( type ) ) return Double.valueOf( val );
-        if( "float".equals( type ) ) return Float.valueOf( val );
-        if( "date".equals( type ) ) return DateMathParser.parseMath(null, val );
-      }
-      catch( Exception ex ) {
-        throw new SolrException( ErrorCode.BAD_REQUEST,
-            "Unable to parse "+type+"="+val, ex );
+        if ("int".equals(type)) return Integer.valueOf(val);
+        if ("double".equals(type)) return Double.valueOf(val);
+        if ("float".equals(type)) return Float.valueOf(val);
+        if ("date".equals(type)) return DateMathParser.parseMath(null, val);
+      } catch (Exception ex) {
+        throw new SolrException(ErrorCode.BAD_REQUEST,
+                "Unable to parse " + type + "=" + val, ex);
       }
     }
     return val;
@@ -61,43 +58,40 @@ public class ValueAugmenterFactory extends TransformerFactory
   @Override
   public DocTransformer create(String field, SolrParams params, SolrQueryRequest req) {
     Object val = value;
-    if( val == null ) {
+    if (val == null) {
       String v = params.get("v");
-      if( v == null ) {
+      if (v == null) {
         val = defaultValue;
-      }
-      else {
+      } else {
         val = getObjectFrom(v, params.get("t"));
       }
-      if( val == null ) {
-        throw new SolrException( ErrorCode.BAD_REQUEST,
-            "ValueAugmenter is missing a value -- should be defined in solrconfig or inline" );
+      if (val == null) {
+        throw new SolrException(ErrorCode.BAD_REQUEST,
+                "ValueAugmenter is missing a value -- should be defined in solrconfig or inline");
       }
     }
-    return new ValueAugmenter( field, val );
+    return new ValueAugmenter(field, val);
   }
-}
 
-class ValueAugmenter extends DocTransformer
-{
-  final String name;
-  final Object value;
 
-  public ValueAugmenter( String name, Object value )
-  {
-    this.name = name;
-    this.value = value;
-  }
+  static class ValueAugmenter extends DocTransformer {
+    final String name;
+    final Object value;
 
-  @Override
-  public String getName()
-  {
-    return name;
-  }
+    public ValueAugmenter(String name, Object value) {
+      this.name = name;
+      this.value = value;
+    }
 
-  @Override
-  public void transform(SolrDocument doc, int docid) {
-    doc.setField( name, value );
+    @Override
+    public String getName() {
+      return name;
+    }
+
+    @Override
+    public void transform(SolrDocument doc, int docid) {
+      doc.setField(name, value);
+    }
   }
 }
 
diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetParser.java b/solr/core/src/java/org/apache/solr/search/facet/FacetParser.java
index 308228b..d8bb697 100644
--- a/solr/core/src/java/org/apache/solr/search/facet/FacetParser.java
+++ b/solr/core/src/java/org/apache/solr/search/facet/FacetParser.java
@@ -16,18 +16,22 @@
  */
 package org.apache.solr.search.facet;
 
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Optional;
+
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.StrUtils;
+import org.apache.solr.search.FunctionQParser;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.schema.IndexSchema;
-import org.apache.solr.search.FunctionQParser;
+import org.apache.solr.search.QParser;
 import org.apache.solr.search.SyntaxError;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
+import static org.apache.solr.common.params.CommonParams.SORT;
 
 abstract class FacetParser<FacetRequestT extends FacetRequest> {
   protected FacetRequestT facet;
@@ -134,9 +138,9 @@ abstract class FacetParser<FacetRequestT extends FacetRequest> {
     switch (type) {
       case "field":
       case "terms":
-        return new FacetRequest.FacetFieldParser(this, key).parse(args);
+        return new FacetFieldParser(this, key).parse(args);
       case "query":
-        return new FacetRequest.FacetQueryParser(this, key).parse(args);
+        return new FacetQueryParser(this, key).parse(args);
       case "range":
         return new FacetRangeParser(this, key).parse(args);
       case "heatmap":
@@ -411,4 +415,223 @@ abstract class FacetParser<FacetRequestT extends FacetRequest> {
     nl.addAll(jsonObject);
     return SolrParams.toSolrParams(nl);
   }
+
+  // TODO Make this private (or at least not static) and introduce
+  // a newInstance method on FacetParser that returns one of these?
+  static class FacetTopParser extends FacetParser<FacetQuery> {
+    private SolrQueryRequest req;
+
+    public FacetTopParser(SolrQueryRequest req) {
+      super(null, "facet");
+      this.facet = new FacetQuery();
+      this.req = req;
+    }
+
+    @Override
+    public FacetQuery parse(Object args) throws SyntaxError {
+      parseSubs(args);
+      return facet;
+    }
+
+    @Override
+    public SolrQueryRequest getSolrRequest() {
+      return req;
+    }
+
+    @Override
+    public IndexSchema getSchema() {
+      return req.getSchema();
+    }
+  }
+
+  static class FacetQueryParser extends FacetParser<FacetQuery> {
+    public FacetQueryParser(@SuppressWarnings("rawtypes") FacetParser parent, String key) {
+      super(parent, key);
+      facet = new FacetQuery();
+    }
+
+    @Override
+    public FacetQuery parse(Object arg) throws SyntaxError {
+      parseCommonParams(arg);
+
+      String qstring = null;
+      if (arg instanceof String) {
+        // just the field name...
+        qstring = (String)arg;
+
+      } else if (arg instanceof Map) {
+        @SuppressWarnings({"unchecked"})
+        Map<String, Object> m = (Map<String, Object>) arg;
+        qstring = getString(m, "q", null);
+        if (qstring == null) {
+          qstring = getString(m, "query", null);
+        }
+
+        // OK to parse subs before we have parsed our own query?
+        // as long as subs don't need to know about it.
+        parseSubs( m.get("facet") );
+      } else if (arg != null) {
+        // something lke json.facet.facet.query=2
+        throw err("Expected string/map for facet query, received " + arg.getClass().getSimpleName() + "=" + arg);
+      }
+
+      // TODO: substats that are from defaults!!!
+
+      if (qstring != null) {
+        QParser parser = QParser.getParser(qstring, getSolrRequest());
+        parser.setIsFilter(true);
+        facet.q = parser.getQuery();
+      }
+
+      return facet;
+    }
+  }
+
+  /*** not a separate type of parser for now...
+   static class FacetBlockParentParser extends FacetParser<FacetBlockParent> {
+   public FacetBlockParentParser(FacetParser parent, String key) {
+   super(parent, key);
+   facet = new FacetBlockParent();
+   }
+
+   @Override
+   public FacetBlockParent parse(Object arg) throws SyntaxError {
+   parseCommonParams(arg);
+
+   if (arg instanceof String) {
+   // just the field name...
+   facet.parents = (String)arg;
+
+   } else if (arg instanceof Map) {
+   Map<String, Object> m = (Map<String, Object>) arg;
+   facet.parents = getString(m, "parents", null);
+
+   parseSubs( m.get("facet") );
+   }
+
+   return facet;
+   }
+   }
+   ***/
+
+  static class FacetFieldParser extends FacetParser<FacetField> {
+    @SuppressWarnings({"rawtypes"})
+    public FacetFieldParser(FacetParser parent, String key) {
+      super(parent, key);
+      facet = new FacetField();
+    }
+
+    public FacetField parse(Object arg) throws SyntaxError {
+      parseCommonParams(arg);
+      if (arg instanceof String) {
+        // just the field name...
+        facet.field = (String)arg;
+
+      } else if (arg instanceof Map) {
+        @SuppressWarnings({"unchecked"})
+        Map<String, Object> m = (Map<String, Object>) arg;
+        facet.field = getField(m);
+        facet.offset = getLong(m, "offset", facet.offset);
+        facet.limit = getLong(m, "limit", facet.limit);
+        facet.overrequest = (int) getLong(m, "overrequest", facet.overrequest);
+        facet.overrefine = (int) getLong(m, "overrefine", facet.overrefine);
+        if (facet.limit == 0) facet.offset = 0;  // normalize.  an offset with a limit of non-zero isn't useful.
+        facet.mincount = getLong(m, "mincount", facet.mincount);
+        facet.missing = getBoolean(m, "missing", facet.missing);
+        facet.numBuckets = getBoolean(m, "numBuckets", facet.numBuckets);
+        facet.prefix = getString(m, "prefix", facet.prefix);
+        facet.allBuckets = getBoolean(m, "allBuckets", facet.allBuckets);
+        facet.method = FacetField.FacetMethod.fromString(getString(m, "method", null));
+        facet.cacheDf = (int)getLong(m, "cacheDf", facet.cacheDf);
+
+        // TODO: pull up to higher level?
+        facet.refine = FacetRequest.RefineMethod.fromObj(m.get("refine"));
+
+        facet.perSeg = getBooleanOrNull(m, "perSeg");
+
+        // facet.sort may depend on a facet stat...
+        // should we be parsing / validating this here, or in the execution environment?
+        Object o = m.get("facet");
+        parseSubs(o);
+
+        facet.sort = parseAndValidateSort(facet, m, SORT);
+        facet.prelim_sort = parseAndValidateSort(facet, m, "prelim_sort");
+      } else if (arg != null) {
+        // something like json.facet.facet.field=2
+        throw err("Expected string/map for facet field, received " + arg.getClass().getSimpleName() + "=" + arg);
+      }
+
+      if (null == facet.sort) {
+        facet.sort = FacetRequest.FacetSort.COUNT_DESC;
+      }
+
+      return facet;
+    }
+
+    /**
+     * Parses, validates and returns the {@link FacetRequest.FacetSort} for given sortParam
+     * and facet field
+     * <p>
+     *   Currently, supported sort specifications are 'mystat desc' OR {mystat: 'desc'}
+     *   index - This is equivalent to 'index asc'
+     *   count - This is equivalent to 'count desc'
+     * </p>
+     *
+     * @param facet {@link FacetField} for which sort needs to be parsed and validated
+     * @param args map containing the sortVal for given sortParam
+     * @param sortParam parameter for which sort needs to parsed and validated
+     * @return parsed facet sort
+     */
+    private static FacetRequest.FacetSort parseAndValidateSort(FacetField facet, Map<String, Object> args, String sortParam) {
+      Object sort = args.get(sortParam);
+      if (sort == null) {
+        return null;
+      }
+
+      FacetRequest.FacetSort facetSort = null;
+
+      if (sort instanceof String) {
+        String sortStr = (String)sort;
+        if (sortStr.endsWith(" asc")) {
+          facetSort =  new FacetRequest.FacetSort(sortStr.substring(0, sortStr.length()-" asc".length()),
+                  FacetRequest.SortDirection.asc);
+        } else if (sortStr.endsWith(" desc")) {
+          facetSort =  new FacetRequest.FacetSort(sortStr.substring(0, sortStr.length()-" desc".length()),
+                  FacetRequest.SortDirection.desc);
+        } else {
+          facetSort =  new FacetRequest.FacetSort(sortStr,
+                  // default direction for "index" is ascending
+                  ("index".equals(sortStr)
+                          ? FacetRequest.SortDirection.asc
+                          : FacetRequest.SortDirection.desc));
+        }
+      } else if (sort instanceof Map) {
+        // { myvar : 'desc' }
+        @SuppressWarnings("unchecked")
+        Optional<Map.Entry<String,Object>> optional = ((Map<String,Object>)sort).entrySet().stream().findFirst();
+        if (optional.isPresent()) {
+          Map.Entry<String, Object> entry = optional.get();
+          facetSort = new FacetRequest.FacetSort(entry.getKey(), FacetRequest.SortDirection.fromObj(entry.getValue()));
+        }
+      } else {
+        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+                "Expected string/map for '" + sortParam +"', received "+ sort.getClass().getSimpleName() + "=" + sort);
+      }
+
+      Map<String, AggValueSource> facetStats = facet.facetStats;
+      // validate facet sort
+      boolean isValidSort = facetSort == null ||
+              "index".equals(facetSort.sortVariable) ||
+              "count".equals(facetSort.sortVariable) ||
+              (facetStats != null && facetStats.containsKey(facetSort.sortVariable));
+
+      if (!isValidSort) {
+        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+                "Invalid " + sortParam + " option '" + sort + "' for field '" + facet.field + "'");
+      }
+      return facetSort;
+    }
+
+  }
+
 }
diff --git a/solr/core/src/java/org/apache/solr/search/facet/FacetRequest.java b/solr/core/src/java/org/apache/solr/search/facet/FacetRequest.java
index 42f8488..db9d9c9 100644
--- a/solr/core/src/java/org/apache/solr/search/facet/FacetRequest.java
+++ b/solr/core/src/java/org/apache/solr/search/facet/FacetRequest.java
@@ -21,16 +21,13 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Optional;
 
 import org.apache.lucene.search.Query;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.request.SolrQueryRequest;
-import org.apache.solr.schema.IndexSchema;
 import org.apache.solr.search.DocSet;
 import org.apache.solr.search.JoinQParserPlugin;
-import org.apache.solr.search.QParser;
 import org.apache.solr.search.QueryContext;
 import org.apache.solr.search.SolrConstantScoreQuery;
 import org.apache.solr.search.SyntaxError;
@@ -38,7 +35,6 @@ import org.apache.solr.search.join.GraphQuery;
 import org.apache.solr.search.join.GraphQueryParser;
 import org.apache.solr.util.RTimer;
 
-import static org.apache.solr.common.params.CommonParams.SORT;
 import static org.apache.solr.search.facet.FacetRequest.RefineMethod.NONE;
 
 /**
@@ -302,7 +298,7 @@ public abstract class FacetRequest {
    */
   public static FacetRequest parse(SolrQueryRequest req, Map<String, Object> params) {
     @SuppressWarnings({"rawtypes"})
-    FacetParser parser = new FacetTopParser(req);
+    FacetParser parser = new FacetParser.FacetTopParser(req);
     try {
       return parser.parse(params);
     } catch (SyntaxError syntaxError) {
@@ -321,7 +317,7 @@ public abstract class FacetRequest {
    */
   public static FacetRequest parseOneFacetReq(SolrQueryRequest req, Map<String, Object> params) {
     @SuppressWarnings("rawtypes")
-    FacetParser parser = new FacetTopParser(req);
+    FacetParser parser = new FacetParser.FacetTopParser(req);
     try {
       return (FacetRequest) parser.parseFacetOrStat("", params);
     } catch (SyntaxError syntaxError) {
@@ -437,221 +433,6 @@ public abstract class FacetRequest {
   
   public abstract Map<String, Object> getFacetDescription();
 
-  static class FacetTopParser extends FacetParser<FacetQuery> {
-    private SolrQueryRequest req;
-
-    public FacetTopParser(SolrQueryRequest req) {
-      super(null, "facet");
-      this.facet = new FacetQuery();
-      this.req = req;
-    }
-
-    @Override
-    public FacetQuery parse(Object args) throws SyntaxError {
-      parseSubs(args);
-      return facet;
-    }
-
-    @Override
-    public SolrQueryRequest getSolrRequest() {
-      return req;
-    }
-
-    @Override
-    public IndexSchema getSchema() {
-      return req.getSchema();
-    }
-  }
-
-  static class FacetQueryParser extends FacetParser<FacetQuery> {
-    public FacetQueryParser(@SuppressWarnings("rawtypes") FacetParser parent, String key) {
-      super(parent, key);
-      facet = new FacetQuery();
-    }
-
-    @Override
-    public FacetQuery parse(Object arg) throws SyntaxError {
-      parseCommonParams(arg);
-
-      String qstring = null;
-      if (arg instanceof String) {
-        // just the field name...
-        qstring = (String)arg;
-
-      } else if (arg instanceof Map) {
-        @SuppressWarnings({"unchecked"})
-        Map<String, Object> m = (Map<String, Object>) arg;
-        qstring = getString(m, "q", null);
-        if (qstring == null) {
-          qstring = getString(m, "query", null);
-        }
-
-        // OK to parse subs before we have parsed our own query?
-        // as long as subs don't need to know about it.
-        parseSubs( m.get("facet") );
-      } else if (arg != null) {
-        // something lke json.facet.facet.query=2
-        throw err("Expected string/map for facet query, received " + arg.getClass().getSimpleName() + "=" + arg);
-      }
-
-      // TODO: substats that are from defaults!!!
-
-      if (qstring != null) {
-        QParser parser = QParser.getParser(qstring, getSolrRequest());
-        parser.setIsFilter(true);
-        facet.q = parser.getQuery();
-      }
-
-      return facet;
-    }
-  }
-
-/*** not a separate type of parser for now...
-static class FacetBlockParentParser extends FacetParser<FacetBlockParent> {
- public FacetBlockParentParser(FacetParser parent, String key) {
- super(parent, key);
- facet = new FacetBlockParent();
- }
-
- @Override
- public FacetBlockParent parse(Object arg) throws SyntaxError {
- parseCommonParams(arg);
-
- if (arg instanceof String) {
- // just the field name...
- facet.parents = (String)arg;
-
- } else if (arg instanceof Map) {
- Map<String, Object> m = (Map<String, Object>) arg;
- facet.parents = getString(m, "parents", null);
-
- parseSubs( m.get("facet") );
- }
-
- return facet;
- }
- }
- ***/
-
-  static class FacetFieldParser extends FacetParser<FacetField> {
-    @SuppressWarnings({"rawtypes"})
-    public FacetFieldParser(FacetParser parent, String key) {
-      super(parent, key);
-      facet = new FacetField();
-    }
-
-    public FacetField parse(Object arg) throws SyntaxError {
-      parseCommonParams(arg);
-      if (arg instanceof String) {
-        // just the field name...
-        facet.field = (String)arg;
-
-      } else if (arg instanceof Map) {
-        @SuppressWarnings({"unchecked"})
-        Map<String, Object> m = (Map<String, Object>) arg;
-        facet.field = getField(m);
-        facet.offset = getLong(m, "offset", facet.offset);
-        facet.limit = getLong(m, "limit", facet.limit);
-        facet.overrequest = (int) getLong(m, "overrequest", facet.overrequest);
-        facet.overrefine = (int) getLong(m, "overrefine", facet.overrefine);
-        if (facet.limit == 0) facet.offset = 0;  // normalize.  an offset with a limit of non-zero isn't useful.
-        facet.mincount = getLong(m, "mincount", facet.mincount);
-        facet.missing = getBoolean(m, "missing", facet.missing);
-        facet.numBuckets = getBoolean(m, "numBuckets", facet.numBuckets);
-        facet.prefix = getString(m, "prefix", facet.prefix);
-        facet.allBuckets = getBoolean(m, "allBuckets", facet.allBuckets);
-        facet.method = FacetField.FacetMethod.fromString(getString(m, "method", null));
-        facet.cacheDf = (int)getLong(m, "cacheDf", facet.cacheDf);
-
-        // TODO: pull up to higher level?
-        facet.refine = RefineMethod.fromObj(m.get("refine"));
-
-        facet.perSeg = getBooleanOrNull(m, "perSeg");
-
-        // facet.sort may depend on a facet stat...
-        // should we be parsing / validating this here, or in the execution environment?
-        Object o = m.get("facet");
-        parseSubs(o);
-
-        facet.sort = parseAndValidateSort(facet, m, SORT);
-        facet.prelim_sort = parseAndValidateSort(facet, m, "prelim_sort");
-      } else if (arg != null) {
-        // something like json.facet.facet.field=2
-        throw err("Expected string/map for facet field, received " + arg.getClass().getSimpleName() + "=" + arg);
-      }
-
-      if (null == facet.sort) {
-        facet.sort = FacetSort.COUNT_DESC;
-      }
-
-      return facet;
-    }
-
-    /**
-     * Parses, validates and returns the {@link FacetSort} for given sortParam
-     * and facet field
-     * <p>
-     *   Currently, supported sort specifications are 'mystat desc' OR {mystat: 'desc'}
-     *   index - This is equivalent to 'index asc'
-     *   count - This is equivalent to 'count desc'
-     * </p>
-     *
-     * @param facet {@link FacetField} for which sort needs to be parsed and validated
-     * @param args map containing the sortVal for given sortParam
-     * @param sortParam parameter for which sort needs to parsed and validated
-     * @return parsed facet sort
-     */
-    private static FacetSort parseAndValidateSort(FacetField facet, Map<String, Object> args, String sortParam) {
-      Object sort = args.get(sortParam);
-      if (sort == null) {
-        return null;
-      }
-
-      FacetSort facetSort = null;
-
-      if (sort instanceof String) {
-        String sortStr = (String)sort;
-        if (sortStr.endsWith(" asc")) {
-          facetSort =  new FacetSort(sortStr.substring(0, sortStr.length()-" asc".length()),
-              SortDirection.asc);
-        } else if (sortStr.endsWith(" desc")) {
-          facetSort =  new FacetSort(sortStr.substring(0, sortStr.length()-" desc".length()),
-              SortDirection.desc);
-        } else {
-          facetSort =  new FacetSort(sortStr,
-              // default direction for "index" is ascending
-              ("index".equals(sortStr)
-                  ? SortDirection.asc
-                  : SortDirection.desc));
-        }
-      } else if (sort instanceof Map) {
-        // { myvar : 'desc' }
-        @SuppressWarnings("unchecked")
-        Optional<Map.Entry<String,Object>> optional = ((Map<String,Object>)sort).entrySet().stream().findFirst();
-        if (optional.isPresent()) {
-          Map.Entry<String, Object> entry = optional.get();
-          facetSort = new FacetSort(entry.getKey(), SortDirection.fromObj(entry.getValue()));
-        }
-      } else {
-        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
-            "Expected string/map for '" + sortParam +"', received "+ sort.getClass().getSimpleName() + "=" + sort);
-      }
-
-      Map<String, AggValueSource> facetStats = facet.facetStats;
-      // validate facet sort
-      boolean isValidSort = facetSort == null ||
-          "index".equals(facetSort.sortVariable) ||
-          "count".equals(facetSort.sortVariable) ||
-          (facetStats != null && facetStats.containsKey(facetSort.sortVariable));
-
-      if (!isValidSort) {
-        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
-            "Invalid " + sortParam + " option '" + sort + "' for field '" + facet.field + "'");
-      }
-      return facetSort;
-    }
-
-  }
 
 }
 
diff --git a/solr/core/src/java/org/apache/solr/search/join/GraphTermsCollector.java b/solr/core/src/java/org/apache/solr/search/join/GraphEdgeCollector.java
similarity index 59%
rename from solr/core/src/java/org/apache/solr/search/join/GraphTermsCollector.java
rename to solr/core/src/java/org/apache/solr/search/join/GraphEdgeCollector.java
index 6ca02d3..02ed123 100644
--- a/solr/core/src/java/org/apache/solr/search/join/GraphTermsCollector.java
+++ b/solr/core/src/java/org/apache/solr/search/join/GraphEdgeCollector.java
@@ -53,7 +53,7 @@ abstract class GraphEdgeCollector extends SimpleCollector implements Collector {
   // known leaf nodes
   DocSet leafNodes;
 
-  int numHits=0;    // number of documents visited
+  int numHits = 0;    // number of documents visited
   BitSet bits;  // if not null, used to collect documents visited
 
   int base;
@@ -74,8 +74,10 @@ abstract class GraphEdgeCollector extends SimpleCollector implements Collector {
   }
 
   // the number of docs visited
-  public int getNumHits() { return numHits; }
-  
+  public int getNumHits() {
+    return numHits;
+  }
+
   public void collect(int segDoc) throws IOException {
     int doc = segDoc + base;
     if (skipSet != null && skipSet.exists(doc)) {
@@ -91,19 +93,19 @@ abstract class GraphEdgeCollector extends SimpleCollector implements Collector {
     // Optimization to not look up edges for a document that is a leaf node (i.e. has no outgoing edges)
     if (leafNodes == null || !leafNodes.exists(doc)) {
       addEdgeIdsToResult(segDoc);
-    } 
+    }
     // Note: tracking links in for each result would be a huge memory hog... so not implementing at this time.
   }
-  
+
   abstract void addEdgeIdsToResult(int doc) throws IOException;
-  
+
   private void addDocToResult(int docWithBase) {
     // this document is part of the traversal. mark it in our bitmap.
     bits.set(docWithBase);
     // increment the hit count so we know how many docs we traversed this time.
     numHits++;
   }
-  
+
   @Override
   public void doSetNextReader(LeafReaderContext context) throws IOException {
     base = context.docBase;
@@ -115,87 +117,90 @@ abstract class GraphEdgeCollector extends SimpleCollector implements Collector {
   public ScoreMode scoreMode() {
     return ScoreMode.COMPLETE_NO_SCORES;
   }
-  
-}
 
-class GraphTermsCollector extends GraphEdgeCollector {
-  // all the collected terms
-  private BytesRefHash collectorTerms;
-  private SortedSetDocValues docTermOrds;
 
+  static class GraphTermsCollector extends GraphEdgeCollector {
+    // all the collected terms
+    private BytesRefHash collectorTerms;
+    private SortedSetDocValues docTermOrds;
 
-  GraphTermsCollector(SchemaField collectField, DocSet skipSet, DocSet leafNodes) {
-    super(collectField, skipSet, leafNodes);
-    this.collectorTerms =  new BytesRefHash();
-  }
 
-  @Override
-  public void doSetNextReader(LeafReaderContext context) throws IOException {
-    super.doSetNextReader(context);
-    // Grab the updated doc values.
-    docTermOrds = DocValues.getSortedSet(context.reader(), collectField.getName());
-  }
+    GraphTermsCollector(SchemaField collectField, DocSet skipSet, DocSet leafNodes) {
+      super(collectField, skipSet, leafNodes);
+      this.collectorTerms = new BytesRefHash();
+    }
 
-  @Override
-  void addEdgeIdsToResult(int doc) throws IOException {
-    // set the doc to pull the edges ids for.
-    if (doc > docTermOrds.docID()) {
-      docTermOrds.advance(doc);
+    @Override
+    public void doSetNextReader(LeafReaderContext context) throws IOException {
+      super.doSetNextReader(context);
+      // Grab the updated doc values.
+      docTermOrds = DocValues.getSortedSet(context.reader(), collectField.getName());
     }
-    if (doc == docTermOrds.docID()) {
-      BytesRef edgeValue = new BytesRef();
-      long ord;
-      while ((ord = docTermOrds.nextOrd()) != SortedSetDocValues.NO_MORE_ORDS) {
-        edgeValue = docTermOrds.lookupOrd(ord);
-        // add the edge id to the collector terms.
-        collectorTerms.add(edgeValue);
+
+    @Override
+    void addEdgeIdsToResult(int doc) throws IOException {
+      // set the doc to pull the edges ids for.
+      if (doc > docTermOrds.docID()) {
+        docTermOrds.advance(doc);
+      }
+      if (doc == docTermOrds.docID()) {
+        BytesRef edgeValue = new BytesRef();
+        long ord;
+        while ((ord = docTermOrds.nextOrd()) != SortedSetDocValues.NO_MORE_ORDS) {
+          edgeValue = docTermOrds.lookupOrd(ord);
+          // add the edge id to the collector terms.
+          collectorTerms.add(edgeValue);
+        }
       }
     }
-  }
 
-  @Override
-  public Query getResultQuery(SchemaField matchField, boolean useAutomaton) {
-    if (collectorTerms == null || collectorTerms.size() == 0) {
-      // return null if there are no terms (edges) to traverse.
-      return null;
-    } else {
-      // Create a query
-      Query q = null;
-
-      // TODO: see if we should dynamically select this based on the frontier size.
-      if (useAutomaton) {
-        // build an automaton based query for the frontier.
-        Automaton autn = buildAutomaton(collectorTerms);
-        AutomatonQuery autnQuery = new AutomatonQuery(new Term(matchField.getName()), autn);
-        q = autnQuery;
+    @Override
+    public Query getResultQuery(SchemaField matchField, boolean useAutomaton) {
+      if (collectorTerms == null || collectorTerms.size() == 0) {
+        // return null if there are no terms (edges) to traverse.
+        return null;
       } else {
-        List<BytesRef> termList = new ArrayList<>(collectorTerms.size());
-        for (int i = 0 ; i < collectorTerms.size(); i++) {
-          BytesRef ref = new BytesRef();
-          collectorTerms.get(i, ref);
-          termList.add(ref);
+        // Create a query
+        Query q = null;
+
+        // TODO: see if we should dynamically select this based on the frontier size.
+        if (useAutomaton) {
+          // build an automaton based query for the frontier.
+          Automaton autn = buildAutomaton(collectorTerms);
+          AutomatonQuery autnQuery = new AutomatonQuery(new Term(matchField.getName()), autn);
+          q = autnQuery;
+        } else {
+          List<BytesRef> termList = new ArrayList<>(collectorTerms.size());
+          for (int i = 0; i < collectorTerms.size(); i++) {
+            BytesRef ref = new BytesRef();
+            collectorTerms.get(i, ref);
+            termList.add(ref);
+          }
+          q = (matchField.hasDocValues() && !matchField.indexed())
+                  ? new DocValuesTermsQuery(matchField.getName(), termList)
+                  : new TermInSetQuery(matchField.getName(), termList);
         }
-        q = (matchField.hasDocValues() && !matchField.indexed())
-            ? new DocValuesTermsQuery(matchField.getName(), termList)
-            : new TermInSetQuery(matchField.getName(), termList);
-      }
 
-      return q;
+        return q;
+      }
     }
-  }
 
 
-  /** Build an automaton to represent the frontier query */
-  private Automaton buildAutomaton(BytesRefHash termBytesHash) {
-    // need top pass a sorted set of terms to the autn builder (maybe a better way to avoid this?)
-    final TreeSet<BytesRef> terms = new TreeSet<BytesRef>();
-    for (int i = 0 ; i < termBytesHash.size(); i++) {
-      BytesRef ref = new BytesRef();
-      termBytesHash.get(i, ref);
-      terms.add(ref);
+    /**
+     * Build an automaton to represent the frontier query
+     */
+    private Automaton buildAutomaton(BytesRefHash termBytesHash) {
+      // need top pass a sorted set of terms to the autn builder (maybe a better way to avoid this?)
+      final TreeSet<BytesRef> terms = new TreeSet<BytesRef>();
+      for (int i = 0; i < termBytesHash.size(); i++) {
+        BytesRef ref = new BytesRef();
+        termBytesHash.get(i, ref);
+        terms.add(ref);
+      }
+      final Automaton a = DaciukMihovAutomatonBuilder.build(terms);
+      return a;
     }
-    final Automaton a = DaciukMihovAutomatonBuilder.build(terms);
-    return a;
+
   }
 }
 
diff --git a/solr/core/src/java/org/apache/solr/search/join/GraphQuery.java b/solr/core/src/java/org/apache/solr/search/join/GraphQuery.java
index 5bec599..c25679b 100644
--- a/solr/core/src/java/org/apache/solr/search/join/GraphQuery.java
+++ b/solr/core/src/java/org/apache/solr/search/join/GraphQuery.java
@@ -200,7 +200,7 @@ public class GraphQuery extends Query {
           // Create the graph result collector for this level
           GraphEdgeCollector graphResultCollector = collectSchemaField.getType().isPointField()
               ? new GraphPointsCollector(collectSchemaField, new BitDocSet(resultBits), leafNodes)
-              : new GraphTermsCollector(collectSchemaField, new BitDocSet(resultBits), leafNodes);
+              : new GraphEdgeCollector.GraphTermsCollector(collectSchemaField, new BitDocSet(resultBits), leafNodes);
 
           fromSet = new BitDocSet(new FixedBitSet(capacity));
           graphResultCollector.setCollectDocs(fromSet.getBits());
diff --git a/solr/core/src/java/org/apache/solr/update/TransactionLog.java b/solr/core/src/java/org/apache/solr/update/TransactionLog.java
index 9fd2f2f..e9ced2e 100644
--- a/solr/core/src/java/org/apache/solr/update/TransactionLog.java
+++ b/solr/core/src/java/org/apache/solr/update/TransactionLog.java
@@ -69,7 +69,7 @@ public class TransactionLog implements Closeable {
   private boolean debug = log.isDebugEnabled();
   private boolean trace = log.isTraceEnabled();
 
-  public final static String END_MESSAGE="SOLR_TLOG_END";
+  public final static String END_MESSAGE = "SOLR_TLOG_END";
 
   long id;
   File tlogFile;
@@ -83,7 +83,7 @@ public class TransactionLog implements Closeable {
   protected volatile boolean deleteOnClose = true;  // we can delete old tlogs since they are currently only used for real-time-get (and in the future, recovery)
 
   AtomicInteger refcount = new AtomicInteger(1);
-  Map<String,Integer> globalStringMap = new HashMap<>();
+  Map<String, Integer> globalStringMap = new HashMap<>();
   List<String> globalStringList = new ArrayList<>();
 
   // write a BytesRef as a byte array
@@ -91,13 +91,13 @@ public class TransactionLog implements Closeable {
     @Override
     public Object resolve(Object o, JavaBinCodec codec) throws IOException {
       if (o instanceof BytesRef) {
-        BytesRef br = (BytesRef)o;
+        BytesRef br = (BytesRef) o;
         codec.writeByteArray(br.bytes, br.offset, br.length);
         return null;
       }
       // Fallback: we have no idea how to serialize this.  Be noisy to prevent insidious bugs
       throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
-          "TransactionLog doesn't know how to serialize " + o.getClass() + "; try implementing ObjectResolver?");
+              "TransactionLog doesn't know how to serialize " + o.getClass() + "; try implementing ObjectResolver?");
     }
   };
 
@@ -167,12 +167,12 @@ public class TransactionLog implements Closeable {
     try {
       if (debug) {
         log.debug("New TransactionLog file= {}, exists={}, size={} openExisting={}"
-            , tlogFile, tlogFile.exists(), tlogFile.length(), openExisting);
+                , tlogFile, tlogFile.exists(), tlogFile.length(), openExisting);
       }
 
       // Parse tlog id from the filename
       String filename = tlogFile.getName();
-      id = Long.parseLong(filename.substring(filename.lastIndexOf('.')+1));
+      id = Long.parseLong(filename.substring(filename.lastIndexOf('.') + 1));
 
       this.tlogFile = tlogFile;
       raf = new RandomAccessFile(this.tlogFile, "rw");
@@ -197,7 +197,7 @@ public class TransactionLog implements Closeable {
           log.warn("New transaction log already exists:{} size={}", tlogFile, raf.length());
           return;
         }
-       
+
         if (start > 0) {
           raf.setLength(0);
         }
@@ -205,7 +205,7 @@ public class TransactionLog implements Closeable {
       }
 
       success = true;
-      
+
       assert ObjectReleaseTracker.track(this);
 
     } catch (IOException e) {
@@ -222,7 +222,8 @@ public class TransactionLog implements Closeable {
   }
 
   // for subclasses
-  protected TransactionLog() {}
+  protected TransactionLog() {
+  }
 
   /** Returns the number of records in the log (currently includes the header and an optional commit).
    * Note: currently returns 0 for reopened existing log files.
@@ -241,12 +242,12 @@ public class TransactionLog implements Closeable {
     }
 
     // the end of the file should have the end message (added during a commit) plus a 4 byte size
-    byte[] buf = new byte[ END_MESSAGE.length() ];
+    byte[] buf = new byte[END_MESSAGE.length()];
     long pos = size - END_MESSAGE.length() - 4;
     if (pos < 0) return false;
     @SuppressWarnings("resource") final ChannelFastInputStream is = new ChannelFastInputStream(channel, pos);
     is.read(buf);
-    for (int i=0; i<buf.length; i++) {
+    for (int i = 0; i < buf.length; i++) {
       if (buf[i] != END_MESSAGE.charAt(i)) return false;
     }
     return true;
@@ -269,17 +270,17 @@ public class TransactionLog implements Closeable {
     // read existing header
     fis = fis != null ? fis : new ChannelFastInputStream(channel, 0);
     @SuppressWarnings("resource") final LogCodec codec = new LogCodec(resolver);
-    Map header = (Map)codec.unmarshal(fis);
+    Map header = (Map) codec.unmarshal(fis);
 
     fis.readInt(); // skip size
 
     // needed to read other records
 
     synchronized (this) {
-      globalStringList = (List<String>)header.get("strings");
+      globalStringList = (List<String>) header.get("strings");
       globalStringMap = new HashMap<>(globalStringList.size());
-      for (int i=0; i<globalStringList.size(); i++) {
-        globalStringMap.put( globalStringList.get(i), i+1);
+      for (int i = 0; i < globalStringList.size(); i++) {
+        globalStringMap.put(globalStringList.get(i), i + 1);
       }
     }
   }
@@ -309,16 +310,16 @@ public class TransactionLog implements Closeable {
     long pos = fos.size();
     assert pos == 0;
 
-    Map header = new LinkedHashMap<String,Object>();
-    header.put("SOLR_TLOG",1); // a magic string + version number
-    header.put("strings",globalStringList);
+    Map header = new LinkedHashMap<String, Object>();
+    header.put("SOLR_TLOG", 1); // a magic string + version number
+    header.put("strings", globalStringList);
     codec.marshal(header, fos);
 
     endRecord(pos);
   }
 
   protected void endRecord(long startRecordPosition) throws IOException {
-    fos.writeInt((int)(fos.size() - startRecordPosition));
+    fos.writeInt((int) (fos.size() - startRecordPosition));
     numRecords++;
   }
 
@@ -347,7 +348,7 @@ public class TransactionLog implements Closeable {
    * the command to the transaction log.)
    * @param cmd The add update command to be written
    * @return Returns the position pointer of the written update command
-   * 
+   *
    * @see #write(AddUpdateCommand, long)
    */
   public long write(AddUpdateCommand cmd) {
@@ -357,14 +358,14 @@ public class TransactionLog implements Closeable {
   /**
    * Writes an add update command to the transaction log. This should be called only for
    * writing in-place updates, or else pass -1 as the prevPointer.
-   * @param cmd The add update command to be written
-   * @param prevPointer The pointer in the transaction log which this update depends 
-   * on (applicable for in-place updates)
+   * @param cmd         The add update command to be written
+   * @param prevPointer The pointer in the transaction log which this update depends
+   *                    on (applicable for in-place updates)
    * @return Returns the position pointer of the written update command
    */
   public long write(AddUpdateCommand cmd, long prevPointer) {
     assert (-1 <= prevPointer && (cmd.isInPlaceUpdate() || (-1 == prevPointer)));
-    
+
     LogCodec codec = new LogCodec(resolver);
     SolrInputDocument sdoc = cmd.getSolrInputDocument();
 
@@ -374,7 +375,7 @@ public class TransactionLog implements Closeable {
       // adaptive buffer sizing
       int bufSize = lastAddSize;    // unsynchronized access of lastAddSize should be fine
       // at least 256 bytes and at most 1 MB
-      bufSize = Math.min(1024*1024, Math.max(256, bufSize+(bufSize>>3)+256));
+      bufSize = Math.min(1024 * 1024, Math.max(256, bufSize + (bufSize >> 3) + 256));
 
       MemOutputStream out = new MemOutputStream(new byte[bufSize]);
       codec.init(out);
@@ -391,7 +392,7 @@ public class TransactionLog implements Closeable {
         codec.writeLong(cmd.getVersion());
         codec.writeSolrInputDocument(cmd.getSolrInputDocument());
       }
-      lastAddSize = (int)out.size();
+      lastAddSize = (int) out.size();
 
       synchronized (this) {
         long pos = fos.size();   // if we had flushed, this should be equal to channel.position()
@@ -465,9 +466,9 @@ public class TransactionLog implements Closeable {
         // fos.flushBuffer();  // flush later
         return pos;
       }
-      } catch (IOException e) {
-        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
-      }
+    } catch (IOException e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
+    }
 
   }
 
@@ -515,10 +516,10 @@ public class TransactionLog implements Closeable {
         fos.flushBuffer();
         /***
          System.out.println("###flushBuffer to " + fos.size() + " raf.length()=" + raf.length() + " pos="+pos);
-        if (fos.size() != raf.length() || pos >= fos.size() ) {
-          throw new RuntimeException("ERROR" + "###flushBuffer to " + fos.size() + " raf.length()=" + raf.length() + " pos="+pos);
-        }
-        ***/
+         if (fos.size() != raf.length() || pos >= fos.size() ) {
+         throw new RuntimeException("ERROR" + "###flushBuffer to " + fos.size() + " raf.length()=" + raf.length() + " pos="+pos);
+         }
+         ***/
       }
 
       ChannelFastInputStream fis = new ChannelFastInputStream(channel, pos);
@@ -633,7 +634,8 @@ public class TransactionLog implements Closeable {
 
   /** Returns a reader that can be used while a log is still in use.
    * Currently only *one* LogReader may be outstanding, and that log may only
-   * be used from a single thread. */
+   * be used from a single thread.
+   */
   public LogReader getReader(long startingPos) {
     return new LogReader(startingPos);
   }
@@ -744,7 +746,7 @@ public class TransactionLog implements Closeable {
         long pos = startingPos;
 
         long lastVersion = Long.MIN_VALUE;
-        while ( (o = super.next()) != null) {
+        while ((o = super.next()) != null) {
           List entry = (List) o;
           long version = (Long) entry.get(UpdateLog.VERSION_IDX);
           version = Math.abs(version);
@@ -780,10 +782,11 @@ public class TransactionLog implements Closeable {
 
     /* returns the position in the log file of the last record returned by next() */
     public abstract long position();
+
     public abstract void close();
 
     @Override
-    public abstract String toString() ;
+    public abstract String toString();
 
   }
 
@@ -812,7 +815,7 @@ public class TransactionLog implements Closeable {
       }
 
       fis = new ChannelFastInputStream(channel, 0);
-      if (sz >=4) {
+      if (sz >= 4) {
         // readHeader(fis);  // should not be needed
         prevPos = sz - 4;
         fis.seek(prevPos);
@@ -843,7 +846,7 @@ public class TransactionLog implements Closeable {
       } else {
         // Position buffer so that this record is at the end.
         // For small records, this will cause subsequent calls to next() to be within the buffer.
-        long seekPos =  endOfThisRecord - fis.getBufferSize();
+        long seekPos = endOfThisRecord - fis.getBufferSize();
         seekPos = Math.min(seekPos, prevPos); // seek to the start of the record if it's larger then the block size.
         seekPos = Math.max(seekPos, 0);
         fis.seek(seekPos);
@@ -880,57 +883,54 @@ public class TransactionLog implements Closeable {
 
   }
 
-}
-
-
+  static class ChannelFastInputStream extends FastInputStream {
+    private FileChannel ch;
 
-class ChannelFastInputStream extends FastInputStream {
-  private FileChannel ch;
-
-  public ChannelFastInputStream(FileChannel ch, long chPosition) {
-    // super(null, new byte[10],0,0);    // a small buffer size for testing purposes
-    super(null);
-    this.ch = ch;
-    super.readFromStream = chPosition;
-  }
+    public ChannelFastInputStream(FileChannel ch, long chPosition) {
+      // super(null, new byte[10],0,0);    // a small buffer size for testing purposes
+      super(null);
+      this.ch = ch;
+      super.readFromStream = chPosition;
+    }
 
-  @Override
-  public int readWrappedStream(byte[] target, int offset, int len) throws IOException {
-    ByteBuffer bb = ByteBuffer.wrap(target, offset, len);
-    int ret = ch.read(bb, readFromStream);
-    return ret;
-  }
+    @Override
+    public int readWrappedStream(byte[] target, int offset, int len) throws IOException {
+      ByteBuffer bb = ByteBuffer.wrap(target, offset, len);
+      int ret = ch.read(bb, readFromStream);
+      return ret;
+    }
 
-  public void seek(long position) throws IOException {
-    if (position <= readFromStream && position >= getBufferPos()) {
-      // seek within buffer
-      pos = (int)(position - getBufferPos());
-    } else {
-      // long currSize = ch.size();   // not needed - underlying read should handle (unless read never done)
-      // if (position > currSize) throw new EOFException("Read past EOF: seeking to " + position + " on file of size " + currSize + " file=" + ch);
-      readFromStream = position;
-      end = pos = 0;
+    public void seek(long position) throws IOException {
+      if (position <= readFromStream && position >= getBufferPos()) {
+        // seek within buffer
+        pos = (int) (position - getBufferPos());
+      } else {
+        // long currSize = ch.size();   // not needed - underlying read should handle (unless read never done)
+        // if (position > currSize) throw new EOFException("Read past EOF: seeking to " + position + " on file of size " + currSize + " file=" + ch);
+        readFromStream = position;
+        end = pos = 0;
+      }
+      assert position() == position;
     }
-    assert position() == position;
-  }
 
   /** where is the start of the buffer relative to the whole file */
-  public long getBufferPos() {
-    return readFromStream - end;
-  }
+    public long getBufferPos() {
+      return readFromStream - end;
+    }
 
-  public int getBufferSize() {
-    return buf.length;
-  }
+    public int getBufferSize() {
+      return buf.length;
+    }
 
-  @Override
-  public void close() throws IOException {
-    ch.close();
-  }
+    @Override
+    public void close() throws IOException {
+      ch.close();
+    }
 
-  @Override
-  public String toString() {
-    return "readFromStream="+readFromStream +" pos="+pos +" end="+end + " bufferPos="+getBufferPos() + " position="+position() ;
+    @Override
+    public String toString() {
+      return "readFromStream=" + readFromStream + " pos=" + pos + " end=" + end + " bufferPos=" + getBufferPos() + " position=" + position();
+    }
   }
 }
 
diff --git a/solr/core/src/java/org/apache/solr/update/processor/DistributedZkUpdateProcessor.java b/solr/core/src/java/org/apache/solr/update/processor/DistributedZkUpdateProcessor.java
index 158900d..8da2df7 100644
--- a/solr/core/src/java/org/apache/solr/update/processor/DistributedZkUpdateProcessor.java
+++ b/solr/core/src/java/org/apache/solr/update/processor/DistributedZkUpdateProcessor.java
@@ -131,7 +131,7 @@ public class DistributedZkUpdateProcessor extends DistributedUpdateProcessor {
     while (nextInChain != null)  {
       Class<? extends UpdateRequestProcessor> klass = nextInChain.getClass();
       if (klass != LogUpdateProcessorFactory.LogUpdateProcessor.class
-          && klass != RunUpdateProcessor.class
+          && klass != RunUpdateProcessorFactory.RunUpdateProcessor.class
           && klass != TolerantUpdateProcessor.class)  {
         shouldClone = true;
         break;
diff --git a/solr/core/src/java/org/apache/solr/update/processor/RunUpdateProcessorFactory.java b/solr/core/src/java/org/apache/solr/update/processor/RunUpdateProcessorFactory.java
index d49ab27..a208d41 100644
--- a/solr/core/src/java/org/apache/solr/update/processor/RunUpdateProcessorFactory.java
+++ b/solr/core/src/java/org/apache/solr/update/processor/RunUpdateProcessorFactory.java
@@ -33,14 +33,12 @@ import org.apache.solr.update.*;
  * @since solr 1.3
  * @see DistributingUpdateProcessorFactory
  */
-public class RunUpdateProcessorFactory extends UpdateRequestProcessorFactory 
-{
+public class RunUpdateProcessorFactory extends UpdateRequestProcessorFactory {
 
   public static final String PRE_RUN_CHAIN_NAME = "_preRun_";
 
   @Override
-  public UpdateRequestProcessor getInstance(SolrQueryRequest req, SolrQueryResponse rsp, UpdateRequestProcessor next) 
-  {
+  public UpdateRequestProcessor getInstance(SolrQueryRequest req, SolrQueryResponse rsp, UpdateRequestProcessor next) {
     RunUpdateProcessor runUpdateProcessor = new RunUpdateProcessor(req, next);
     UpdateRequestProcessorChain preRun = req.getCore().getUpdateProcessingChain(PRE_RUN_CHAIN_NAME);
     if (preRun != null) {
@@ -49,82 +47,79 @@ public class RunUpdateProcessorFactory extends UpdateRequestProcessorFactory
       return runUpdateProcessor;
     }
   }
-}
 
-class RunUpdateProcessor extends UpdateRequestProcessor 
-{
-  private final SolrQueryRequest req;
-  private final UpdateHandler updateHandler;
 
-  private boolean changesSinceCommit = false;
+  static class RunUpdateProcessor extends UpdateRequestProcessor {
+    private final SolrQueryRequest req;
+    private final UpdateHandler updateHandler;
 
-  public RunUpdateProcessor(SolrQueryRequest req, UpdateRequestProcessor next) {
-    super( next );
-    this.req = req;
-    this.updateHandler = req.getCore().getUpdateHandler();
-  }
+    private boolean changesSinceCommit = false;
 
-  @Override
-  public void processAdd(AddUpdateCommand cmd) throws IOException {
-    
-    if (AtomicUpdateDocumentMerger.isAtomicUpdate(cmd)) {
-      throw new SolrException
-        (SolrException.ErrorCode.BAD_REQUEST,
-         "RunUpdateProcessor has received an AddUpdateCommand containing a document that appears to still contain Atomic document update operations, most likely because DistributedUpdateProcessorFactory was explicitly disabled from this updateRequestProcessorChain");
+    public RunUpdateProcessor(SolrQueryRequest req, UpdateRequestProcessor next) {
+      super(next);
+      this.req = req;
+      this.updateHandler = req.getCore().getUpdateHandler();
     }
 
-    updateHandler.addDoc(cmd);
-    super.processAdd(cmd);
-    changesSinceCommit = true;
-  }
+    @Override
+    public void processAdd(AddUpdateCommand cmd) throws IOException {
 
-  @Override
-  public void processDelete(DeleteUpdateCommand cmd) throws IOException {
-    if( cmd.isDeleteById()) {
-      updateHandler.delete(cmd);
+      if (AtomicUpdateDocumentMerger.isAtomicUpdate(cmd)) {
+        throw new SolrException
+                (SolrException.ErrorCode.BAD_REQUEST,
+                        "RunUpdateProcessor has received an AddUpdateCommand containing a document that appears to still contain Atomic document update operations, most likely because DistributedUpdateProcessorFactory was explicitly disabled from this updateRequestProcessorChain");
+      }
+
+      updateHandler.addDoc(cmd);
+      super.processAdd(cmd);
+      changesSinceCommit = true;
     }
-    else {
-      updateHandler.deleteByQuery(cmd);
+
+    @Override
+    public void processDelete(DeleteUpdateCommand cmd) throws IOException {
+      if (cmd.isDeleteById()) {
+        updateHandler.delete(cmd);
+      } else {
+        updateHandler.deleteByQuery(cmd);
+      }
+      super.processDelete(cmd);
+      changesSinceCommit = true;
     }
-    super.processDelete(cmd);
-    changesSinceCommit = true;
-  }
 
-  @Override
-  public void processMergeIndexes(MergeIndexesCommand cmd) throws IOException {
-    updateHandler.mergeIndexes(cmd);
-    super.processMergeIndexes(cmd);
-  }
+    @Override
+    public void processMergeIndexes(MergeIndexesCommand cmd) throws IOException {
+      updateHandler.mergeIndexes(cmd);
+      super.processMergeIndexes(cmd);
+    }
 
-  @Override
-  public void processCommit(CommitUpdateCommand cmd) throws IOException
-  {
-    updateHandler.commit(cmd);
-    super.processCommit(cmd);
-    if (!cmd.softCommit) {
-      // a hard commit means we don't need to flush the transaction log
-      changesSinceCommit = false;
+    @Override
+    public void processCommit(CommitUpdateCommand cmd) throws IOException {
+      updateHandler.commit(cmd);
+      super.processCommit(cmd);
+      if (!cmd.softCommit) {
+        // a hard commit means we don't need to flush the transaction log
+        changesSinceCommit = false;
+      }
     }
-  }
 
-  /**
-   * @since Solr 1.4
-   */
-  @Override
-  public void processRollback(RollbackUpdateCommand cmd) throws IOException
-  {
-    updateHandler.rollback(cmd);
-    super.processRollback(cmd);
-    changesSinceCommit = false;
-  }
+    /**
+     * @since Solr 1.4
+     */
+    @Override
+    public void processRollback(RollbackUpdateCommand cmd) throws IOException {
+      updateHandler.rollback(cmd);
+      super.processRollback(cmd);
+      changesSinceCommit = false;
+    }
 
 
-  @Override
-  public void finish() throws IOException {
-    if (changesSinceCommit && updateHandler.getUpdateLog() != null) {
-      updateHandler.getUpdateLog().finish(null);
+    @Override
+    public void finish() throws IOException {
+      if (changesSinceCommit && updateHandler.getUpdateLog() != null) {
+        updateHandler.getUpdateLog().finish(null);
+      }
+      super.finish();
     }
-    super.finish();
   }
 }
 
diff --git a/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacetRefinement.java b/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacetRefinement.java
index 2a5dcd1..522b22c 100644
--- a/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacetRefinement.java
+++ b/solr/core/src/test/org/apache/solr/search/facet/TestJsonFacetRefinement.java
@@ -136,7 +136,7 @@ public class TestJsonFacetRefinement extends SolrTestCaseHS {
     try {
       int nShards = responsesAndTests.length / 2;
       Object jsonFacet = Utils.fromJSONString(facet);
-      FacetParser parser = new FacetRequest.FacetTopParser(req);
+      FacetParser parser = new FacetParser.FacetTopParser(req);
       FacetRequest facetRequest = parser.parse(jsonFacet);
 
       FacetMerger merger = null;
diff --git a/solr/core/src/test/org/apache/solr/update/processor/UpdateRequestProcessorFactoryTest.java b/solr/core/src/test/org/apache/solr/update/processor/UpdateRequestProcessorFactoryTest.java
index 66d612f..cbd6920 100644
--- a/solr/core/src/test/org/apache/solr/update/processor/UpdateRequestProcessorFactoryTest.java
+++ b/solr/core/src/test/org/apache/solr/update/processor/UpdateRequestProcessorFactoryTest.java
@@ -162,7 +162,7 @@ public class UpdateRequestProcessorFactoryTest extends SolrTestCaseJ4 {
 
       // for these 3 (distrib) chains, the last proc should always be RunUpdateProcessor
       assertTrue(name + " (distrib) last processor isn't a RunUpdateProcessor: " + procs.toString(),
-                 procs.get(procs.size()-1) instanceof RunUpdateProcessor );
+                 procs.get(procs.size()-1) instanceof RunUpdateProcessorFactory.RunUpdateProcessor );
 
       // either 1 proc was droped in distrib mode, or 1 for the "implicit" chain
 
diff --git a/solr/solrj/src/java/org/noggit/CharArr.java b/solr/solrj/src/java/org/noggit/CharArr.java
index 9ecc8e6..0431e10 100644
--- a/solr/solrj/src/java/org/noggit/CharArr.java
+++ b/solr/solrj/src/java/org/noggit/CharArr.java
@@ -225,170 +225,170 @@ public class CharArr implements CharSequence, Appendable {
     write(c);
     return this;
   }
-}
 
 
-class NullCharArr extends CharArr {
-  public NullCharArr() {
-    super(new char[1], 0, 0);
-  }
+  static class NullCharArr extends CharArr {
+    public NullCharArr() {
+      super(new char[1], 0, 0);
+    }
 
-  @Override
-  public void unsafeWrite(char b) {
-  }
+    @Override
+    public void unsafeWrite(char b) {
+    }
 
-  @Override
-  public void unsafeWrite(char b[], int off, int len) {
-  }
+    @Override
+    public void unsafeWrite(char b[], int off, int len) {
+    }
 
-  @Override
-  public void unsafeWrite(int b) {
-  }
+    @Override
+    public void unsafeWrite(int b) {
+    }
 
-  @Override
-  public void write(char b) {
-  }
+    @Override
+    public void write(char b) {
+    }
 
-  @Override
-  public void write(char b[], int off, int len) {
-  }
+    @Override
+    public void write(char b[], int off, int len) {
+    }
 
-  @Override
-  public void reserve(int num) {
-  }
+    @Override
+    public void reserve(int num) {
+    }
 
-  @Override
-  protected void resize(int len) {
-  }
+    @Override
+    protected void resize(int len) {
+    }
 
-  @Override
-  public Appendable append(CharSequence csq, int start, int end) throws IOException {
-    return this;
-  }
+    @Override
+    public Appendable append(CharSequence csq, int start, int end) throws IOException {
+      return this;
+    }
 
-  @Override
-  public char charAt(int index) {
-    return 0;
-  }
+    @Override
+    public char charAt(int index) {
+      return 0;
+    }
 
-  @Override
-  public void write(String s, int stringOffset, int len) {
+    @Override
+    public void write(String s, int stringOffset, int len) {
+    }
   }
-}
 
 
-// IDEA: a subclass that refills the array from a reader?
-class CharArrReader extends CharArr {
-  protected final Reader in;
+  // IDEA: a subclass that refills the array from a reader?
+  class CharArrReader extends CharArr {
+    protected final Reader in;
 
-  public CharArrReader(Reader in, int size) {
-    super(size);
-    this.in = in;
-  }
+    public CharArrReader(Reader in, int size) {
+      super(size);
+      this.in = in;
+    }
 
-  @Override
-  public int read() throws IOException {
-    if (start >= end) fill();
-    return start >= end ? -1 : buf[start++];
-  }
+    @Override
+    public int read() throws IOException {
+      if (start >= end) fill();
+      return start >= end ? -1 : buf[start++];
+    }
 
-  @Override
-  public int read(CharBuffer cb) throws IOException {
-    // empty the buffer and then read direct
-    int sz = size();
-    if (sz > 0) cb.put(buf, start, end);
-    int sz2 = in.read(cb);
-    if (sz2 >= 0) return sz + sz2;
-    return sz > 0 ? sz : -1;
-  }
+    @Override
+    public int read(CharBuffer cb) throws IOException {
+      // empty the buffer and then read direct
+      int sz = size();
+      if (sz > 0) cb.put(buf, start, end);
+      int sz2 = in.read(cb);
+      if (sz2 >= 0) return sz + sz2;
+      return sz > 0 ? sz : -1;
+    }
 
-  @Override
-  public int fill() throws IOException {
-    if (start >= end) {
-      reset();
-    } else if (start > 0) {
-      System.arraycopy(buf, start, buf, 0, size());
-      end = size();
-      start = 0;
+    @Override
+    public int fill() throws IOException {
+      if (start >= end) {
+        reset();
+      } else if (start > 0) {
+        System.arraycopy(buf, start, buf, 0, size());
+        end = size();
+        start = 0;
+      }
+      /***
+       // fill fully or not???
+       do {
+       int sz = in.read(buf,end,buf.length-end);
+       if (sz==-1) return;
+       end+=sz;
+       } while (end < buf.length);
+       ***/
+
+      int sz = in.read(buf, end, buf.length - end);
+      if (sz > 0) end += sz;
+      return sz;
     }
-    /***
-     // fill fully or not???
-     do {
-     int sz = in.read(buf,end,buf.length-end);
-     if (sz==-1) return;
-     end+=sz;
-     } while (end < buf.length);
-     ***/
 
-    int sz = in.read(buf, end, buf.length - end);
-    if (sz > 0) end += sz;
-    return sz;
   }
 
-}
-
 
-class CharArrWriter extends CharArr {
-  protected Writer sink;
+  class CharArrWriter extends CharArr {
+    protected Writer sink;
 
-  @Override
-  public void flush() {
-    try {
-      sink.write(buf, start, end - start);
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
-    start = end = 0;
-  }
-
-  @Override
-  public void write(char b) {
-    if (end >= buf.length) {
-      flush();
-    }
-    unsafeWrite(b);
-  }
-
-  @Override
-  public void write(char b[], int off, int len) {
-    int space = buf.length - end;
-    if (len < space) {
-      unsafeWrite(b, off, len);
-    } else if (len < buf.length) {
-      unsafeWrite(b, off, space);
-      flush();
-      unsafeWrite(b, off + space, len - space);
-    } else {
-      flush();
+    @Override
+    public void flush() {
       try {
-        sink.write(b, off, len);
+        sink.write(buf, start, end - start);
       } catch (IOException e) {
         throw new RuntimeException(e);
       }
+      start = end = 0;
     }
-  }
 
-  @Override
-  public void write(String s, int stringOffset, int len) {
-    int space = buf.length - end;
-    if (len < space) {
-      s.getChars(stringOffset, stringOffset + len, buf, end);
-      end += len;
-    } else if (len < buf.length) {
-      // if the data to write is small enough, buffer it.
-      s.getChars(stringOffset, stringOffset + space, buf, end);
-      flush();
-      s.getChars(stringOffset + space, stringOffset + len, buf, 0);
-      end = len - space;
-    } else {
-      flush();
-      // don't buffer, just write to sink
-      try {
-        sink.write(s, stringOffset, len);
-      } catch (IOException e) {
-        throw new RuntimeException(e);
+    @Override
+    public void write(char b) {
+      if (end >= buf.length) {
+        flush();
       }
+      unsafeWrite(b);
+    }
 
+    @Override
+    public void write(char b[], int off, int len) {
+      int space = buf.length - end;
+      if (len < space) {
+        unsafeWrite(b, off, len);
+      } else if (len < buf.length) {
+        unsafeWrite(b, off, space);
+        flush();
+        unsafeWrite(b, off + space, len - space);
+      } else {
+        flush();
+        try {
+          sink.write(b, off, len);
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+      }
+    }
+
+    @Override
+    public void write(String s, int stringOffset, int len) {
+      int space = buf.length - end;
+      if (len < space) {
+        s.getChars(stringOffset, stringOffset + len, buf, end);
+        end += len;
+      } else if (len < buf.length) {
+        // if the data to write is small enough, buffer it.
+        s.getChars(stringOffset, stringOffset + space, buf, end);
+        flush();
+        s.getChars(stringOffset + space, stringOffset + len, buf, 0);
+        end = len - space;
+      } else {
+        flush();
+        // don't buffer, just write to sink
+        try {
+          sink.write(s, stringOffset, len);
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+
+      }
     }
   }
 }
diff --git a/solr/solrj/src/java/org/noggit/JSONParser.java b/solr/solrj/src/java/org/noggit/JSONParser.java
index 8b1ac01..d1655d1 100644
--- a/solr/solrj/src/java/org/noggit/JSONParser.java
+++ b/solr/solrj/src/java/org/noggit/JSONParser.java
@@ -132,7 +132,7 @@ public class JSONParser {
     return "Unknown: " + e;
   }
 
-  private static final CharArr devNull = new NullCharArr();
+  private static final CharArr devNull = new CharArr.NullCharArr();
 
   protected int flags = FLAGS_DEFAULT;