You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by no...@apache.org on 2015/02/14 20:21:15 UTC

svn commit: r1659847 - in /lucene/dev/branches/branch_5x: ./ solr/ solr/solrj/ solr/solrj/src/java/org/apache/solr/client/solrj/beans/ solr/solrj/src/java/org/apache/solr/client/solrj/util/ solr/solrj/src/test/org/apache/solr/client/solrj/beans/

Author: noble
Date: Sat Feb 14 19:21:14 2015
New Revision: 1659847

URL: http://svn.apache.org/r1659847
Log:
SOLR-1945: Add support for child docs in DocumentObjectBinder

Modified:
    lucene/dev/branches/branch_5x/   (props changed)
    lucene/dev/branches/branch_5x/solr/   (props changed)
    lucene/dev/branches/branch_5x/solr/CHANGES.txt   (contents, props changed)
    lucene/dev/branches/branch_5x/solr/solrj/   (props changed)
    lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/beans/DocumentObjectBinder.java
    lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/beans/Field.java
    lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
    lucene/dev/branches/branch_5x/solr/solrj/src/test/org/apache/solr/client/solrj/beans/TestDocumentObjectBinder.java

Modified: lucene/dev/branches/branch_5x/solr/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/CHANGES.txt?rev=1659847&r1=1659846&r2=1659847&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/CHANGES.txt (original)
+++ lucene/dev/branches/branch_5x/solr/CHANGES.txt Sat Feb 14 19:21:14 2015
@@ -66,6 +66,8 @@ New Features
 * SOLR-6832: Queries be served locally rather than being forwarded to another replica.
   (Sachin Goyal, Timothy Potter)
 
+* SOLR-1945 : Add support for child docs in DocumentObjectBinder (Noble Paul, Mark Miller)
+
 Bug Fixes
 ----------------------
 

Modified: lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/beans/DocumentObjectBinder.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/beans/DocumentObjectBinder.java?rev=1659847&r1=1659846&r2=1659847&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/beans/DocumentObjectBinder.java (original)
+++ lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/beans/DocumentObjectBinder.java Sat Feb 14 19:21:14 2015
@@ -28,12 +28,12 @@ import java.nio.ByteBuffer;
 
 /**
  * A class to map objects to and from solr documents.
- * 
+ *
  *
  * @since solr 1.3
  */
 public class DocumentObjectBinder {
-  
+
   private final Map<Class, List<DocField>> infocache = new ConcurrentHashMap<>();
 
   public DocumentObjectBinder() {
@@ -52,7 +52,7 @@ public class DocumentObjectBinder {
   public <T> T getBean(Class<T> clazz, SolrDocument solrDoc) {
     return getBean(clazz, null, solrDoc);
   }
-  
+
   private <T> T getBean(Class<T> clazz, List<DocField> fields, SolrDocument solrDoc) {
     if (fields == null) {
       fields = getDocFields(clazz);
@@ -68,7 +68,7 @@ public class DocumentObjectBinder {
       throw new BindingException("Could not instantiate object of " + clazz, e);
     }
   }
-  
+
   public SolrInputDocument toSolrInputDocument(Object obj) {
     List<DocField> fields = getDocFields(obj.getClass());
     if (fields.isEmpty()) {
@@ -86,12 +86,33 @@ public class DocumentObjectBinder {
           doc.setField(e.getKey(), e.getValue(), 1.0f);
         }
       } else {
-        doc.setField(field.name, field.get(obj), 1.0f);
+        if (field.child != null) {
+          addChild(obj, field, doc);
+        } else {
+          doc.setField(field.name, field.get(obj), 1.0f);
+        }
       }
     }
     return doc;
   }
-  
+
+  private void addChild(Object obj, DocField field, SolrInputDocument doc) {
+    Object val = field.get(obj);
+    if (val == null) return;
+    if (val instanceof Collection) {
+      Collection collection = (Collection) val;
+      for (Object o : collection) {
+        SolrInputDocument child = toSolrInputDocument(o);
+        doc.addChildDocument(child);
+      }
+    } else if (val.getClass().isArray()) {
+      Object[] objs = (Object[]) val;
+      for (Object o : objs) doc.addChildDocument(toSolrInputDocument(o));
+    } else {
+      doc.addChildDocument(toSolrInputDocument(val));
+    }
+  }
+
   private List<DocField> getDocFields(Class clazz) {
     List<DocField> fields = infocache.get(clazz);
     if (fields == null) {
@@ -112,17 +133,24 @@ public class DocumentObjectBinder {
       members.addAll(Arrays.asList(superClazz.getDeclaredMethods()));
       superClazz = superClazz.getSuperclass();
     }
-
+    boolean childFieldFound = false;
     for (AccessibleObject member : members) {
       if (member.isAnnotationPresent(Field.class)) {
         member.setAccessible(true);
-        fields.add(new DocField(member));
+        DocField df = new DocField(member);
+        if (df.child != null) {
+          if (childFieldFound)
+            throw new BindingException(clazz.getName() + " cannot have more than one Field with child=true");
+          childFieldFound = true;
+        }
+        fields.add(df);
       }
     }
     return fields;
   }
 
-  private static class DocField {
+  private class DocField {
+    private Field annotation;
     private String name;
     private java.lang.reflect.Field field;
     private Method setter;
@@ -130,6 +158,7 @@ public class DocumentObjectBinder {
     private Class type;
     private boolean isArray;
     private boolean isList;
+    private List<DocField> child;
 
     /*
      * dynamic fields may use a Map based data structure to bind a given field.
@@ -145,10 +174,10 @@ public class DocumentObjectBinder {
       } else {
         setter = (Method) member;
       }
-      Field annotation = member.getAnnotation(Field.class);
+      annotation = member.getAnnotation(Field.class);
       storeName(annotation);
       storeType();
-      
+
       // Look for a matching getter
       if (setter != null) {
         String gname = setter.getName();
@@ -172,7 +201,7 @@ public class DocumentObjectBinder {
     }
 
     private void storeName(Field annotation) {
-      if (annotation.value().equals(Field.DEFAULT)) {
+      if (annotation.value().equals(DEFAULT)) {
         if (field != null) {
           name = field.getName();
         } else {
@@ -204,15 +233,24 @@ public class DocumentObjectBinder {
         type = params[0];
       }
 
-      if(type == Collection.class || type == List.class || type == ArrayList.class) {
-        type = Object.class;
+      if (type == Collection.class || type == List.class || type == ArrayList.class) {
         isList = true;
+        if (annotation.child()) {
+          populateChild(field.getGenericType());
+        } else {
+          type = Object.class;
+        }
       } else if (type == byte[].class) {
         //no op
       } else if (type.isArray()) {
         isArray = true;
-        type = type.getComponentType();
+        if (annotation.child()) {
+          populateChild(type.getComponentType());
+        } else {
+          type = type.getComponentType();
+        }
       } else if (type == Map.class || type == HashMap.class) { //corresponding to the support for dynamicFields
+        if (annotation.child()) throw new BindingException("Map should is not a valid type for a child document");
         isContainedInMap = true;
         //assigned a default type
         type = Object.class;
@@ -226,7 +264,7 @@ public class DocumentObjectBinder {
               //Raw and primitive types
               if (types[1] instanceof Class) {
                 //the value could be multivalued then it is a List, Collection, ArrayList
-                if (types[1]== Collection.class || types[1] == List.class || types[1] == ArrayList.class) {
+                if (types[1] == Collection.class || types[1] == List.class || types[1] == ArrayList.class) {
                   type = Object.class;
                   isList = true;
                 } else {
@@ -234,8 +272,8 @@ public class DocumentObjectBinder {
                   type = (Class) types[1];
                 }
               } else if (types[1] instanceof ParameterizedType) { //Of all the Parameterized types, only List is supported
-                Type rawType = ((ParameterizedType)types[1]).getRawType();
-                if(rawType== Collection.class || rawType == List.class || rawType == ArrayList.class){
+                Type rawType = ((ParameterizedType) types[1]).getRawType();
+                if (rawType == Collection.class || rawType == List.class || rawType == ArrayList.class) {
                   type = Object.class;
                   isList = true;
                 }
@@ -249,9 +287,32 @@ public class DocumentObjectBinder {
             }
           }
         }
+      } else {
+        if (annotation.child()) {
+          populateChild(type);
+        }
       }
     }
 
+    private void populateChild(Type typ) {
+      if (typ == null) {
+        throw new RuntimeException("no type information available for" + (field == null ? setter : field));
+      }
+      if (typ.getClass() == Class.class) {//of type class
+        type = (Class) typ;
+      } else if (typ instanceof ParameterizedType) {
+        try {
+          type = Class.forName(((ParameterizedType) typ).getActualTypeArguments()[0].getTypeName());
+        } catch (ClassNotFoundException e) {
+          throw new BindingException("Invalid type information available for" + (field == null ? setter : field));
+        }
+      } else {
+        throw new BindingException("Invalid type information available for" + (field == null ? setter : field));
+
+      }
+      child = getDocFields(type);
+    }
+
     /**
      * Called by the {@link #inject} method to read the value(s) for a field
      * This method supports reading of all "matching" fieldName's in the <code>SolrDocument</code>
@@ -261,6 +322,26 @@ public class DocumentObjectBinder {
      */
     @SuppressWarnings("unchecked")
     private Object getFieldValue(SolrDocument solrDocument) {
+      if (child != null) {
+        List<SolrDocument> children = solrDocument.getChildDocuments();
+        if (children == null || children.isEmpty()) return null;
+        if (isList) {
+          ArrayList list = new ArrayList(children.size());
+          for (SolrDocument c : children) {
+            list.add(getBean(type, child, c));
+          }
+          return list;
+        } else if (isArray) {
+          Object[] arr = (Object[]) Array.newInstance(type, children.size());
+          for (int i = 0; i < children.size(); i++) {
+            arr[i] = getBean(type, child, children.get(i));
+          }
+          return arr;
+
+        } else {
+          return getBean(type, child, children.get(0));
+        }
+      }
       Object fieldValue = solrDocument.getFieldValue(name);
       if (fieldValue != null) {
         //this is not a dynamic field. so return the value
@@ -365,7 +446,7 @@ public class DocumentObjectBinder {
         } else if (setter != null) {
           setter.invoke(obj, v);
         }
-      } 
+      }
       catch (Exception e) {
         throw new BindingException("Exception while setting value : " + v + " on " + (field != null ? field : setter), e);
       }
@@ -389,4 +470,5 @@ public class DocumentObjectBinder {
       }
     }
   }
+  public static final String DEFAULT = "#default";
 }

Modified: lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/beans/Field.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/beans/Field.java?rev=1659847&r1=1659846&r2=1659847&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/beans/Field.java (original)
+++ lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/beans/Field.java Sat Feb 14 19:21:14 2015
@@ -18,18 +18,21 @@ package org.apache.solr.client.solrj.bea
 
 import static java.lang.annotation.ElementType.*;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static org.apache.solr.client.solrj.beans.DocumentObjectBinder.DEFAULT;
 
 import java.lang.annotation.Target;
 import java.lang.annotation.Retention;
 
 
 /**
+ * This class can be used to annotate a field or a setter an any class
+ * and SlrJ would help you convert to SolrInputDocument and from SolrDocument
  *
  * @since solr 1.3
  */
 @Target({FIELD, METHOD})
 @Retention(RUNTIME)
 public @interface Field {
-  public static final String DEFAULT ="#default";
+  boolean child() default false;
   String value() default DEFAULT;
 }

Modified: lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java?rev=1659847&r1=1659846&r2=1659847&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java (original)
+++ lucene/dev/branches/branch_5x/solr/solrj/src/java/org/apache/solr/client/solrj/util/ClientUtils.java Sat Feb 14 19:21:14 2015
@@ -83,11 +83,16 @@ public class ClientUtils
    * @param d SolrInputDocument to convert
    * @return a SolrDocument with the same fields and values as the SolrInputDocument
    */
-  public static SolrDocument toSolrDocument( SolrInputDocument d )
-  {
+  public static SolrDocument toSolrDocument(SolrInputDocument d) {
     SolrDocument doc = new SolrDocument();
-    for( SolrInputField field : d ) {
-      doc.setField( field.getName(), field.getValue() );
+    for (SolrInputField field : d) {
+      doc.setField(field.getName(), field.getValue());
+    }
+    if (d.getChildDocuments() != null) {
+      for (SolrInputDocument in : d.getChildDocuments()) {
+        doc.addChildDocument(toSolrDocument(in));
+      }
+
     }
     return doc;
   }

Modified: lucene/dev/branches/branch_5x/solr/solrj/src/test/org/apache/solr/client/solrj/beans/TestDocumentObjectBinder.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/solrj/src/test/org/apache/solr/client/solrj/beans/TestDocumentObjectBinder.java?rev=1659847&r1=1659846&r2=1659847&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/solrj/src/test/org/apache/solr/client/solrj/beans/TestDocumentObjectBinder.java (original)
+++ lucene/dev/branches/branch_5x/solr/solrj/src/test/org/apache/solr/client/solrj/beans/TestDocumentObjectBinder.java Sat Feb 14 19:21:14 2015
@@ -26,7 +26,6 @@ import org.apache.solr.common.SolrInputD
 import org.apache.solr.common.SolrInputField;
 import org.apache.solr.common.SolrDocument;
 import org.apache.solr.common.util.NamedList;
-import org.junit.Assert;
 import org.junit.Test;
 
 import java.io.StringReader;
@@ -36,7 +35,6 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import static org.junit.Assert.*;
 
 public class TestDocumentObjectBinder extends LuceneTestCase {
 
@@ -164,6 +162,52 @@ public class TestDocumentObjectBinder ex
     assertEquals(supB, out1.supplier.get("supplier_supB"));
   }
 
+  public void testChild() throws Exception {
+    SingleValueChild in = new SingleValueChild();
+    in.id = "1";
+    in.child = new Child();
+    in.child.id = "1.0";
+    in.child.name = "Name One";
+    DocumentObjectBinder binder = new DocumentObjectBinder();
+    SolrInputDocument doc = binder.toSolrInputDocument(in);
+    assertEquals(1, doc.getChildDocuments().size());
+    assertEquals(1, ClientUtils.toSolrDocument(doc).getChildDocuments().size());
+    SingleValueChild out = binder.getBean(SingleValueChild.class, ClientUtils.toSolrDocument(doc));
+    assertEquals(in.id, out.id);
+    assertEquals(in.child.id, out.child.id);
+    assertEquals(in.child.name, out.child.name);
+
+    ListChild listIn = new ListChild();
+    listIn.id = "2";
+    Child child = new Child();
+    child.id = "1.1";
+    child.name = "Name Two";
+    listIn.child = Arrays.asList(in.child, child);
+    doc = binder.toSolrInputDocument(listIn);
+    assertEquals(2, doc.getChildDocuments().size());
+    assertEquals(2, ClientUtils.toSolrDocument(doc).getChildDocuments().size());
+    ListChild listOut = binder.getBean(ListChild.class, ClientUtils.toSolrDocument(doc));
+    assertEquals(listIn.id, listOut.id);
+    assertEquals(listIn.child.get(0).id, listOut.child.get(0).id);
+    assertEquals(listIn.child.get(0).name, listOut.child.get(0).name);
+    assertEquals(listIn.child.get(1).id, listOut.child.get(1).id);
+    assertEquals(listIn.child.get(1).name, listOut.child.get(1).name);
+
+    ArrayChild arrIn = new ArrayChild();
+    arrIn.id = "3";
+    arrIn.child = new Child[]{in.child, child};
+    doc = binder.toSolrInputDocument(arrIn);
+    assertEquals(2, doc.getChildDocuments().size());
+    assertEquals(2, ClientUtils.toSolrDocument(doc).getChildDocuments().size());
+    ArrayChild arrOut = binder.getBean(ArrayChild.class, ClientUtils.toSolrDocument(doc));
+    assertEquals(arrIn.id, arrOut.id);
+    assertEquals(arrIn.child[0].id, arrOut.child[0].id);
+    assertEquals(arrIn.child[0].name, arrOut.child[0].name);
+    assertEquals(arrIn.child[1].id, arrOut.child[1].id);
+    assertEquals(arrIn.child[1].name, arrOut.child[1].name);
+
+  }
+
   public static class Item {
     @Field
     String id;
@@ -184,7 +228,7 @@ public class TestDocumentObjectBinder ex
 
     @Field("supplier_*")
     Map<String, List<String>> supplier;
-    
+
     @Field("sup_simple_*")
     Map<String, String> supplier_simple;
 
@@ -192,7 +236,7 @@ public class TestDocumentObjectBinder ex
 
     @Field("supplier_*")
     public void setAllSuppliers(String[] allSuppliers) {
-      this.allSuppliers = allSuppliers;  
+      this.allSuppliers = allSuppliers;
     }
 
     public String[] getAllSuppliers() {
@@ -203,12 +247,47 @@ public class TestDocumentObjectBinder ex
     public void setInStock(Boolean b) {
       inStock = b;
     }
-    
+
     // required if you want to fill SolrDocuments with the same annotaion...
     public boolean isInStock() {
       return inStock;
     }
   }
+
+  public static class Child {
+    @Field
+    String id;
+
+    @Field
+    String name;
+
+  }
+
+  public static class SingleValueChild {
+    @Field
+    String id;
+
+    @Field(child = true)
+    Child child;
+  }
+
+  public static class ListChild {
+    @Field
+    String id;
+
+    @Field(child = true)
+    List<Child> child;
+
+  }
+
+  public static class ArrayChild {
+
+    @Field
+    String id;
+
+    @Field(child = true)
+    Child[] child;
+  }
   
 
   public static class NotGettableItem {