Index: distributed/src/org/apache/lucene/distributed/ClassService.java
===================================================================
--- distributed/src/org/apache/lucene/distributed/ClassService.java	(revision 0)
+++ distributed/src/org/apache/lucene/distributed/ClassService.java	(revision 0)
@@ -0,0 +1,8 @@
+package org.apache.lucene.distributed;
+
+/**
+ * Remote service used to load class bytes from
+ */
+public interface ClassService extends CLInterface {
+  public static final long serialVersionUID = 1l;
+}
Index: distributed/src/org/apache/lucene/distributed/CLInterface.java
===================================================================
--- distributed/src/org/apache/lucene/distributed/CLInterface.java	(revision 0)
+++ distributed/src/org/apache/lucene/distributed/CLInterface.java	(revision 0)
@@ -0,0 +1,16 @@
+package org.apache.lucene.distributed;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.lucene.distributed.RMIClasses.ClassKey;
+
+/**
+ * Interface for loading multi versioned class bytes.  The ClassKey
+ * holds the class name and serialVersionUID.
+ *
+ */
+public interface CLInterface {
+  public byte[] getResource(String resource) throws IOException;
+  public byte[] loadClassData(ClassKey classKey) throws IOException;
+}
\ No newline at end of file
Index: distributed/src/org/apache/lucene/distributed/GetLuceneClasses.java
===================================================================
--- distributed/src/org/apache/lucene/distributed/GetLuceneClasses.java	(revision 0)
+++ distributed/src/org/apache/lucene/distributed/GetLuceneClasses.java	(revision 0)
@@ -0,0 +1,79 @@
+package org.apache.lucene.distributed;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.commons.io.FileUtils;
+
+/**
+ * Finds Lucene core Lucene subclasses
+ *
+ */
+public class GetLuceneClasses {
+  Set<String> rootNames;
+  static Set<String> defaultRootNames;
+  static {
+    defaultRootNames = new HashSet<String>();
+    defaultRootNames.add("org.apache.lucene.search.Query");
+    defaultRootNames.add("org.apache.lucene.search.Filter");
+  }
+
+  public static void main(String[] args) throws Exception {
+    // GetLuceneClasses getLuceneClasses = new
+    // GetLuceneClasses(defaultRootNames);
+    // System.out.println(getLuceneClasses.get() + "");
+    GenerateLuceneClassExclude e = new GenerateLuceneClassExclude(new File(
+        "G:\\oceangoogle\\trunk\\distributedlucene\\src\\org\\apache\\lucene\\distributed\\LuceneClasses.java"));
+    e.generate();
+  }
+
+  public static class GenerateLuceneClassExclude {
+    File file;
+
+    public GenerateLuceneClassExclude(File file) {
+      this.file = file;
+    }
+
+    public void generate() throws Exception {
+      StringBuilder b = new StringBuilder();
+      b.append("package org.apache.lucene.distributed;\n\n");
+      b.append("import java.util.HashSet;\n");
+      b.append("import java.util.Set;\n\n");
+      b.append("public class LuceneClasses {\n");
+      b.append("  public static Set<String> classes = new HashSet<String>();\n\n");
+      b.append("  static {\n");
+      GetLuceneClasses glc = new GetLuceneClasses(defaultRootNames);
+      Set<String> set = glc.get();
+      for (String name : set) {
+        String s = name.substring(name.lastIndexOf('.')+1, name.length());
+        //System.out.println(s);
+        if (s.startsWith("Test")) continue;
+        b.append("    classes.add(\"" + name + "\");\n");
+      }
+      b.append("  }");
+      b.append("}");
+
+      FileUtils.writeStringToFile(file, b.toString());
+    }
+  }
+
+  public GetLuceneClasses(Set<String> rootNames) {
+    this.rootNames = rootNames;
+  }
+
+  public Set<String> get() throws Exception {
+    Set<String> names = new HashSet<String>();
+    ResolverUtil resolverUtil = new ResolverUtil();
+    for (String rootName : rootNames) {
+      Class cl = Class.forName(rootName);
+      resolverUtil.findImplementations(cl, "org.apache.lucene.search", "org.apache.lucene.search.spans");
+    }
+    Set<Class> classes = resolverUtil.getClasses();
+    for (Class c : classes) {
+      names.add(c.getName());
+    }
+    return names;
+  }
+}
Index: distributed/src/org/apache/lucene/distributed/LuceneClasses.java
===================================================================
--- distributed/src/org/apache/lucene/distributed/LuceneClasses.java	(revision 0)
+++ distributed/src/org/apache/lucene/distributed/LuceneClasses.java	(revision 0)
@@ -0,0 +1,50 @@
+package org.apache.lucene.distributed;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class LuceneClasses {
+  public static Set<String> classes = new HashSet<String>();
+
+  static {
+    classes.add("org.apache.lucene.search.RemoteCachingWrapperFilter");
+    classes.add("org.apache.lucene.search.ConstantScoreQuery");
+    classes.add("org.apache.lucene.search.QueryUtils$1");
+    classes.add("org.apache.lucene.search.TermQuery");
+    classes.add("org.apache.lucene.search.function.ValueSourceQuery");
+    classes.add("org.apache.lucene.search.RangeQuery");
+    classes.add("org.apache.lucene.search.MockFilter");
+    classes.add("org.apache.lucene.search.QueryWrapperFilter");
+    classes.add("org.apache.lucene.search.Query");
+    classes.add("org.apache.lucene.search.FuzzyQuery");
+    classes.add("org.apache.lucene.search.spans.SpanNearQuery");
+    classes.add("org.apache.lucene.search.CachingWrapperFilter");
+    classes.add("org.apache.lucene.search.PrefixQuery");
+    classes.add("org.apache.lucene.search.function.FieldScoreQuery");
+    classes.add("org.apache.lucene.search.spans.SpanTermQuery");
+    classes.add("org.apache.lucene.search.QueryFilter");
+    classes.add("org.apache.lucene.search.SpanFilter");
+    classes.add("org.apache.lucene.search.ConstantScoreRangeQuery");
+    classes.add("org.apache.lucene.search.Filter");
+    classes.add("org.apache.lucene.search.FilteredQuery");
+    classes.add("org.apache.lucene.search.PrefixFilter");
+    classes.add("org.apache.lucene.search.spans.SpanOrQuery");
+    classes.add("org.apache.lucene.search.spans.SpanFirstQuery");
+    classes.add("org.apache.lucene.search.MatchAllDocsQuery");
+    classes.add("org.apache.lucene.search.RangeFilter");
+    classes.add("org.apache.lucene.search.PhraseQuery");
+    classes.add("org.apache.lucene.search.WildcardQuery");
+    classes.add("org.apache.lucene.search.RemoteCachingWrapperFilterHelper");
+    classes.add("org.apache.lucene.search.spans.SpanQuery");
+    classes.add("org.apache.lucene.search.DisjunctionMaxQuery");
+    classes.add("org.apache.lucene.search.spans.SpanNotQuery");
+    classes.add("org.apache.lucene.search.CachingWrapperFilterHelper");
+    classes.add("org.apache.lucene.search.function.CustomScoreQuery");
+    classes.add("org.apache.lucene.search.BooleanQuery");
+    classes.add("org.apache.lucene.search.MultiPhraseQuery");
+    classes.add("org.apache.lucene.search.payloads.BoostingTermQuery");
+    classes.add("org.apache.lucene.search.CachingSpanFilter");
+    classes.add("org.apache.lucene.search.SpanQueryFilter");
+    classes.add("org.apache.lucene.search.MultiTermQuery");
+    classes.add("org.apache.lucene.search.SingleDocTestFilter");
+  }}
\ No newline at end of file
Index: distributed/src/org/apache/lucene/distributed/ResolverUtil.java
===================================================================
--- distributed/src/org/apache/lucene/distributed/ResolverUtil.java	(revision 0)
+++ distributed/src/org/apache/lucene/distributed/ResolverUtil.java	(revision 0)
@@ -0,0 +1,509 @@
+package org.apache.lucene.distributed;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.lang.annotation.Annotation;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+
+/**
+ * <p>
+ * ResolverUtil is used to locate classes that are available in the/a class path
+ * and meet arbitrary conditions. The two most common conditions are that a
+ * class implements/extends another class, or that is it annotated with a
+ * specific annotation. However, through the use of the {@link Test} class it is
+ * possible to search using arbitrary conditions.
+ * </p>
+ * 
+ * <p>
+ * A ClassLoader is used to locate all locations (directories and jar files) in
+ * the class path that contain classes within certain packages, and then to load
+ * those classes and check them. By default the ClassLoader returned by
+ * {@code Thread.currentThread().getContextClassLoader()} is used, but this can
+ * be overridden by calling {@link #setClassLoader(ClassLoader)} prior to
+ * invoking any of the {@code find()} methods.
+ * </p>
+ * 
+ * <p>
+ * General searches are initiated by calling the
+ * {@link #find(com.opensymphony.xwork2.util.ResolverUtil.Test, String...)} ()}
+ * method and supplying a package name and a Test instance. This will cause the
+ * named package <b>and all sub-packages</b> to be scanned for classes that
+ * meet the test. There are also utility methods for the common use cases of
+ * scanning multiple packages for extensions of particular classes, or classes
+ * annotated with a specific annotation.
+ * </p>
+ * 
+ * <p>
+ * The standard usage pattern for the ResolverUtil class is as follows:
+ * </p>
+ * 
+ * <pre>
+ * esolverUtil&lt;ActionBean&gt; resolver = new ResolverUtil&lt;ActionBean&gt;();
+ * esolver.findImplementation(ActionBean.class, pkg1, pkg2);
+ * esolver.find(new CustomTest(), pkg1);
+ * esolver.find(new CustomTest(), pkg2);
+ * ollection&lt;ActionBean&gt; beans = resolver.getClasses();
+ * </pre>
+ * 
+ * <p>
+ * This class was copied from Stripes -
+ * http://stripes.mc4j.org/confluence/display/stripes/Home
+ * </p>
+ * 
+ * @author Tim Fennell
+ */
+public class ResolverUtil<T> {
+  PrintStream ERR = System.err;
+  /** An instance of Log to use for logging in this class. */
+  //private static final Logger LOG = LoggerFactory.getLogger(ResolverUtil.class);
+
+  /**
+   * A simple interface that specifies how to test classes to determine if they
+   * are to be included in the results produced by the ResolverUtil.
+   */
+  public static interface Test {
+    /**
+     * Will be called repeatedly with candidate classes. Must return True if a
+     * class is to be included in the results, false otherwise.
+     */
+    boolean matches(Class type);
+
+    boolean matches(URL resource);
+
+    boolean doesMatchClass();
+
+    boolean doesMatchResource();
+  }
+
+  public static abstract class ClassTest implements Test {
+    public boolean matches(URL resource) {
+      throw new UnsupportedOperationException();
+    }
+
+    public boolean doesMatchClass() {
+      return true;
+    }
+
+    public boolean doesMatchResource() {
+      return false;
+    }
+  }
+
+  public static abstract class ResourceTest implements Test {
+    public boolean matches(Class cls) {
+      throw new UnsupportedOperationException();
+    }
+
+    public boolean doesMatchClass() {
+      return false;
+    }
+
+    public boolean doesMatchResource() {
+      return true;
+    }
+  }
+
+  /**
+   * A Test that checks to see if each class is assignable to the provided
+   * class. Note that this test will match the parent type itself if it is
+   * presented for matching.
+   */
+  public static class IsA extends ClassTest {
+    private Class parent;
+
+    /**
+     * Constructs an IsA test using the supplied Class as the parent
+     * class/interface.
+     */
+    public IsA(Class parentType) {
+      this.parent = parentType;
+    }
+
+    /**
+     * Returns true if type is assignable to the parent type supplied in the
+     * constructor.
+     */
+    public boolean matches(Class type) {
+      return type != null && parent.isAssignableFrom(type);
+    }
+
+    @Override
+    public String toString() {
+      return "is assignable to " + parent.getSimpleName();
+    }
+  }
+
+  /**
+   * A Test that checks to see if each class name ends with the provided suffix.
+   */
+  public static class NameEndsWith extends ClassTest {
+    private String suffix;
+
+    /** Constructs a NameEndsWith test using the supplied suffix. */
+    public NameEndsWith(String suffix) {
+      this.suffix = suffix;
+    }
+
+    /**
+     * Returns true if type name ends with the suffix supplied in the
+     * constructor.
+     */
+    public boolean matches(Class type) {
+      return type != null && type.getName().endsWith(suffix);
+    }
+
+    @Override
+    public String toString() {
+      return "ends with the suffix " + suffix;
+    }
+  }
+
+  /**
+   * A Test that checks to see if each class is annotated with a specific
+   * annotation. If it is, then the test returns true, otherwise false.
+   */
+  public static class AnnotatedWith extends ClassTest {
+    private Class<? extends Annotation> annotation;
+
+    /** Construts an AnnotatedWith test for the specified annotation type. */
+    public AnnotatedWith(Class<? extends Annotation> annotation) {
+      this.annotation = annotation;
+    }
+
+    /**
+     * Returns true if the type is annotated with the class provided to the
+     * constructor.
+     */
+    public boolean matches(Class type) {
+      return type != null && type.isAnnotationPresent(annotation);
+    }
+
+    @Override
+    public String toString() {
+      return "annotated with @" + annotation.getSimpleName();
+    }
+  }
+
+  public static class NameIs extends ResourceTest {
+    private String name;
+
+    public NameIs(String name) {
+      this.name = "/" + name;
+    }
+
+    public boolean matches(URL resource) {
+      return (resource.getPath().endsWith(name));
+    }
+
+    @Override
+    public String toString() {
+      return "named " + name;
+    }
+  }
+
+  /** The set of matches being accumulated. */
+  private Set<Class<? extends T>> classMatches = new HashSet<Class<? extends T>>();
+
+  /** The set of matches being accumulated. */
+  private Set<URL> resourceMatches = new HashSet<URL>();
+
+  /**
+   * The ClassLoader to use when looking for classes. If null then the
+   * ClassLoader returned by Thread.currentThread().getContextClassLoader() will
+   * be used.
+   */
+  private ClassLoader classloader;
+
+  /**
+   * Provides access to the classes discovered so far. If no calls have been
+   * made to any of the {@code find()} methods, this set will be empty.
+   * 
+   * @return the set of classes that have been discovered.
+   */
+  public Set<Class<? extends T>> getClasses() {
+    return classMatches;
+  }
+
+  public Set<URL> getResources() {
+    return resourceMatches;
+  }
+
+  /**
+   * Returns the classloader that will be used for scanning for classes. If no
+   * explicit ClassLoader has been set by the calling, the context class loader
+   * will be used.
+   * 
+   * @return the ClassLoader that will be used to scan for classes
+   */
+  public ClassLoader getClassLoader() {
+    return classloader == null ? Thread.currentThread().getContextClassLoader() : classloader;
+  }
+
+  /**
+   * Sets an explicit ClassLoader that should be used when scanning for classes.
+   * If none is set then the context classloader will be used.
+   * 
+   * @param classloader
+   *          a ClassLoader to use when scanning for classes
+   */
+  public void setClassLoader(ClassLoader classloader) {
+    this.classloader = classloader;
+  }
+
+  /**
+   * Attempts to discover classes that are assignable to the type provided. In
+   * the case that an interface is provided this method will collect
+   * implementations. In the case of a non-interface class, subclasses will be
+   * collected. Accumulated classes can be accessed by calling
+   * {@link #getClasses()}.
+   * 
+   * @param parent
+   *          the class of interface to find subclasses or implementations of
+   * @param packageNames
+   *          one or more package names to scan (including subpackages) for
+   *          classes
+   */
+  public void findImplementations(Class parent, String... packageNames) {
+    if (packageNames == null)
+      return;
+
+    Test test = new IsA(parent);
+    for (String pkg : packageNames) {
+      findInPackage(test, pkg);
+    }
+  }
+
+  /**
+   * Attempts to discover classes who's name ends with the provided suffix.
+   * Accumulated classes can be accessed by calling {@link #getClasses()}.
+   * 
+   * @param suffix
+   *          The class name suffix to match
+   * @param packageNames
+   *          one or more package names to scan (including subpackages) for
+   *          classes
+   */
+  public void findSuffix(String suffix, String... packageNames) {
+    if (packageNames == null)
+      return;
+
+    Test test = new NameEndsWith(suffix);
+    for (String pkg : packageNames) {
+      findInPackage(test, pkg);
+    }
+  }
+
+  /**
+   * Attempts to discover classes that are annotated with to the annotation.
+   * Accumulated classes can be accessed by calling {@link #getClasses()}.
+   * 
+   * @param annotation
+   *          the annotation that should be present on matching classes
+   * @param packageNames
+   *          one or more package names to scan (including subpackages) for
+   *          classes
+   */
+  public void findAnnotated(Class<? extends Annotation> annotation, String... packageNames) {
+    if (packageNames == null)
+      return;
+
+    Test test = new AnnotatedWith(annotation);
+    for (String pkg : packageNames) {
+      findInPackage(test, pkg);
+    }
+  }
+
+  public void findNamedResource(String name, String... pathNames) {
+    if (pathNames == null)
+      return;
+
+    Test test = new NameIs(name);
+    for (String pkg : pathNames) {
+      findInPackage(test, pkg);
+    }
+  }
+
+  /**
+   * Attempts to discover classes that pass the test. Accumulated classes can be
+   * accessed by calling {@link #getClasses()}.
+   * 
+   * @param test
+   *          the test to determine matching classes
+   * @param packageNames
+   *          one or more package names to scan (including subpackages) for
+   *          classes
+   */
+  public void find(Test test, String... packageNames) {
+    if (packageNames == null)
+      return;
+
+    for (String pkg : packageNames) {
+      findInPackage(test, pkg);
+    }
+  }
+
+  /**
+   * Scans for classes starting at the package provided and descending into
+   * subpackages. Each class is offered up to the Test as it is discovered, and
+   * if the Test returns true the class is retained. Accumulated classes can be
+   * fetched by calling {@link #getClasses()}.
+   * 
+   * @param test
+   *          an instance of {@link Test} that will be used to filter classes
+   * @param packageName
+   *          the name of the package from which to start scanning for classes,
+   *          e.g. {@code net.sourceforge.stripes}
+   */
+  public void findInPackage(Test test, String packageName) {
+    packageName = packageName.replace('.', '/');
+    ClassLoader loader = getClassLoader();
+    Enumeration<URL> urls;
+
+    try {
+      urls = loader.getResources(packageName);
+    } catch (IOException ioe) {
+      ERR.println("Could not read package: " + packageName);
+      return;
+    }
+
+    while (urls.hasMoreElements()) {
+      try {
+        String urlPath = urls.nextElement().getFile();
+        urlPath = URLDecoder.decode(urlPath, "UTF-8");
+
+        // If it's a file in a directory, trim the stupid file: spec
+        if (urlPath.startsWith("file:")) {
+          urlPath = urlPath.substring(5);
+        }
+
+        // Else it's in a JAR, grab the path to the jar
+        if (urlPath.indexOf('!') > 0) {
+          urlPath = urlPath.substring(0, urlPath.indexOf('!'));
+        }
+
+        ERR.println("Scanning for classes in [" + urlPath + "] matching criteria: " + test);
+        File file = new File(urlPath);
+        if (file.isDirectory()) {
+          loadImplementationsInDirectory(test, packageName, file);
+        } else {
+          loadImplementationsInJar(test, packageName, file);
+        }
+      } catch (IOException ioe) {
+        ERR.println("could not read entries");
+      }
+    }
+  }
+
+  /**
+   * Finds matches in a physical directory on a filesystem. Examines all files
+   * within a directory - if the File object is not a directory, and ends with
+   * <i>.class</i> the file is loaded and tested to see if it is acceptable
+   * according to the Test. Operates recursively to find classes within a folder
+   * structure matching the package structure.
+   * 
+   * @param test
+   *          a Test used to filter the classes that are discovered
+   * @param parent
+   *          the package name up to this directory in the package hierarchy.
+   *          E.g. if /classes is in the classpath and we wish to examine files
+   *          in /classes/org/apache then the values of <i>parent</i> would be
+   *          <i>org/apache</i>
+   * @param location
+   *          a File object representing a directory
+   */
+  private void loadImplementationsInDirectory(Test test, String parent, File location) {
+    File[] files = location.listFiles();
+    StringBuilder builder = null;
+
+    for (File file : files) {
+      builder = new StringBuilder(100);
+      builder.append(parent).append("/").append(file.getName());
+      String packageOrClass = (parent == null ? file.getName() : builder.toString());
+
+      if (file.isDirectory()) {
+        loadImplementationsInDirectory(test, packageOrClass, file);
+      } else if (isTestApplicable(test, file.getName())) {
+        addIfMatching(test, packageOrClass);
+      }
+    }
+  }
+
+  private boolean isTestApplicable(Test test, String path) {
+    return test.doesMatchResource() || path.endsWith(".class") && test.doesMatchClass();
+  }
+
+  /**
+   * Finds matching classes within a jar files that contains a folder structure
+   * matching the package structure. If the File is not a JarFile or does not
+   * exist a warning will be logged, but no error will be raised.
+   * 
+   * @param test
+   *          a Test used to filter the classes that are discovered
+   * @param parent
+   *          the parent package under which classes must be in order to be
+   *          considered
+   * @param jarfile
+   *          the jar file to be examined for classes
+   */
+  private void loadImplementationsInJar(Test test, String parent, File jarfile) {
+
+    try {
+      JarEntry entry;
+      JarInputStream jarStream = new JarInputStream(new FileInputStream(jarfile));
+
+      while ((entry = jarStream.getNextJarEntry()) != null) {
+        String name = entry.getName();
+        if (!entry.isDirectory() && name.startsWith(parent) && isTestApplicable(test, name)) {
+          addIfMatching(test, name);
+        }
+      }
+    } catch (IOException ioe) {
+      ERR.println("Could not search jar file '" + jarfile + "' for classes matching criteria: " + test + " due to an IOException");
+    }
+  }
+
+  /**
+   * Add the class designated by the fully qualified class name provided to the
+   * set of resolved classes if and only if it is approved by the Test supplied.
+   * 
+   * @param test
+   *          the test used to determine if the class matches
+   * @param fqn
+   *          the fully qualified name of a class
+   */
+  protected void addIfMatching(Test test, String fqn) {
+    try {
+      ClassLoader loader = getClassLoader();
+      if (test.doesMatchClass()) {
+        String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
+        //if (LOG.isDebugEnabled()) {
+        ERR.println("Checking to see if class " + externalName + " matches criteria [" + test + "]");
+        //}
+
+        Class type = loader.loadClass(externalName);
+        if (test.matches(type)) {
+          classMatches.add((Class<T>) type);
+        }
+      }
+      if (test.doesMatchResource()) {
+        URL url = loader.getResource(fqn);
+        if (url == null) {
+          url = loader.getResource(fqn.substring(1));
+        }
+        if (url != null && test.matches(url)) {
+          resourceMatches.add(url);
+        }
+      }
+    } catch (Throwable t) {
+      ERR.println("Could not examine class '" + fqn + "' due to a " + t.getClass().getName() + " with message: " + t.getMessage());
+    }
+  }
+}
\ No newline at end of file
Index: distributed/src/org/apache/lucene/distributed/RMI.java
===================================================================
--- distributed/src/org/apache/lucene/distributed/RMI.java	(revision 0)
+++ distributed/src/org/apache/lucene/distributed/RMI.java	(revision 0)
@@ -0,0 +1,397 @@
+package org.apache.lucene.distributed;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.ConnectException;
+import java.net.InetSocketAddress;
+import java.net.SocketTimeoutException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.net.SocketFactory;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.io.Writable;
+import org.apache.hadoop.ipc.Client;
+import org.apache.lucene.distributed.RMIClasses.ClassAccept;
+import org.apache.lucene.distributed.RMIClasses.ClassKey;
+
+/*
+ * Base class for performing RMI over Hadoop RPC.  Creates a server and 
+ * allows for creation of proxy clients bound to a specific service.  A service
+ * is a remote class defined by an interface.  
+ * 
+ * A default service is ClassService which allows the loading of class bytes.  This is used
+ * during the deserialization of a class on the Lucene server to dynamically load
+ * classes that are not a part of the core Lucene library such as those that subclass
+ * Query or Filter.  
+ * 
+ * The RMI.Server.addService(String name, Object service) method allows defining a 
+ * service by name with the server.
+ * 
+ */
+public class RMI {
+  private static Map<SocketFactory,Client> CLIENTS = new HashMap<SocketFactory,Client>();
+  Map<String,RMIClient> rmiClientMap = new HashMap<String,RMIClient>();
+  RMIClasses clientRmiClasses;
+  ConcurrentHashMap<ProxyKey,Object> proxies = new ConcurrentHashMap<ProxyKey,Object>();
+  Server server;
+  SocketFactory socketFactory;
+  String localhost;
+  int port;
+  ClassAccept classAccept;
+
+  public RMI(String bindAddress, int port, int numHandlers, ClassAccept classAccept) throws IOException {
+    socketFactory = SocketFactory.getDefault();
+    this.port = port;
+    localhost = bindAddress;// InetAddress.getLocalHost().getHostAddress();
+    this.classAccept = classAccept;
+    clientRmiClasses = new RMIClasses(classAccept);
+    Configuration conf = new Configuration();
+    server = new Server(conf, bindAddress, port, numHandlers, false);
+  }
+
+  public static class DefaultClassAccept implements ClassAccept {
+    Set<String> exclude;
+    
+    public DefaultClassAccept(Set<String> exclude) {
+      this.exclude = exclude;
+    }
+    
+    public boolean isSpecial(String name) {
+      if (exclude.contains(name) || name.startsWith("java.") 
+          || name.startsWith("org.apache.lucene.distributed.RMI")
+          || name.startsWith("[")) {
+        return false;
+      }
+      return true;
+    }
+  }
+
+  public Server getServer() {
+    return server;
+  }
+
+  // public void addProxy(RMIClientKey key, Object service) {
+  // proxies.put(key, service);
+  // }
+
+  public Object waitForProxy(String service, Class<?> protocol, long clientVersion, InetSocketAddress addr, Configuration conf)
+      throws IOException {
+    while (true) {
+      try {
+        return getProxy(service, protocol, clientVersion, addr, conf);
+      } catch (ConnectException se) { // namenode has not been started
+        System.out.println("Server at " + addr + " not available yet, Zzzzz...");
+      } catch (SocketTimeoutException te) { // namenode is busy
+        System.out.println("Problem connecting to server: " + addr);
+      }
+      try {
+        Thread.sleep(1000);
+      } catch (InterruptedException ie) {
+        // IGNORE
+      }
+    }
+  }
+
+  // TODO: match versions
+  public synchronized Object getProxy(String service, Class<?> protocol, long clientVersion, InetSocketAddress addr, Configuration conf)
+      throws IOException {
+    ProxyKey key = new ProxyKey(service, addr);
+    Object proxy = proxies.get(key);
+    if (proxy == null) {
+      RMIClient rmiClient = getRMIClient(service, addr, conf);
+      proxy = Proxy.newProxyInstance(protocol.getClassLoader(), new Class[] { protocol }, rmiClient);
+      proxies.put(key, proxy);
+    }
+    return proxy;
+    // if (serverVersion == clientVersion) {
+    // return service;
+    // } else {
+    // throw new VersionMismatch(protocol.getName(), clientVersion,
+    // serverVersion);
+    // }
+  }
+
+  public static class ProxyKey {
+    String service;
+    InetSocketAddress address;
+
+    public ProxyKey(String service, InetSocketAddress address) {
+      this.service = service;
+      this.address = address;
+    }
+
+    @Override
+    public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((address == null) ? 0 : address.hashCode());
+      result = prime * result + ((service == null) ? 0 : service.hashCode());
+      return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj)
+        return true;
+      if (obj == null)
+        return false;
+      if (getClass() != obj.getClass())
+        return false;
+      final ProxyKey other = (ProxyKey) obj;
+      if (address == null) {
+        if (other.address != null)
+          return false;
+      } else if (!address.equals(other.address))
+        return false;
+      if (service == null) {
+        if (other.service != null)
+          return false;
+      } else if (!service.equals(other.service))
+        return false;
+      return true;
+    }
+  }
+
+  public RMIClient getRMIClient(String service, InetSocketAddress addr, Configuration conf) {
+    RMIClient rmiClient = rmiClientMap.get(new ProxyKey(service, addr));
+    if (rmiClient == null) {
+      rmiClient = new RMIClient(service, addr, conf, socketFactory);
+    }
+    return rmiClient;
+  }
+
+  private synchronized Client getClient(Configuration conf, SocketFactory factory) {
+    // Construct & cache client. The configuration is only used for timeout,
+    // and Clients have connection pools. So we can either (a) lose some
+    // connection pooling and leak sockets, or (b) use the same timeout for all
+    // configurations. Since the IPC is usually intended globally, not
+    // per-job, we choose (a).
+    Client client = CLIENTS.get(factory);
+    if (client == null) {
+      client = new Client(ByteArrayWriteable.class, conf, factory);
+      CLIENTS.put(factory, client);
+    }
+    return client;
+  }
+
+  public class RMIClient implements InvocationHandler {
+    Client client;
+    InetSocketAddress address;
+    String service;
+
+    public RMIClient(String service, InetSocketAddress address, Configuration conf, SocketFactory factory) {
+      this.service = service;
+      this.address = address;
+      client = getClient(conf, factory);
+      client.setTimeout(1000 * 3 * 60);
+    }
+
+    public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
+      long startTime = System.currentTimeMillis();
+      Invocation invocation = new Invocation(service, method, params);
+
+      ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+      ObjectOutputStream objectOutput = clientRmiClasses.createOutput(byteOut);
+      objectOutput.writeObject(invocation);
+      objectOutput.flush();
+      ByteArrayWriteable bytesSent = new ByteArrayWriteable(localhost, port, byteOut.toByteArray());
+
+      ByteArrayWriteable bytesReturned = (ByteArrayWriteable) client.call(bytesSent, address);
+      ObjectInputStream objectInput = clientRmiClasses.createInput(bytesReturned.getInputStream(), null);
+      Object object = null;
+      try {
+        object = objectInput.readObject();
+      } catch (ClassNotFoundException cnfe) {
+        IOException ioException = new IOException();
+        ioException.initCause(cnfe);
+        throw ioException;
+      }
+      long callTime = System.currentTimeMillis() - startTime;
+      // LOG.debug("Call: " + method.getName() + " " + callTime);
+      Return r = (Return) object;
+      if (r.value != null)
+        return r.value;
+      else if (r.exception != null)
+        throw r.exception;
+      else
+        return null;
+    }
+  }
+
+  /**
+   * public static class SpecialOutputStream extends OutputStream { private
+   * DataOutput out;
+   * 
+   * public SpecialOutputStream(DataOutput out) { this.out = out; }
+   * 
+   * public void write(int b) throws IOException { out.write(b); } } // TODO:
+   * add read(byte[]) method public static class SpecialInputStream extends
+   * InputStream { private DataInput in;
+   * 
+   * public SpecialInputStream(DataInput in) { this.in = in; }
+   * 
+   * public int read() throws IOException { return in.readByte(); } }
+   */
+  public static class ByteArrayWriteable implements Writable {
+    public String host;
+    public int port;
+    public byte[] bytes;
+
+    public ByteArrayWriteable() {
+    }
+
+    public ByteArrayWriteable(String host, int port, byte[] bytes) {
+      this.host = host;
+      this.port = port;
+      this.bytes = bytes;
+    }
+
+    public InputStream getInputStream() {
+      return new ByteArrayInputStream(bytes);
+    }
+
+    public void write(DataOutput out) throws IOException {
+      out.writeUTF(host);
+      out.writeInt(port);
+      out.writeInt(bytes.length);
+      out.write(bytes);
+    }
+
+    public void readFields(DataInput in) throws IOException {
+      host = in.readUTF();
+      port = in.readInt();
+      int length = in.readInt();
+      bytes = new byte[length];
+      in.readFully(bytes);
+    }
+  }
+
+  public static class Invocation implements Serializable {
+    public String service;
+    public String methodName;
+    public Object[] params;
+    public Class[] paramsClasses;
+
+    public Invocation(String service, Method method, Object[] params) {
+      this.service = service;
+      this.params = params;
+      this.methodName = method.getName();
+      this.paramsClasses = method.getParameterTypes();
+    }
+  }
+
+  public static class Return implements Serializable {
+    public Throwable exception;
+    public Object value;
+  }
+
+  public class Server extends org.apache.hadoop.ipc.Server {
+    Map<InetSocketAddress,ClassService> classServices = new HashMap<InetSocketAddress,ClassService>();
+    RMIClasses rmiClasses;
+    HashMap<String,Object> services = new HashMap<String,Object>();
+
+    public Server(Configuration conf, String bindAddress, int port) throws IOException {
+      this(conf, bindAddress, port, 5, false);
+    }
+
+    public Server(Configuration conf, String bindAddress, int port, int numHandlers, boolean verbose) throws IOException {
+      super(bindAddress, port, ByteArrayWriteable.class, numHandlers, conf);
+      System.out.println("bindAddress: " + bindAddress);
+      rmiClasses = new RMIClasses(classAccept);
+      services.put("classService", new ClassServiceImpl());
+      start();
+    }
+
+    public class ClassServiceImpl implements ClassService {
+      public byte[] getResource(String resource) throws IOException {
+        InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource);
+        if (input == null)
+          return null;
+        return IOUtils.toByteArray(input);
+      }
+
+      public byte[] loadClassData(ClassKey classKey) throws IOException {
+        byte[] bytes = clientRmiClasses.loadClassData(classKey);
+        if (bytes == null) {
+          bytes = rmiClasses.loadClassData(classKey);
+        }
+        if (bytes == null) {
+          String resource = RMIClasses.classToResource(classKey.name);
+          bytes = getResource(resource);
+        }
+        return bytes;
+      }
+    }
+
+    public void addService(String name, Object service) {
+      services.put(name, service);
+    }
+
+    public ClassService getClassService(InetSocketAddress addr) throws IOException {
+      ClassService classService = classServices.get(addr);
+      if (classService == null) {
+        classService = (ClassService) getProxy("classService", ClassService.class, ClassService.serialVersionUID, addr, new Configuration());
+        classServices.put(addr, classService);
+      }
+      return classService;
+    }
+
+    protected ByteArrayWriteable toByteArrayWriteable(Object o) throws IOException {
+      ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+      ObjectOutputStream objectOutput = rmiClasses.createOutput(byteOut);
+      objectOutput.writeObject(o);
+      objectOutput.flush();
+      return new ByteArrayWriteable(localhost, port, byteOut.toByteArray());
+    }
+
+    public Writable call(Writable param) throws IOException {
+      ByteArrayWriteable bytes = (ByteArrayWriteable) param;
+      ClassService classService = getClassService(new InetSocketAddress(bytes.host, bytes.port));
+      ObjectInputStream objectInput = rmiClasses.createInput(bytes.getInputStream(), classService);
+      Object sent = null;
+      try {
+        sent = objectInput.readObject();
+      } catch (ClassNotFoundException cnfe) {
+        IOException ioException = new IOException();
+        ioException.initCause(cnfe);
+        throw ioException;
+      }
+      Invocation invocation = (Invocation) sent;
+      try {
+        Object service = services.get(invocation.service);
+        Method method = service.getClass().getMethod(invocation.methodName, invocation.paramsClasses);
+        long startTime = System.currentTimeMillis();
+        Object value = method.invoke(service, invocation.params);
+        Return r = new Return();
+        r.value = value;
+        return toByteArrayWriteable(r);
+        // return new ObjectWriteable(r, rmiClasses, classService);
+      } catch (InvocationTargetException e) {
+        Throwable target = e.getTargetException();
+        Return r = new Return();
+        r.exception = target;
+        return toByteArrayWriteable(r);
+      } catch (Throwable e) {
+        IOException ioe = new IOException(e.toString());
+        ioe.setStackTrace(e.getStackTrace());
+        throw ioe;
+      }
+    }
+  }
+}
Index: distributed/src/org/apache/lucene/distributed/RMIClasses.java
===================================================================
--- distributed/src/org/apache/lucene/distributed/RMIClasses.java	(revision 0)
+++ distributed/src/org/apache/lucene/distributed/RMIClasses.java	(revision 0)
@@ -0,0 +1,227 @@
+package org.apache.lucene.distributed;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.io.IOUtils;
+
+// TODO: need to load classes from jar names of classes that are in Lucene
+// and store as a text file
+// TODO: if class cannot be loaded from
+/**
+ * Offers object streaming with dynamic class loading.  
+ * 
+ * The ClassKey holds the class name and serialVersionUID.  The combination makes a 
+ * class unique.  This allows classes that have changed to be loading dynamically on
+ * deserialization in RMIObjectInputStream.  This is because when a class changes, the 
+ * serialVersionUID changes as well.
+ * 
+ * RMIObjectOutputStream records classes that are ok for ClassAccept.isSpecial in the 
+ * classLoaders map.  In the Lucene use case these would be subclasses of for example
+ * the Query class serialized on the client side.
+ * 
+ * RMIObjectInputStream dynamically loads classes that are ClassAccept.isSpecial from the
+ * the given CLInterface.  If the class is new, a SingleClassLoader is created and
+ * placed into classLoaders.
+ * 
+ */
+public class RMIClasses implements CLInterface {
+  ConcurrentHashMap<ClassKey,ClassLoader> classLoaders = new ConcurrentHashMap<ClassKey,ClassLoader>();
+  ClassAccept classAccept;
+
+  public RMIClasses(ClassAccept classAccept) {
+    this.classAccept = classAccept;
+  }
+  
+  public static interface ClassAccept {
+    public boolean isSpecial(String name);
+  }
+  
+  public byte[] getResource(String resource) throws IOException {
+    throw new UnsupportedOperationException("");
+  }
+
+  public static String classToResource(String name) {
+    return name.replace('.', '/') + ".class";
+  }
+
+  public byte[] loadClassData(ClassKey classKey) throws IOException {
+    ClassLoader classLoader = classLoaders.get(classKey);
+    if (classLoader != null) {
+      String resource = classToResource(classKey.name);
+      InputStream input = classLoader.getResourceAsStream(resource);
+      return IOUtils.toByteArray(input);
+    } else
+      return null;
+  }
+  
+  /**
+   * Maps a ClassKey to a ClassLoader.  ClassLoader will only load the 
+   * Class given by the ClassKey.  The class bytes are read from CLInterface.
+   *
+   */
+  public static class SingleClassLoader extends ClassLoader {
+    CLInterface clInterface;
+    ClassKey classKey;
+
+    public SingleClassLoader(ClassKey classKey, ClassLoader parent, CLInterface clInterface) {
+      super(parent);
+      this.classKey = classKey;
+      this.clInterface = clInterface;
+    }
+
+    public InputStream getResourceAsStream(String name) {
+      try {
+        return new ByteArrayInputStream(clInterface.getResource(name));
+      } catch (IOException ioException) {
+        throw new RuntimeException(ioException);
+      }
+    }
+
+    protected Class<?> findClass(final String name) throws ClassNotFoundException {
+      if (name.equals(classKey.name)) {
+        byte[] classData = null;
+        try {
+          classData = clInterface.loadClassData(classKey);
+        } catch (Exception exception) {
+          throw new ClassNotFoundException("", exception);
+        }
+        return defineClass(name, classData, 0, classData.length);
+      } else {
+        return super.findClass(name);
+      }
+    }
+
+    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+      Class c = findLoadedClass(name);
+      if (c == null) {
+        if (name.equals(classKey.name)) {
+          c = findClass(name);
+          if (resolve) {
+            resolveClass(c);
+          }
+          return c;
+        } else {
+          return super.loadClass(name, resolve);
+        }
+      }
+      return c;
+    }
+  }
+
+  public class RMIObjectOutputStream extends ObjectOutputStream {
+    public RMIObjectOutputStream(OutputStream output) throws IOException {
+      super(output);
+    }
+
+    protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException {
+      String name = desc.getName();
+      if (classAccept.isSpecial(name)) {
+        Class cl = desc.forClass();
+        ClassKey classKey = new ClassKey(desc.getName(), desc.getSerialVersionUID());
+        if (!classLoaders.containsKey(classKey)) {
+          ClassLoader classLoader = cl.getClassLoader();
+          if (classLoader == null) {
+            assert classLoader != null;
+          }
+          classLoaders.put(classKey, classLoader);
+        }
+      }
+      super.writeClassDescriptor(desc);
+    }
+  }
+
+  public RMIObjectOutputStream createOutput(OutputStream output) throws IOException {
+    return new RMIObjectOutputStream(output);
+  }
+
+  public RMIObjectInputStream createInput(InputStream input, CLInterface clInterface) throws IOException {
+    return new RMIObjectInputStream(input, clInterface);
+  }
+
+  public class RMIObjectInputStream extends ObjectInputStream {
+    CLInterface clInterface;
+
+    public RMIObjectInputStream(InputStream input, CLInterface clInterface) throws IOException {
+      super(input);
+      this.clInterface = clInterface;
+    }
+
+    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
+      String name = desc.getName();
+      if (!classAccept.isSpecial(name)) {
+        try {
+          return super.resolveClass(desc);
+        } catch (ClassNotFoundException ex) {
+          // could not find class locally, try loading it
+        }
+      }
+      long serialUID = desc.getSerialVersionUID();
+      ClassLoader classLoader = getClassLoader(new ClassKey(name, serialUID), clInterface);
+      if (classLoader == null)
+        return super.resolveClass(desc);
+      Class c = classLoader.loadClass(name);
+      if (c == null) {
+        throw new ClassNotFoundException(name);
+      }
+      return c;
+    }
+  }
+
+  public static class ClassKey implements Serializable {
+    public String name;
+    public long serialUID;
+
+    public ClassKey(String name, long serialUID) {
+      this.name = name;
+      this.serialUID = serialUID;
+    }
+
+    @Override
+    public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((name == null) ? 0 : name.hashCode());
+      result = prime * result + (int) (serialUID ^ (serialUID >>> 32));
+      return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj)
+        return true;
+      if (obj == null)
+        return false;
+      if (getClass() != obj.getClass())
+        return false;
+      final ClassKey other = (ClassKey) obj;
+      if (name == null) {
+        if (other.name != null)
+          return false;
+      } else if (!name.equals(other.name))
+        return false;
+      if (serialUID != other.serialUID)
+        return false;
+      return true;
+    }
+
+  }
+
+  public ClassLoader getClassLoader(ClassKey classKey, CLInterface clInterface) {
+    ClassLoader classLoader = classLoaders.get(classKey);
+    if (classLoader == null) {
+      ClassLoader parent = Thread.currentThread().getContextClassLoader();
+      classLoader = new SingleClassLoader(classKey, parent, clInterface);
+      classLoaders.put(classKey, classLoader);
+    }
+    return classLoader;
+  }
+}
Index: distributed/test/org/apache/lucene/distributed/TestClassLoader.java
===================================================================
--- distributed/test/org/apache/lucene/distributed/TestClassLoader.java	(revision 0)
+++ distributed/test/org/apache/lucene/distributed/TestClassLoader.java	(revision 0)
@@ -0,0 +1,118 @@
+package org.apache.lucene.distributed;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.HashSet;
+import java.util.Random;
+import java.util.Set;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.lucene.distributed.RMI.DefaultClassAccept;
+import org.apache.lucene.distributed.RMIClasses.ClassKey;
+import org.apache.lucene.search.Query;
+
+public class TestClassLoader {
+  public static final String javacCmd = "G:\\jdk1.5.0_15\\bin\\javac.exe";
+  public static final String classPath = ".;G:\\oceanworkspace\\LuceneOcean\\bin"; // for use when compiling, needs to have Lucene classes
+  static File dir = new File("g:\\testclassloader"); // where output source and classes are compiled to
+  static Random random = new Random(System.currentTimeMillis());
+  static DefaultClassAccept classAccept = new DefaultClassAccept(LuceneClasses.classes);
+
+  public static void main(String[] args) throws Exception {
+    Instance i1 = new Instance(1);
+    Query q1 = testInput(i1);
+    Instance i2 = new Instance(2);
+    Query q2 = testInput(i2);
+    q2.toString(q1.toString());
+    // Instance i3 = new Instance(3);
+  }
+
+  public static Query testInput(Instance i1) throws Exception {
+    byte[] bytes = i1.serialize();
+    RMIClasses rc = new RMIClasses(classAccept);
+    ObjectInputStream objIn = rc.createInput(new ByteArrayInputStream(bytes), i1);
+    Query query = (Query) objIn.readObject();
+    System.out.println(query.toString(null));
+    return query;
+  }
+
+  public static class Instance implements CLInterface {
+    File srcFile;
+    File d;
+    RMIClasses classes;
+    Query query;
+
+    public Instance(int i) throws Exception {
+      d = new File(dir, i + "");
+      d.mkdirs();
+      srcFile = new File(d, "TP.java");
+      outputRandomSource((long)i, srcFile);
+      javac(d.getAbsolutePath(), srcFile.getAbsolutePath());
+      classes = new RMIClasses(classAccept);
+      ClassLoader cl = classes.getClassLoader(new ClassKey("TP", 1l), this);
+      Class c = cl.loadClass("TP");
+      query = (Query) c.newInstance();
+    }
+
+    public byte[] serialize() throws Exception {
+      System.out.println(query.toString(null));
+      ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+      ObjectOutputStream objOut = classes.createOutput(byteOut);
+      objOut.writeObject(query);
+      objOut.flush();
+      objOut.close();
+      return byteOut.toByteArray();
+    }
+
+    public byte[] getResource(String name) {
+      try {
+        File f = new File(d, name);
+        return FileUtils.readFileToByteArray(f);
+      } catch (IOException ioException) {
+        throw new RuntimeException(ioException);
+      }
+    }
+
+    public byte[] loadClassData(ClassKey classKey) throws IOException {
+      String resource = classKey.name.replace('.', '/') + ".class";
+      File f = new File(d, resource);
+      byte[] bytes = FileUtils.readFileToByteArray(f);
+      return bytes;
+    }
+  }
+
+  public static void javac(String outputDir, String source) throws IOException {
+    String cmd = javacCmd + " -d " + outputDir + " -cp "+classPath+" " + source;
+    Process process = Runtime.getRuntime().exec(cmd);
+    IOUtils.copy(process.getErrorStream(), System.err);
+    IOUtils.copy(process.getInputStream(), System.err);
+  }
+
+  public static void outputRandomSource(long id, File file) throws IOException {
+    StringBuilder b = new StringBuilder();
+    // b.append("package test;\n\n");
+    b.append("public class TP extends org.apache.lucene.search.Query {\n");
+    b.append("private static final long serialVersionUID = "+id+"l;\n");
+    StringBuilder tb = new StringBuilder();
+    for (int x = 0; x < 10; x++) {
+      tb.append(random.nextInt());
+    }
+    b.append("public String toString(String field) {\n");
+    b.append("  return \"" + tb.toString() + "\";\n");
+    b.append("}");
+    // b.append("public String text = \""+tb.toString()+"\";\n");
+    b.append("}");
+    FileUtils.writeStringToFile(file, b.toString());
+  }
+
+  public void testMain() throws Exception {
+    // RMIClasses classes1 = new RMIClasses(CLInterface clInterface);
+    // RMIClasses classes2 = new RMIClasses(CLInterface clInterface);
+
+  }
+}
Index: distributed/test/org/apache/lucene/distributed/TestRMIClient.java
===================================================================
--- distributed/test/org/apache/lucene/distributed/TestRMIClient.java	(revision 0)
+++ distributed/test/org/apache/lucene/distributed/TestRMIClient.java	(revision 0)
@@ -0,0 +1,40 @@
+package org.apache.lucene.distributed;
+
+import java.net.InetSocketAddress;
+
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.lucene.distributed.RMI.DefaultClassAccept;
+import org.apache.lucene.distributed.TestClassLoader.Instance;
+import org.apache.lucene.util.LuceneTestCase;
+
+/**
+ * 1) Dynamically compiles custom Query class that overrides the toString(String field) 
+ * method to return a random string.  Calls TestService.search(Query) on a remote server
+ * which returns the random string.  
+ * 
+ * 2) Again compiles the Query class with a different random string and calls TestService.search(Query)
+ * if the string returned this time is different than 1) then the new query class was dynamically
+ * downloaded by the server. 
+ *
+ */
+public class TestRMIClient extends LuceneTestCase {
+  public static void main(String args[]) throws Exception {
+    TestRunner.run(new TestSuite(TestRMIClient.class));
+  }
+
+  public void testMain() throws Exception {
+    Instance i1 = new Instance(1);
+    RMI rmi2 = new RMI("192.168.1.2", 9001, 2, new DefaultClassAccept(LuceneClasses.classes));
+    TestService testService = (TestService) rmi2.waitForProxy("testService", TestService.class, 1, new InetSocketAddress("192.168.1.2",
+        9000), new Configuration());
+    String str1 = testService.search(i1.query);
+    System.out.println("str1: " + str1);
+
+    Instance i2 = new Instance(2);
+    String str2 = testService.search(i2.query);
+    System.out.println("str2: " + str2);
+  }
+}
Index: distributed/test/org/apache/lucene/distributed/TestRMIServer.java
===================================================================
--- distributed/test/org/apache/lucene/distributed/TestRMIServer.java	(revision 0)
+++ distributed/test/org/apache/lucene/distributed/TestRMIServer.java	(revision 0)
@@ -0,0 +1,30 @@
+package org.apache.lucene.distributed;
+
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+
+import org.apache.lucene.distributed.RMI.DefaultClassAccept;
+import org.apache.lucene.distributed.RMI.Server;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.util.LuceneTestCase;
+
+public class TestRMIServer extends LuceneTestCase {
+  public static void main(String args[]) throws Exception {
+    TestRunner.run(new TestSuite(TestRMIServer.class));
+  }
+
+  public void testMain() throws Exception {
+    RMI rmi1 = new RMI("192.168.1.2", 9000, 2, new DefaultClassAccept(LuceneClasses.classes));
+    Server server = rmi1.getServer();
+    server.addService("testService", new TestServiceImpl());
+    while (true) {
+      Thread.sleep(50);
+    }
+  }
+  
+  public class TestServiceImpl implements TestService {
+    public String search(Query query) throws Exception {
+      return query.toString(null);
+    }
+  }
+}
Index: distributed/test/org/apache/lucene/distributed/TestService.java
===================================================================
--- distributed/test/org/apache/lucene/distributed/TestService.java	(revision 0)
+++ distributed/test/org/apache/lucene/distributed/TestService.java	(revision 0)
@@ -0,0 +1,7 @@
+package org.apache.lucene.distributed;
+
+import org.apache.lucene.search.Query;
+
+public interface TestService {
+  public String search(Query query) throws Exception;
+}
