You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by mi...@apache.org on 2014/03/16 19:11:10 UTC

svn commit: r1578133 [8/11] - in /lucene/dev/branches/lucene5376_2/lucene: ./ analysis/common/src/java/org/apache/lucene/analysis/charfilter/ analysis/common/src/java/org/apache/lucene/analysis/pattern/ analysis/common/src/java/org/apache/lucene/analys...

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/Request.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/Request.java?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/Request.java (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/Request.java Sun Mar 16 18:11:07 2014
@@ -0,0 +1,679 @@
+package org.apache.lucene.server.params;
+
+/*
+ * 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.
+ */
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lucene.server.params.PolyType.PolyEntry;
+import net.minidev.json.JSONArray;
+import net.minidev.json.JSONObject;
+
+// nocommit instead of removing as we getXXX, we could do a
+// Set<String> seen?
+//   - would close the loophole of foobar={} not being detected
+//   - would allow us to lookup same param more than once
+
+/** Pairs up the actual parameters with its type.  For
+ *  complex requests, e.g. {@code search}, this is used
+ *  recursively.  For example, the top-most Request is
+ *  created, but then when a sub-struct parameter is
+ *  retrieved with {@link #getStruct}, that returns another
+ *  {@code Request} wrapping that value. */
+
+public class Request {
+
+  /** Type describing the expected object. */
+  private final StructType type;
+
+  /** The actual request parameters. */
+  private final JSONObject params;
+
+  /** Parent, if this is a sub Request, else null for the
+   *  top-level request.  This is used for back-trace for
+   *  error reporting. */
+  private final Request parent;
+
+  /** Our parameter name from our parent, or null if we are
+   *  a top request.  This is used for back-trace for error
+   *  reporting. */
+  private final String name;
+
+  /** Creates this. */
+  public Request(Request parent, String name, JSONObject params, StructType type) {
+    this.params = params;
+    this.type = type;
+    this.parent = parent;
+    this.name = name;
+  }
+
+  /** Creates Request from another Request, changing the
+   *  struct type. */
+  public Request(Request other, StructType type) {
+    this.params = other.params;
+    this.type = type;
+    this.parent = other.parent;
+    this.name = other.name;
+  }
+
+  /** Clears all parameters. */
+  public void clearParams() {
+    params.clear();
+  }
+
+  /** Clear a specific parameter. */
+  public void clearParam(String param) {
+    params.remove(param);
+  }
+
+  /** Get the type for this request. */
+  public StructType getType() {
+    return type;
+  }
+
+  /** True if this param was specified. */
+  public boolean hasParam(String name) {
+    Param p = type.params.get(name);
+    assert p != null: "name \"" + name + "\" is not a known parameter";
+    return params.containsKey(name);
+  }
+
+  /** Returns an iterator over all parameters and their
+   *  values. */
+  public Iterator<Map.Entry<String,Object>> getParams() {
+    return params.entrySet().iterator();
+  }
+
+  /** Returns the parameters. */
+  public JSONObject getRawParams() {
+    return params;
+  }
+
+  @Override
+  public String toString() {
+    return params.toString();
+  }
+
+  /** Returns the raw (un type cast) value for this
+   *  parameter.  Once this is called
+   *  for a given parameter it cannot be called again on
+   *  that parameter.*/
+  public Object getAny(String name) {
+    Param p = type.params.get(name);
+    assert p != null: "name \"" + name + "\" is not a known parameter";
+    assert p.type instanceof AnyType;
+
+    Object v = params.get(name);
+    if (v == null) {
+      // Request didn't provide it:
+      if (p.defaultValue != null) {
+        // Fallback to default
+        return p.defaultValue;
+      } else {
+        fail(name, "required parameter \"" + name + "\" is missing");
+        // dead code but compiler disagrees:
+        return false;
+      }
+    } else {
+      params.remove(name);
+      p.type.validate(v);
+      return v;
+    }
+  }
+
+  /** Retrieve a boolean parameter.  Once this is called
+   *  for a given parameter it cannot be called again on
+   *  that parameter. */
+  public boolean getBoolean(String name) {
+    Param p = type.params.get(name);
+    assert p != null: "name \"" + name + "\" is not a known parameter";
+    assert p.type instanceof BooleanType: "name \"" + name + "\" is not BooleanType: got " + p.type;
+
+    Object v = params.get(name);
+    if (v == null) {
+      // Request didn't provide it:
+      if (p.defaultValue != null) {
+        // Fallback to default
+        return ((Boolean) p.defaultValue).booleanValue();
+      } else {
+        fail(name, "required parameter \"" + name + "\" is missing");
+        // dead code but compiler disagrees:
+        return false;
+      }
+    } else {
+      try {
+        p.type.validate(v);
+      } catch (IllegalArgumentException iae) {
+        fail(name, iae.getMessage(), iae);
+      }
+      params.remove(name);
+      return ((Boolean) v).booleanValue();
+    }
+  }
+
+  /** Retrieve a float parameter.  Once this is called
+   *  for a given parameter it cannot be called again on
+   *  that parameter. */
+  public float getFloat(String name) {
+    Param p = type.params.get(name);
+    assert p != null: "name \"" + name + "\" is not a known parameter";
+    assert p.type instanceof FloatType: "name \"" + name + "\" is not FloatType: got " + p.type;
+
+    Object v = params.get(name);
+    if (v == null) {
+      // Request didn't provide it:
+      if (p.defaultValue != null) {
+        // Fallback to default
+        return ((Float) p.defaultValue).floatValue();
+      } else {
+        fail(name, "required parameter \"" + name + "\" is missing");
+        // dead code but compiler disagrees:
+        return 0;
+      }
+    } else {
+      try {
+        p.type.validate(v);
+      } catch (IllegalArgumentException iae) {
+        fail(name, iae.getMessage(), iae);
+      }
+      params.remove(name);
+      return ((Number) v).floatValue();
+    }
+  }
+
+  /** Retrieve a double parameter.  Once this is called
+   *  for a given parameter it cannot be called again on
+   *  that parameter. */
+  public double getDouble(String name) {
+    Param p = type.params.get(name);
+    assert p != null: "name \"" + name + "\" is not a known parameter";
+    // assert p.type instanceof FloatType: "name \"" + name + "\" is not FloatType: got " + p.type;
+
+    Object v = params.get(name);
+    if (v == null) {
+      // Request didn't provide it:
+      if (p.defaultValue != null) {
+        // Fallback to default
+        return ((Number) p.defaultValue).doubleValue();
+      } else {
+        fail(name, "required parameter \"" + name + "\" is missing");
+        // dead code but compiler disagrees:
+        return 0;
+      }
+    } else {
+      try {
+        p.type.validate(v);
+      } catch (IllegalArgumentException iae) {
+        fail(name, iae.getMessage(), iae);
+      }
+      params.remove(name);
+      return ((Number) v).doubleValue();
+    }
+  }
+
+  /** Retrieve an int parameter.  Once this is called
+   *  for a given parameter it cannot be called again on
+   *  that parameter. */
+  public int getInt(String name) {
+    Param p = type.params.get(name);
+    assert p != null: "name \"" + name + "\" is not a known parameter";
+    assert p.type instanceof IntType: "name \"" + name + "\" is not IntType: got " + p.type;
+
+    Object v = params.get(name);
+    if (v == null) {
+      // Request didn't provide it:
+      if (p.defaultValue != null) {
+        // Fallback to default
+        return ((Integer) p.defaultValue).intValue();
+      } else {
+        fail(name, "required parameter \"" + name + "\" is missing");
+        // dead code but compiler disagrees:
+        return 0;
+      }
+    } else if (!(v instanceof Integer)) {
+      fail(name, "expected Integer but got: " + v.getClass());
+      // Dead code but compiler disagrees:
+      return 0;
+    } else {
+      params.remove(name);
+      return ((Integer) v).intValue();
+    }
+  }
+
+  /** Retrieve a long parameter.  Once this is called
+   *  for a given parameter it cannot be called again on
+   *  that parameter. */
+  public long getLong(String name) {
+    Param p = type.params.get(name);
+    assert p != null: "name \"" + name + "\" is not a known parameter";
+    //assert p.type instanceof LongType: "name \"" + name + "\" is not LongType: got " + p.type;
+
+    Object v = params.get(name);
+    if (v == null) {
+      // Request didn't provide it:
+      if (p.defaultValue != null) {
+        // Fallback to default
+        return ((Long) p.defaultValue).longValue();
+      } else {
+        fail(name, "required parameter \"" + name + "\" is missing");
+        // dead code but compiler disagrees:
+        return 0L;
+      }
+    } else if (!(v instanceof Long) && !(v instanceof Integer)) {
+      fail(name, "expected Long but got: " + v.getClass());
+      // Dead code but compiler disagrees:
+      return 0L;
+    } else {
+      params.remove(name);
+      return ((Number) v).longValue();
+    }
+  }
+
+  /** True if the parameter is a string value. */
+  public boolean isString(String name) {
+    Param p = type.params.get(name);
+    assert p != null: "name \"" + name + "\" is not a known parameter";
+    Object v = params.get(name);
+    return v instanceof String;
+  }
+
+  /** Retrieve a string parameter.  Once this is called
+   *  for a given parameter it cannot be called again on
+   *  that parameter. */
+  public String getString(String name) {
+    Param p = type.params.get(name);
+    assert p != null: "name \"" + name + "\" is not a known parameter";
+    // Use getEnum instead:
+    assert !(p.type instanceof EnumType);
+
+    Object v = params.get(name);
+    if (v == null) {
+      // Request didn't provide it:
+      if (p.defaultValue != null) {
+        // Fallback to default
+        return (String) p.defaultValue;
+      } else {
+        fail(name, "required parameter \"" + name + "\" is missing");
+        // dead code but compiler disagrees:
+        return null;
+      }
+    } else {
+      if ((v instanceof String) == false) {
+        fail(name, "expected String but got " + toSimpleString(v.getClass()));
+      }
+      try {
+        p.type.validate(v);
+      } catch (IllegalArgumentException iae) {
+        fail(name, iae.getMessage(), iae);
+      }
+      params.remove(name);
+      return (String) v;
+    }
+  }
+
+  private static String toSimpleString(Class<?> cl) {
+    return cl.getSimpleName();
+  }
+
+  /** Retrieve an enum parameter.  Once this is called
+   *  for a given parameter it cannot be called again on
+   *  that parameter. */
+  public String getEnum(String name) {
+    Param p = type.params.get(name);
+    assert p != null: "name \"" + name + "\" is not a known parameter";
+    assert p.type instanceof EnumType: "name \"" + name + "\" is not EnumType: got " + p.type;
+
+    Object v = params.get(name);
+    if (v == null) {
+      // Request didn't provide it:
+      if (p.defaultValue != null) {
+        // Fallback to default
+        return (String) p.defaultValue;
+      } else {
+        fail(name, "required parameter \"" + name + "\" is missing");
+        // dead code but compiler disagrees:
+        return null;
+      }
+    } else {
+      // Make sure the value is valid for this enum:
+      try {
+        p.type.validate(v);
+      } catch (IllegalArgumentException iae) {
+        fail(name, iae.getMessage(), iae);
+      }
+      params.remove(name);
+      return (String) v;
+    }
+  }
+
+  /** A result returned from {@link #getPoly}. */
+  public static class PolyResult {
+    /** The name of the poly parameter. */
+    public final String name;
+
+    /** The new request, cast to the poly sub type */
+    public final Request r;
+
+    /** Sole constructor. */
+    PolyResult(String name, Request r) {
+      this.name = name;
+      this.r = r;
+    }
+  }
+
+  /** Retrieve a poly typed parameter.  Once this is called
+   *  for a given parameter it cannot be called again on
+   *  that parameter. */
+  public PolyResult getPoly(String name) {
+    Param p = type.params.get(name);
+    assert p != null: "name \"" + name + "\" is not a known parameter";
+    assert p.type instanceof PolyType: "name \"" + name + "\" is not PolyType: got " + p.type;
+    Object v = params.get(name);
+    if (v == null) {
+      fail(name, "required parameter \"" + name + "\" is missing");
+      // dead code but compiler disagrees:
+      return null;
+    } else if (!(v instanceof String)) {
+      fail(name, "expected String but got " + v);
+      // dead code but compiler disagrees:
+      return null;
+    } else {
+      PolyType pt = (PolyType) p.type;
+      String value = (String) v;
+      PolyEntry sub = pt.types.get(value);
+      if (sub == null) {
+        fail(name, "unrecognized value \"" + value + "\"; must be one of: " + pt.types.keySet());
+      }
+      params.remove(name);
+      return new PolyResult((String) v, new Request(parent, name, params, sub.type));
+    }
+  }
+
+  /** Retrieve the raw object for a parameter, or null if
+   *  the parameter was not specified.  This can be called
+   *  multiple types for a given parameter. */
+  public Object getRaw(String name) {
+    return params.get(name);
+  }
+
+  /** Returns the raw object, and removes the binding. */
+  public Object getAndRemoveRaw(String name) {
+    Object o = params.get(name);
+    params.remove(name);
+    return o;
+  }
+
+  /** Retrieve a struct parameter.  This can be called
+   *  multiple times for a given parameter name. */
+  public Request getStruct(String name) {
+    Param p = type.params.get(name);
+    assert p != null: "name \"" + name + "\" is not a known parameter; valid params=" + type.params.keySet() + "; path=" + getPath();
+    Type pType = p.type;
+    if (pType instanceof WrapType) {
+      pType = ((WrapType) pType).getWrappedType();
+    }
+    if (pType instanceof StructType == false) {
+      pType = findStructType(pType);
+    }
+    assert pType instanceof StructType: "name \"" + name + "\" is not StructType: got " + type;
+    
+    Object v = params.get(name);
+    if (v == null) {
+      fail(name, "required parameter \"" + name + "\" is missing");
+      // dead code but compiler disagrees:
+      return null;
+    } else {
+      // If the value is a String, and the type is Struct
+      // that has a "class" param, pretend this was a Struct
+      // with just the "class" param.  This is so user can
+      // just do String (for the class name) instead of
+      // struct with only "class" param:
+      if (v instanceof String) {
+        StructType st = (StructType) pType;
+        if (st.params.containsKey("class") && st.params.get("class").type instanceof PolyType) {
+          JSONObject o = new JSONObject();
+          o.put("class", v);
+          v = o;
+          params.remove(name);
+        }
+      }
+
+      if (!(v instanceof JSONObject)) {
+        fail(name, "expected Object but got " + v.getClass());
+      }
+
+      // nocommit does this mean we fail to detect when a
+      // whole extra struct was specified
+
+      // Don't remove, so that we can recurse and make sure
+      // all structs had all their params visited too
+      //params.remove(name);
+      return new Request(this, name, (JSONObject) v, (StructType) pType);
+    }
+  }
+
+  // nocommit hacky
+  private StructType findStructType(Type t) {
+    if (t instanceof StructType) {
+      return (StructType) t;
+    } else if (t instanceof ListType) {
+      ListType lt = (ListType) t;
+      if (lt.subType instanceof StructType) {
+        return (StructType) lt.subType;
+      } else if (lt.subType instanceof OrType) {
+        for(Type t2 : ((OrType) lt.subType).types) {
+          if (t2 instanceof StructType) {
+            return (StructType) t2;
+          }
+        }
+      }
+    } else if (t instanceof OrType) {
+      OrType ot = (OrType) t;
+      for(Type t2 : ot.types) {
+        if (t2 instanceof StructType) {
+          return (StructType) t2;
+        }
+      }
+    }
+
+    return null;
+  }
+
+  /** Retrieve a list parameter.  Once this is called for a
+   *  given parameter it cannot be called again on that
+   *  parameter. */
+  @SuppressWarnings("unchecked")
+  public List<Object> getList(String name) {
+    Param p = type.params.get(name);
+    assert p != null: "name \"" + name + "\" is not a known parameter";
+    // nocommit make this stronger ... check the subTypes of OrType:
+    //assert p.type instanceof ListType: "name \"" + name + "\" is not ListType: got " + p.type;
+    assert p.type instanceof ListType || p.type instanceof OrType: "name \"" + name + "\" is not ListType: got " + p.type;
+    Object v = params.get(name);
+    if (v == null) {
+      if (p.defaultValue != null) {
+        // Fallback to default
+        return (List<Object>) p.defaultValue;
+      } else {
+        fail(name, "required parameter \"" + name + "\" is missing");
+        // dead code but compiler disagrees:
+        return null;
+      }
+    } else {
+      if (!(v instanceof JSONArray)) {
+        fail(name, "expected array but got " + v.getClass());
+      }
+      List<Object> subs = new ArrayList<Object>();
+      Iterator<Object> it = ((JSONArray) v).iterator();
+      int idx = 0;
+      while(it.hasNext()) {
+        Object o = it.next();
+
+        // If the value is a String, and the type is Struct
+        // that has a "class" param, pretend this was a Struct
+        // with just the "class" param.  This is so user can
+        // just do String (for the class name) instead of
+        // struct with only "class" param:
+        if (o instanceof String && p.type instanceof ListType && ((ListType) p.type).subType instanceof StructType) {
+          StructType st = (StructType) ((ListType) p.type).subType;
+          if (st.params.containsKey("class") && st.params.get("class").type instanceof PolyType) {
+            JSONObject o2 = new JSONObject();
+            o2.put("class", o);
+            o = o2;
+            it.remove();
+          }
+        }
+
+        if (o instanceof JSONObject) {
+          StructType st = findStructType(p.type);
+          if (st != null) {
+            subs.add(new Request(this, name + "[" + idx + "]", (JSONObject) o, st));
+          } else {
+            subs.add(o);
+          }
+        } else {
+          // nocommit make this work w/ OrType
+          if (p.type instanceof ListType) {
+            try {
+              ((ListType) p.type).subType.validate(o);
+            } catch (IllegalArgumentException iae) {
+              fail(name + "[" + idx + "]", iae.getMessage(), iae);
+            }
+          }
+          subs.add(o);
+        }
+        if (!(o instanceof JSONObject)) {
+          // nocommit this is O(N^2)!!
+          it.remove();
+        }
+        idx++;
+      }
+      return subs;
+    }
+  }
+
+  /** True if this request has any bindings, excluding
+   *  empty containers. */
+  public static boolean anythingLeft(JSONObject obj) {
+    Iterator<Map.Entry<String,Object>> it = obj.entrySet().iterator();
+    boolean anything = false;
+    while(it.hasNext()) {
+      Map.Entry<String,Object> ent = it.next();
+      if (ent.getValue() instanceof JSONObject) {
+        if (!anythingLeft((JSONObject) ent.getValue())) {
+          it.remove();
+        } else {
+          anything = true;
+        }
+      } else if (ent.getValue() instanceof JSONArray) {
+        Iterator<Object> it2 = ((JSONArray) ent.getValue()).iterator();
+        while(it2.hasNext()) {
+          Object obj2 = it2.next();
+          if (obj2 instanceof JSONObject) {
+            if (!anythingLeft((JSONObject) obj2)) {
+              it2.remove();
+            } else {
+              anything = true;
+            }
+          }
+        }
+        if (((JSONArray) ent.getValue()).isEmpty()) {
+          it.remove();
+        } else {
+          anything = true;
+        }
+      } else {
+        anything = true;
+      }
+    }
+
+    return anything;
+  }
+
+  private void buildPath(StringBuilder sb) {
+    if (parent != null) {
+      parent.buildPath(sb);
+    }
+    if (sb.length() > 0) {
+      sb.append(" > ");
+    }
+    if (name != null) {
+      sb.append(name);
+    }
+  }
+
+  private String getPath() {
+    StringBuilder sb = new StringBuilder();
+    buildPath(sb);
+    return sb.toString();
+  }
+
+  /** Throws a {@link RequestFailedException} with the
+   *  provided message. */
+  public void fail(String message) {
+    fail(null, message, null);
+  }
+
+  /** Throws a {@link RequestFailedException} with the
+   *  provided parameter and message. */
+  public void fail(String param, String message) {
+    fail(param, message, null);
+  }
+
+  /** Throws a {@link RequestFailedException} with the
+   *  provided parameter and message. */
+  public void fail(String message, Throwable cause) {
+    fail(null, message, cause);
+  }
+
+  /** Throws {@link RequestFailedException} when the wrong
+   *  class was encountered. */
+  public void failWrongClass(String param, String reason, Object thingy) {
+    fail(param, reason + "; got: " + thingy.getClass());
+  }
+
+  /** Throws a {@link RequestFailedException} with the
+   *  provided parameter and message and original cause. */
+  public void fail(String param, String reason, Throwable cause) {
+    StringBuilder sb = new StringBuilder();
+    buildPath(sb);
+    if (param != null) {
+      Param p = type.params.get(param);
+
+      // Exempt param[N]:
+      if (p == null && param.indexOf('[') == -1) {
+        // BUG:
+        assert false: "name \"" + param + "\" is not a known parameter";
+      }
+
+      if (sb.length() > 0) {
+        sb.append(" > ");
+      }
+      sb.append(param);
+    }
+
+    RequestFailedException e = new RequestFailedException(this, param, sb.toString(), reason);
+    if (cause != null) {
+      e.initCause(cause);
+    }
+
+    throw e;
+  }
+}

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/RequestFailedException.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/RequestFailedException.java?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/RequestFailedException.java (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/RequestFailedException.java Sun Mar 16 18:11:07 2014
@@ -0,0 +1,58 @@
+package org.apache.lucene.server.params;
+
+/*
+ * 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.
+ */
+
+// nocommit rename to InvalidRequestExc?
+
+/** Exception thrown on an invalid request. */
+public class RequestFailedException extends IllegalArgumentException {
+
+  /** The request that contains the failure. */
+  public final Request request;
+
+  /** Which parameter led to the failure, if any (can be
+   * null). */
+  public final String param;
+
+  /** The full path leading to the parameter (e.g.,
+   *  sort.fields[0].field). */
+  public final String path;
+
+  /** The specific reason for the failure (e.g., "expected
+   *  int but got string"). */
+  public final String reason;
+
+  /** Creates this. */
+  public RequestFailedException(Request r, String param, String path, String reason) {
+    super(path + ": " + reason);
+    this.request = r;
+    this.param = param;
+    this.path = path;
+    this.reason = reason;
+  }
+
+  /** Creates this from another {@code
+   *  RequestFailedException}, adding further details. */
+  public RequestFailedException(RequestFailedException other, String details) {
+    super(other.reason + ": " + details);
+    this.reason = other.reason + ": " + details;
+    this.request = other.request;
+    this.param = other.param;
+    this.path = other.path;
+  }
+}

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/StringType.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/StringType.java?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/StringType.java (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/StringType.java Sun Mar 16 18:11:07 2014
@@ -0,0 +1,33 @@
+package org.apache.lucene.server.params;
+
+/*
+ * 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.
+ */
+
+/** Type for a string. */
+public class StringType extends Type {
+
+  /** Sole constructor. */
+  public StringType() {
+  }
+
+  @Override
+  public void validate(Object o) {
+    if (!(o instanceof String)) {
+      throw new IllegalArgumentException("expected String but got " + o.getClass());
+    }
+  }
+}

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/StructType.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/StructType.java?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/StructType.java (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/StructType.java Sun Mar 16 18:11:07 2014
@@ -0,0 +1,62 @@
+package org.apache.lucene.server.params;
+
+/*
+ * 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.
+ */
+
+import java.util.HashMap;
+import java.util.Map;
+
+import net.minidev.json.JSONObject;
+
+/** Type for a structure containing named typed parameters. */
+public class StructType extends Type {
+
+  /** Parameters contained in this struct. */
+  public final Map<String,Param> params = new HashMap<String,Param>();
+
+  /** Sole constructor. */
+  public StructType(Param... params) {
+    boolean sawPoly = false;
+    for(Param p : params) {
+      if (this.params.containsKey(p.name)) {
+        throw new IllegalArgumentException("param name \"" + p.name + "\" appears more than once");
+      }
+      if (p.type instanceof PolyType) {
+        if (sawPoly) {
+          throw new IllegalArgumentException("only one PolyType per struct");
+        }
+        sawPoly = true;
+      }
+      this.params.put(p.name, p);
+    }
+  }
+
+  /** Add another parameter. */
+  public void addParam(Param param) {
+    if (params.containsKey(param.name)) {
+      throw new IllegalArgumentException("param name \"" + param.name + "\" already exists");
+    }
+    params.put(param.name, param);
+  }
+
+  @Override
+  public void validate(Object _o) {
+    if (!(_o instanceof JSONObject)) {
+      throw new IllegalArgumentException("expected struct but got " + _o.getClass());
+    }
+  }
+}

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/Type.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/Type.java?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/Type.java (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/Type.java Sun Mar 16 18:11:07 2014
@@ -0,0 +1,31 @@
+package org.apache.lucene.server.params;
+
+/*
+ * 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.
+ */
+
+/** Base class for al types. */
+public abstract class Type {
+
+  /** Sole constructor. */
+  protected Type() {
+  }
+
+  /** Confirms that the object is a valid item matching the
+   *  type; if it is not, throw {@code
+   *  IllegalArgumentException}. */
+  public abstract void validate(Object o);
+}

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/WrapType.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/WrapType.java?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/WrapType.java (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/WrapType.java Sun Mar 16 18:11:07 2014
@@ -0,0 +1,46 @@
+package org.apache.lucene.server.params;
+
+/*
+ * 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.
+ */
+
+/** Wraps another type; use this to break type recursion. */
+public class WrapType extends Type {
+  private Type other;
+
+  /** Sole constructor. */
+  public WrapType() {
+  }
+
+  /** Set our wrapped type. */
+  public void set(Type other) {
+    if (this.other != null) {
+      throw new IllegalStateException("already set");
+    }
+
+    this.other = other;
+  }
+
+  @Override
+  public void validate(Object o) {
+    other.validate(o);
+  }
+
+  /** Return the type we wrap. */
+  public Type getWrappedType() {
+    return other;
+  }
+}

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/package.html
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/package.html?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/package.html (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/params/package.html Sun Mar 16 18:11:07 2014
@@ -0,0 +1,25 @@
+<!--
+ 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.
+-->
+<html>
+  <head>
+    <title>Lucene Server: params</title>
+  </head>
+  <body>
+    <h1>Lucene Server: params</h1>
+    Classes to handle matching incoming JSON requests against declared expected types.
+  </body>
+</html>

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/plugins/Plugin.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/plugins/Plugin.java?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/plugins/Plugin.java (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/plugins/Plugin.java Sun Mar 16 18:11:07 2014
@@ -0,0 +1,32 @@
+package org.apache.lucene.server.plugins;
+
+/*
+ * 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.
+ */
+
+/** Base class for all plugins. */
+public abstract class Plugin {
+
+  /** Sole constructor. */
+  public Plugin() {
+  }
+
+  /** Name of this plugin. */
+  public abstract String getName();
+
+  /** Documentation for this plugin (English). */
+  public abstract String getTopDoc();
+}

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/plugins/package.html
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/plugins/package.html?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/plugins/package.html (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/java/org/apache/lucene/server/plugins/package.html Sun Mar 16 18:11:07 2014
@@ -0,0 +1,25 @@
+<!--
+ 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.
+-->
+<html>
+  <head>
+    <title>Lucene Server: plugins</title>
+  </head>
+  <body>
+    <h1>Lucene Server: plugins</h1>
+    Classes to handle plugins.
+  </body>
+</html>

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/java/overview.html
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/java/overview.html?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/java/overview.html (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/java/overview.html Sun Mar 16 18:11:07 2014
@@ -0,0 +1,26 @@
+<!--
+ 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.
+-->
+<html>
+  <head>
+    <title>
+      server
+    </title>
+  </head>
+  <body>
+  Demo search server
+  </body>
+</html>

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/resources/META-INF/services/org.apache.lucene.codecs.Codec
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/resources/META-INF/services/org.apache.lucene.codecs.Codec?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/resources/META-INF/services/org.apache.lucene.codecs.Codec (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/resources/META-INF/services/org.apache.lucene.codecs.Codec Sun Mar 16 18:11:07 2014
@@ -0,0 +1,16 @@
+#  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.
+
+org.apache.lucene.server.ServerCodec

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/scripts/another-post.py
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/scripts/another-post.py?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/scripts/another-post.py (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/scripts/another-post.py Sun Mar 16 18:11:07 2014
@@ -0,0 +1,57 @@
+import json
+import threading
+import http.client
+import urllib.request, urllib.error, urllib.parse
+import subprocess
+import shutil
+
+shutil.rmtree('index')
+shutil.rmtree('taxonomy')
+
+def send(command, data):
+  u = urllib.request.urlopen('http://localhost:4000/%s' % command, data)
+  print('did open')
+  print(u.read())
+
+def readServerOutput(p, startupEvent, failureEvent):
+  while True:
+    l = p.stdout.readline()
+    if l == '':
+      failureEvent.set()
+      startupEvent.set()
+      raise RuntimeError('Server failed to start')
+    if l.find('listening on port 4000') != -1:
+      startupEvent.set()
+    print('SVR: %s' % l.rstrip())
+
+startupEvent = threading.Event()
+failureEvent = threading.Event()
+
+p = subprocess.Popen('java -cp /home/mike/.ivy2/cache/net.minidev/json-smart/jars/json-smart-1.1.1.jar:/home/mike/.ivy2/cache/org.apache.lucene/lucene-facet/jars/lucene-facet-4.1-SNAPSHOT.jar:/home/mike/.ivy2/cache/org.apache.lucene/lucene-highlighter/jars/lucene-highlighter-4.1-SNAPSHOT.jar:/home/mike/.ivy2/cache/io.netty/netty/bundles/netty-3.5.11.Final.jar:/home/mike/.ivy2/cache/org.apache.lucene/lucene-core/jars/lucene-core-4.1-SNAPSHOT.jar:/home/mike/.ivy2/cache/org.apache.lucene/lucene-analyzers-common/jars/lucene-analyzers-common-4.1-SNAPSHOT.jar:/l/server/build/luceneserver.jar org.apache.lucene.server.Server', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+
+t = threading.Thread(target=readServerOutput, args=(p, startupEvent, failureEvent))
+t.setDaemon(True)
+t.start()
+
+startupEvent.wait()
+if failureEvent.isSet():
+  sys.exit(0)
+
+print('Server started...')
+
+fields = {'date': {'type': 'atom',
+                   'index': True,
+                   'store': True}}
+
+send('registerFields', json.dumps(fields))
+
+print('Done register')
+
+h = http.client.HTTPConnection('localhost', 4000)
+h.request('POST', '/addDocument')
+h.endheaders()
+
+for i in range(100):
+  h.send('{"date": "foobar"}')
+r = h.getresponse()
+print(r.read())

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/scripts/loadTest2.py
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/scripts/loadTest2.py?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/scripts/loadTest2.py (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/scripts/loadTest2.py Sun Mar 16 18:11:07 2014
@@ -0,0 +1,122 @@
+import time
+import threading
+import sys
+import json
+import asyncore
+import socket
+import random
+
+class RollingStats:
+
+  def __init__(self, count):
+    self.buffer = [0] * count
+    self.sum = 0
+    self.upto = 0
+
+  def add(self, value):
+    if value < 0:
+      raise RuntimeError('values should be positive')
+    idx = self.upto % len(self.buffer)
+    self.sum += value - self.buffer[idx]
+    self.buffer[idx] = value
+    self.upto += 1
+
+  def get(self):
+    if self.upto == 0:
+      return -1.0
+    else:
+      if self.upto < len(self.buffer):
+        v = float(self.sum)/self.upto
+      else:
+        v = float(self.sum)/len(self.buffer)
+      # Don't let roundoff error manifest as -0.0:
+      return max(0.0, v)
+
+class HTTPClient(asyncore.dispatcher):
+
+  def __init__(self, host, port, path, data, doneCallback):
+    self.startTime = time.time()
+    asyncore.dispatcher.__init__(self)
+    self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+    l = ['POST %s HTTP/1.0' % path]
+    l.append('Content-Length: %d' % len(data))
+    self.buffer = '\r\n'.join(l) + '\r\n\r\n' + data
+    self.result = []
+    self.doneCallback = doneCallback
+    self.connect((host, port))
+
+  def handle_connect(self):
+    pass
+    
+  def handle_close(self):
+    self.close()
+    self.doneCallback(time.time() - self.startTime)
+      
+  def handle_read(self):
+    self.result.append(self.recv(8192))
+    print('read %s' % self.result[-1])
+    
+  def writable(self):
+    return (len(self.buffer) > 0)
+
+  def handle_write(self):
+    sent = self.send(self.buffer)
+    self.buffer = self.buffer[sent:]
+
+class Stats:
+
+  def __init__(self):
+    self.actualQPSStats = RollingStats(5)
+    self.totalTimeStats = RollingStats(100)
+    self.lastIntSec = None
+    self.startTime = time.time()
+
+  def queryDone(self, t):
+    self.totalTimeStats.add(t)
+    intSec = int(time.time()-self.startTime)
+    if intSec != self.lastIntSec:
+      if self.lastIntSec is not None:
+        self.actualQPSStats.add(self.queriesThisSec)
+      self.queriesThisSec = 0
+      self.lastIntSec = intSec
+    self.queriesThisSec += 1
+
+def loop():
+  while True:
+    asyncore.loop()
+    
+def main(targetQPS):
+
+  t = threading.Thread(target=loop, args=())
+  t.setDaemon(True)
+  t.start()
+
+  r = random.Random(0)
+
+  queryText = '1'
+  query = {'indexName': 'wiki',
+           'queryText': queryText,
+           'facets': [{'path': 'dateFacet', 'topN': 10}]}
+  data = json.dumps(query)
+
+  targetTime = time.time()
+
+  stats = Stats()
+  lastPrintTime = time.time()
+  while True:
+
+    targetTime += r.expovariate(targetQPS)
+    now = time.time()
+    if now - lastPrintTime > 1.0:
+      print('%.1f QPS, %.1f msec' % (stats.actualQPSStats.get(), 1000*stats.totalTimeStats.get()))
+      lastPrintTime = time.time()
+      
+    pause = targetTime - time.time()
+    if pause > 0:
+      time.sleep(pause)
+        
+    HTTPClient('10.17.4.91', 4001, '/search', data, stats.queryDone)
+    
+
+if __name__ == '__main__':
+  main(int(sys.argv[1]))

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/scripts/post.py
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/scripts/post.py?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/scripts/post.py (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/scripts/post.py Sun Mar 16 18:11:07 2014
@@ -0,0 +1,6 @@
+import urllib2
+
+r = urllib2.urlopen('http://localhost:4000/updateDocument', 'here is some data')
+print r.read()
+
+

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/MockPlugin-hello.txt
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/MockPlugin-hello.txt?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/MockPlugin-hello.txt (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/MockPlugin-hello.txt Sun Mar 16 18:11:07 2014
@@ -0,0 +1 @@
+hello world!

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/MockPlugin-lucene-server-plugin.properties
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/MockPlugin-lucene-server-plugin.properties?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/MockPlugin-lucene-server-plugin.properties (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/MockPlugin-lucene-server-plugin.properties Sun Mar 16 18:11:07 2014
@@ -0,0 +1,16 @@
+#  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.
+
+class: org.apache.lucene.server.MockPlugin

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/MockPlugin.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/MockPlugin.java?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/MockPlugin.java (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/MockPlugin.java Sun Mar 16 18:11:07 2014
@@ -0,0 +1,63 @@
+package org.apache.lucene.server;
+
+/*
+ * 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.
+ */
+
+import org.apache.lucene.server.handlers.Handler;
+import org.apache.lucene.server.params.IntType;
+import org.apache.lucene.server.params.Param;
+import org.apache.lucene.server.params.Request;
+import org.apache.lucene.server.params.StructType;
+import org.apache.lucene.server.plugins.Plugin;
+import net.minidev.json.JSONObject;
+
+public class MockPlugin extends Plugin {
+
+  @Override
+  public String getTopDoc() {
+    return "Adds foobar param to AddDocument";
+  }
+
+  @Override
+  public String getName() {
+    return "Mock";
+  }
+
+  private static class AddDocumentPreHandler implements PreHandle {
+
+    @Override
+    public void invoke(Request r) {
+      Request r2 = r.getStruct("fields");
+      if (r2.hasParam("mockFoobar")) {
+        int x = r2.getInt("mockFoobar");
+        JSONObject params = r2.getRawParams(); 
+        params.put("intfield", 2*x);
+      }
+    }
+  }
+
+  public MockPlugin(GlobalState state) {
+    Handler addDocHandler = state.getHandler("addDocument");
+
+    // Register our pre-processor in addDocument:
+    addDocHandler.addPreHandle(new AddDocumentPreHandler());
+
+    StructType fieldsType = ((StructType) addDocHandler.getType().params.get("fields").type);
+
+    fieldsType.addParam(new Param("mockFoobar", "Testing adding a new silly param", new IntType()));
+  }
+}

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/ServerBaseTestCase.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/ServerBaseTestCase.java?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/ServerBaseTestCase.java (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/ServerBaseTestCase.java Sun Mar 16 18:11:07 2014
@@ -0,0 +1,572 @@
+package org.apache.lucene.server;
+
+/*
+ * 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.
+ */
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ConnectException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.TestUtil;
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.ObjectWriter;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import net.minidev.json.JSONArray;
+import net.minidev.json.JSONObject;
+import net.minidev.json.JSONStyle;
+import net.minidev.json.JSONStyleIdent;
+import net.minidev.json.JSONStyleIdent;
+import net.minidev.json.JSONValue;
+import net.minidev.json.parser.JSONParser;
+import net.minidev.json.parser.ParseException;
+
+public abstract class ServerBaseTestCase extends LuceneTestCase {
+
+  private static Thread serverThread;
+  static int port;
+
+  /** Current index name; we auto-insert this to outgoing
+   *  commands that need it. */
+
+  protected static String curIndexName = "index";
+  protected static boolean useDefaultIndex = true;
+  
+  protected static File STATE_DIR;
+
+  /** Last result from the server; tests can access this to
+   *  check results. */
+  protected static JSONObject lastResult;
+  
+  /** We record the last indexGen we saw return from the
+   *  server, and then insert that for search command if no
+   *  searcher is already specified.  This avoids a common
+   *  test bug of forgetting to specify which indexGen to
+   *  search. */
+  private static long lastIndexGen = -1;
+
+  @BeforeClass
+  public static void beforeClassServerBase() throws Exception {
+    File dir = TestUtil.getTempDir("ServerBase");
+    STATE_DIR = new File(dir, "state");
+  }
+  
+  @AfterClass
+  public static void afterClassServerBase() throws Exception {
+    // who sets this? netty? what a piece of crap
+    System.clearProperty("sun.nio.ch.bugLevel");
+    STATE_DIR = null;
+    lastResult = null;
+  }
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    lastIndexGen = -1;
+    if (useDefaultIndex) {
+      curIndexName = "index";
+
+      // Some tests bounce the server, so we need to restart
+      // the default "index" index for those tests that expect
+      // it to be running:
+      send("startIndex");
+    }
+  }
+
+  protected long addDocument(String json) throws Exception {
+    JSONObject o = send("addDocument", json);
+    return ((Number) o.get("indexGen")).longValue();
+  }
+
+  protected static void installPlugin(File sourceFile) throws IOException {
+    ZipFile zipFile = new ZipFile(sourceFile);
+    
+    Enumeration<? extends ZipEntry> entries = zipFile.entries();
+
+    File pluginsDir = new File(STATE_DIR, "plugins");
+    if (!pluginsDir.exists()) {
+      pluginsDir.mkdirs();
+    }
+
+    while (entries.hasMoreElements()) {
+      ZipEntry entry = entries.nextElement();
+      
+      InputStream in = zipFile.getInputStream(entry);
+      File targetFile = new File(pluginsDir, entry.getName());
+      if (entry.isDirectory()) {
+        // allow unzipping with directory structure
+        targetFile.mkdirs();
+      } else {
+        if (targetFile.getParentFile()!=null) {
+          targetFile.getParentFile().mkdirs();   
+        }
+        OutputStream out = new BufferedOutputStream(new FileOutputStream(targetFile));
+        
+        byte[] buffer = new byte[8192];
+        int len;
+        while((len = in.read(buffer)) >= 0) {
+          out.write(buffer, 0, len);
+        }
+        
+        in.close();
+        out.close();
+      }
+    }
+    
+    zipFile.close();
+  }
+
+  protected static void put(JSONObject o, String key, String value) throws ParseException {
+    o.put(key, JSONValue.parseWithException(value));
+  }
+    
+  protected static void startServer() throws Exception {
+    final CountDownLatch ready = new CountDownLatch(1);
+    final Exception[] exc = new Exception[1];
+    final AtomicReference<Server> theServer = new AtomicReference<Server>();
+    serverThread = new Thread() {
+        @Override
+        public void run() {
+          try {
+            Server s = new Server(STATE_DIR);
+            theServer.set(s);
+            s.run(0, 1, ready);
+          } catch (Exception e) {
+            exc[0] = e;
+            ready.countDown();
+          }
+        }
+      };
+    serverThread.start();
+    if (!ready.await(2, TimeUnit.SECONDS)) {
+      throw new IllegalStateException("server took more than 2 seconds to start");
+    }
+    if (exc[0] != null) {
+      throw exc[0];
+    }
+    ServerBaseTestCase.port = theServer.get().actualPort;
+  }
+
+  protected static void createAndStartIndex() throws Exception {
+    TestUtil.rmDir(new File(curIndexName));
+    send("createIndex", "{indexName: " + curIndexName + ", rootDir: " + curIndexName + "}");
+    // Wait at most 1 msec for a searcher to reopen; this
+    // value is too low for a production site but for
+    // testing we want to minimize sleep time:
+    send("liveSettings", "{indexName: " + curIndexName + ", minRefreshSec: 0.001}");
+    send("startIndex", "{indexName: " + curIndexName + "}");
+  }
+
+  protected static void shutdownServer() throws Exception {
+    send("shutdown");
+    if (serverThread != null) {
+      serverThread.join();
+      serverThread = null;
+    }
+    lastIndexGen = -1;
+  }
+
+  protected static void deleteAllDocs() throws Exception {
+    if (VERBOSE) {
+      System.out.println("TEST: deleteAllDocs");
+    }
+    send("deleteAllDocuments", "{indexName: " + curIndexName + "}");
+  }
+
+  protected static void commit() throws Exception {
+    send("commit", "{indexName: " + curIndexName + "}");
+  }
+
+  /** Send a no-args command, or a command taking just
+   *  indexName which is automatically added (e.g., commit,
+   *  closeIndex, startIndex). */
+  protected static JSONObject send(String command) throws Exception {
+    if (command.equals("startIndex")) {
+      // We do this so tests that index a doc and then need
+      // to search it, don't wait very long for the new
+      // searcher:
+      send("liveSettings", "{minRefreshSec: 0.001}");
+    }
+    return _send(command, "{}");
+  }
+
+  protected static JSONObject send(String command, String args) throws Exception {
+    if (args.equals("{}")) {
+      throw new IllegalArgumentException("don't pass empty args");
+    }
+    JSONObject o;
+    try {
+      o = (JSONObject) new JSONParser(JSONParser.MODE_PERMISSIVE & ~(JSONParser.ACCEPT_TAILLING_DATA)).parse(args);
+    } catch (ParseException pe) {
+      // NOTE: don't send pe as the cause; it adds lots of
+      // unhelpful noise because the message usually states
+      // what's wrong very well:
+      throw new IllegalArgumentException("test bug: failed to parse json args \"" + args + "\": " + pe.getMessage());
+    }
+    return send(command, o);
+  }
+
+  private static JSONObject _send(String command, String args) throws Exception {
+    JSONObject o;
+    try {
+      o = (JSONObject) new JSONParser(JSONParser.MODE_PERMISSIVE & ~(JSONParser.ACCEPT_TAILLING_DATA)).parse(args);
+    } catch (ParseException pe) {
+      // NOTE: don't send pe as the cause; it adds lots of
+      // unhelpful noise because the message usually states
+      // what's wrong very well:
+      throw new IllegalArgumentException("test bug: failed to parse json args \"" + args + "\": " + pe.getMessage());
+    }
+    return send(command, o);
+  }
+
+  private static boolean requiresIndexName(String command) {
+    if (command.equals("shutdown")) {
+      return false;
+    }
+    return true;
+  }
+
+  protected static JSONObject send(String command, JSONObject args) throws Exception {
+    // Auto-insert indexName:
+    if (curIndexName != null && requiresIndexName(command) && args.get("indexName") == null) {
+      if (VERBOSE) {
+        System.out.println("NOTE: ServerBaseTestCase: now add current indexName: " + curIndexName);
+      }
+      args.put("indexName", curIndexName);
+    }
+
+    if (command.equals("search") && args.containsKey("searcher") == false && lastIndexGen != -1) {
+      if (VERBOSE) {
+        System.out.println("\nNOTE: ServerBaseTestCase: inserting 'searcher: {indexGen: " + lastIndexGen + "}' into search request");
+      }
+      JSONObject o = new JSONObject();
+      o.put("indexGen", lastIndexGen);
+      args.put("searcher", o);
+    }
+
+    if (VERBOSE) {
+      System.out.println("\nNOTE: ServerBaseTestCase: sendRaw command=" + command + " args:\n" + args.toJSONString(new JSONStyleIdent()));
+    }
+
+    lastResult = sendRaw(command, args.toJSONString(JSONStyle.NO_COMPRESS));
+
+    if (VERBOSE) {
+      System.out.println("NOTE: ServerBaseTestCase: server response:\n" + lastResult.toJSONString(new JSONStyleIdent()));
+    }
+
+    if (lastResult.containsKey("indexGen")) {
+      lastIndexGen = getLong(lastResult, "indexGen");
+      if (VERBOSE) {
+        System.out.println("NOTE: ServerBaseTestCase: record lastIndexGen=" + lastIndexGen);
+      }
+    }
+
+    return lastResult;
+  }
+
+  protected static JSONObject sendRaw(String command, String body) throws Exception {
+    byte[] bytes = body.getBytes("UTF-8");
+    HttpURLConnection c = (HttpURLConnection) new URL("http://localhost:" + port + "/" + command).openConnection();
+    c.setUseCaches(false);
+    c.setDoOutput(true);
+    c.setRequestMethod("POST");
+    c.setRequestProperty("Content-Length", ""+bytes.length);
+    c.setRequestProperty("Charset", "UTF-8");
+    try {
+      c.getOutputStream().write(bytes);
+    } catch (ConnectException ce) {
+      System.out.println("FAILED port=" + port + ":");
+      ce.printStackTrace(System.out);
+      throw ce;
+    }
+    // c.connect()
+    int code = c.getResponseCode();
+    int size = c.getContentLength();
+    bytes = new byte[size];
+    if (code == 200) {
+      InputStream is = c.getInputStream();
+      is.read(bytes);
+      c.disconnect();
+      return (JSONObject) JSONValue.parseStrict(new String(bytes, "UTF-8"));
+    } else {
+      InputStream is = c.getErrorStream();
+      is.read(bytes);
+      c.disconnect();
+      throw new IOException("Server error:\n" + new String(bytes, "UTF-8"));
+    }
+  }
+
+  protected static void copyFile(File source, File dest) throws IOException {
+    InputStream is = null;
+    OutputStream os = null;
+    try {
+      is = new FileInputStream(source);
+      os = new FileOutputStream(dest);
+      byte[] buffer = new byte[1024];
+      int length;
+      while ((length = is.read(buffer)) > 0) {
+        os.write(buffer, 0, length);
+      }
+    } finally {
+      is.close();
+      os.close();
+    }
+  }
+
+  protected String prettyPrint(JSONObject o) throws Exception {
+    return o.toJSONString(new JSONStyleIdent());
+  }
+
+  protected static String httpLoad(String path) throws Exception {
+    HttpURLConnection c = (HttpURLConnection) new URL("http://localhost:" + port + "/" + path).openConnection();
+    c.setUseCaches(false);
+    c.setDoOutput(true);
+    c.setRequestMethod("GET");
+    // c.connect()
+    int code = c.getResponseCode();
+    int size = c.getContentLength();
+    byte[] bytes = new byte[size];
+    if (code == 200) {
+      InputStream is = c.getInputStream();
+      is.read(bytes);
+      c.disconnect();
+      return new String(bytes, "UTF-8");
+    } else {
+      InputStream is = c.getErrorStream();
+      is.read(bytes);
+      c.disconnect();
+      throw new IOException("Server error:\n" + new String(bytes, "UTF-8"));
+    }
+  }
+
+  protected static JSONObject sendChunked(String body, String request) throws Exception {
+    HttpURLConnection c = (HttpURLConnection) new URL("http://localhost:" + port + "/" + request).openConnection();
+    c.setUseCaches(false);
+    c.setDoOutput(true);
+    c.setChunkedStreamingMode(256);
+    c.setRequestMethod("POST");
+    c.setRequestProperty("Charset", "UTF-8");
+    byte[] bytes = body.getBytes("UTF-8");
+    c.getOutputStream().write(bytes);
+    // c.connect()
+    int code = c.getResponseCode();
+    int size = c.getContentLength();
+    if (code == 200) {
+      InputStream is = c.getInputStream();
+      bytes = new byte[size];
+      is.read(bytes);
+      c.disconnect();
+      return (JSONObject) JSONValue.parseStrict(new String(bytes, "UTF-8"));
+    } else {
+      InputStream is = c.getErrorStream();
+      is.read(bytes);
+      c.disconnect();
+      throw new IOException("Server error:\n" + new String(bytes, "UTF-8"));
+    }
+  }
+
+  /** Simple xpath-like utility method to jump down and grab
+   *  something out of the JSON response. */
+  protected static Object get(Object o, String path) {
+    int upto = 0;
+    int tokStart = 0;
+    boolean inArrayIndex = false;
+    while(upto < path.length()) {       
+      char ch = path.charAt(upto++);
+      if (inArrayIndex) {
+        if (ch == ']') {
+          int index = Integer.parseInt(path.substring(tokStart, upto-1));
+          o = ((JSONArray) o).get(index);
+          inArrayIndex = false;
+          tokStart = upto;
+        }
+      } else if (ch == '.' || ch == '[') {
+        String name = path.substring(tokStart, upto-1);
+        if (name.length() != 0) {
+          o = ((JSONObject) o).get(name);
+          if (o == null) {
+            // Likely a test bug: try to help out:
+            throw new IllegalArgumentException("path " + path.substring(0, tokStart-1) + " does not have member ." + name);
+          }
+        }
+        tokStart = upto;
+        if (ch == '[') {
+          inArrayIndex = true;
+        }
+      }
+    }
+
+    String name = path.substring(tokStart, upto);
+    if (name.length() > 0) {
+      if (o instanceof JSONArray && name.equals("length")) {
+        o = new Integer(((JSONArray) o).size());
+      } else {
+        o = ((JSONObject) o).get(name);
+        if (o == null) {
+          // Likely a test bug: try to help out:
+          throw new IllegalArgumentException("path " + path.substring(0, tokStart) + " does not have member ." + name);
+        }
+      }
+    }
+    return o;
+  }
+
+  protected boolean hasParam(Object o, String path) {
+    try {
+      get(o, path);
+      return true;
+    } catch (IllegalArgumentException iae) {
+      return false;
+    }
+  }
+
+  protected boolean hasParam(String path) {
+    return hasParam(lastResult, path);
+  }
+
+  protected static String getString(Object o, String path) {
+    return (String) get(o, path);
+  }
+
+  protected static String getString(String path) {
+    return getString(lastResult, path);
+  }
+
+  protected static int getInt(Object o, String path) {
+    return ((Number) get(o, path)).intValue();
+  }
+
+  protected static int getInt(String path) {
+    return getInt(lastResult, path);
+  }
+
+  protected static boolean getBoolean(Object o, String path) {
+    return ((Boolean) get(o, path)).booleanValue();
+  }
+
+  protected static boolean getBoolean(String path) {
+    return getBoolean(lastResult, path);
+  }
+
+  protected static long getLong(Object o, String path) {
+    return ((Number) get(o, path)).longValue();
+  }
+
+  protected static long getLong(String path) {
+    return getLong(lastResult, path);
+  }
+
+  protected static float getFloat(Object o, String path) {
+    return ((Number) get(o, path)).floatValue();
+  }
+
+  protected static float getFloat(String path) {
+    return getFloat(lastResult, path);
+  }
+
+  protected static JSONObject getObject(Object o, String path) {
+    return (JSONObject) get(o, path);
+  }
+
+  protected static JSONObject getObject(String path) {
+    return getObject(lastResult, path);
+  }
+
+  protected static JSONArray getArray(Object o, String path) {
+    return (JSONArray) get(o, path);
+  }
+
+  protected static JSONArray getArray(String path) {
+    return getArray(lastResult, path);
+  }
+
+  protected static JSONArray getArray(JSONArray o, int index) {
+    return (JSONArray) o.get(index);
+  }
+
+  /** Renders one hilited field (multiple passages) value
+   * with <b>...</b> tags, and ... separating the passages. */ 
+  protected String renderHighlight(JSONArray hit) {
+    StringBuilder sb = new StringBuilder();
+    for(Object o : hit) {
+      if (sb.length() != 0) {
+        sb.append("...");
+      }
+      sb.append(renderSingleHighlight(getArray(o, "parts")));
+    }
+
+    return sb.toString();
+  }
+
+  /** Renders a single passage with <b>...</b> tags. */
+  protected String renderSingleHighlight(JSONArray passage) {
+    StringBuilder sb = new StringBuilder();
+    for(Object o2 : passage) {
+      if (o2 instanceof String) {
+        sb.append((String) o2);
+      } else {
+        JSONObject obj = (JSONObject) o2;
+        sb.append("<b>");
+        sb.append(obj.get("text"));
+        sb.append("</b>");
+      }
+    }
+
+    return sb.toString();
+  }
+
+  /** Sends the command + args, expecting a failure such
+   *  that all fragments occur in the failure message
+   *  string.  Use this to verify a failure case is hitting
+   *  the right error messages back to the user. */
+  protected void assertFailsWith(String command, JSONObject args, String... fragments) throws Exception {
+    assertFailsWith(command, args.toString(), fragments);
+  }
+
+  /** Sends the command + args, expecting a failure such
+   *  that all fragments occur in the failure message
+   *  string.  Use this to verify a failure case is hitting
+   *  the right error messages back to the user. */
+  protected void assertFailsWith(String command, String args, String... fragments) throws Exception {
+    try {
+      send(command, args);
+      fail("did not hit expected exception");
+    } catch (IOException ioe) {
+      for(String fragment : fragments) {
+        if (ioe.getMessage().contains(fragment) == false) {
+          fail("expected: " + fragment + "\nactual: \"" + ioe.getMessage());
+        }
+      }
+    }
+  }
+}
+

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/TermFreqPayload.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/TermFreqPayload.java?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/TermFreqPayload.java (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/TermFreqPayload.java Sun Mar 16 18:11:07 2014
@@ -0,0 +1,36 @@
+package org.apache.lucene.server;
+
+/*
+ * 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.
+ */
+
+import org.apache.lucene.util.BytesRef;
+
+public final class TermFreqPayload {
+  public final BytesRef term;
+  public final long v;
+  public final BytesRef payload;
+
+  public TermFreqPayload(String term, long v, BytesRef payload) {
+    this(new BytesRef(term), v, payload);
+  }
+  
+  public TermFreqPayload(BytesRef term, long v, BytesRef payload) {
+    this.term = term;
+    this.v = v;
+    this.payload = payload;
+  }
+}
\ No newline at end of file

Added: lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/TestAddDocuments.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/TestAddDocuments.java?rev=1578133&view=auto
==============================================================================
--- lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/TestAddDocuments.java (added)
+++ lucene/dev/branches/lucene5376_2/lucene/server/src/test/org/apache/lucene/server/TestAddDocuments.java Sun Mar 16 18:11:07 2014
@@ -0,0 +1,123 @@
+package org.apache.lucene.server;
+
+/*
+ * 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.
+ */
+
+import java.util.Locale;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import net.minidev.json.JSONArray;
+import net.minidev.json.JSONObject;
+
+public class TestAddDocuments extends ServerBaseTestCase {
+  
+  @BeforeClass
+  public static void initClass() throws Exception {
+    useDefaultIndex = true;
+    curIndexName = "index";
+    startServer();
+    createAndStartIndex();
+    registerFields();
+    //commit();
+  }
+
+  @AfterClass
+  public static void fini() throws Exception {
+    shutdownServer();
+  }
+
+  private static void registerFields() throws Exception {
+    send("registerFields", "{fields: {docType: {type: atom}, name: {type: atom}, country: {type: atom}, skill: {type: atom}}}");
+  }
+
+  private JSONObject getResume(String name, String country) {
+    JSONObject o = new JSONObject();
+    o.put("docType", "resume");
+    o.put("name", name);
+    o.put("country", country);
+    JSONObject o2 = new JSONObject();
+    o2.put("fields", o);
+    return o2;
+  }
+
+  private JSONObject getJob(String skill, int year) {
+    JSONObject o = new JSONObject();
+    o.put("skill", skill);
+    //o.put("year", year);
+    JSONObject o2 = new JSONObject();
+    o2.put("fields", o);
+    return o2;
+  }
+
+  public void testAddDocuments() throws Exception {
+    deleteAllDocs();
+
+    JSONObject o = new JSONObject();
+    o.put("indexName", "index");
+    o.put("parent", getResume("Lisa", "United Kingdom"));
+    JSONArray arr = new JSONArray();
+    o.put("children", arr);
+    arr.add(getJob("java", 2007));
+    arr.add(getJob("python", 2010));
+    JSONObject result = send("addDocuments", o);
+    long indexGen = ((Number) result.get("indexGen")).longValue();    
+
+    // search on parent:
+    result = send("search", String.format(Locale.ROOT, "{queryText: 'name:Lisa', searcher: {indexGen: %d}}", indexGen));
+    assertEquals(1, result.get("totalHits"));
+
+    // search on child:
+    result = send("search", String.format(Locale.ROOT, "{queryText: 'skill:python', searcher: {indexGen: %d}}", indexGen));
+    assertEquals(1, result.get("totalHits"));
+  }
+
+  public void testBulkAddDocuments() throws Exception {
+    deleteAllDocs();
+    StringBuilder sb = new StringBuilder();
+    sb.append("{\"indexName\": \"index\", \"documents\": [");
+    for(int i=0;i<100;i++) {
+      JSONObject o = new JSONObject();
+      o.put("parent", getResume("Lisa", "United Kingdom"));
+      JSONArray arr = new JSONArray();
+      o.put("children", arr);
+      arr.add(getJob("java", 2007));
+      arr.add(getJob("python", 2010));
+      if (i > 0) {
+        sb.append(',');
+      }
+      sb.append(o.toString());
+    }
+    sb.append("]}");
+
+    String s = sb.toString();
+
+    JSONObject result = sendChunked(s, "bulkAddDocuments");
+    assertEquals(100, result.get("indexedDocumentBlockCount"));
+    long indexGen = ((Number) result.get("indexGen")).longValue();
+
+    // search on parent:
+    result = send("search", String.format(Locale.ROOT, "{queryText: 'name:Lisa', searcher: {indexGen: %d}}", indexGen));
+    assertEquals(100, result.get("totalHits"));
+
+    // search on child:
+    result = send("search", String.format(Locale.ROOT, "{queryText: 'skill:python', searcher: {indexGen: %d}}", indexGen));
+    assertEquals(100, result.get("totalHits"));
+  }
+
+  // TODO: test block join/grouping once they are impl'd!
+}