Author: ryan
Date: Thu Jun  5 08:40:26 2008
New Revision: 663643

URL: http://svn.apache.org/viewvc?rev=663643&view=rev
Log:
SOLR-536: Add a DocumentObjectBinder to solrj that converts Objects to and from 
SolrDocuments.

Added:
    lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/beans/
    
lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/beans/DocumentObjectBinder.java
    
lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/beans/Field.java
    lucene/solr/trunk/client/java/solrj/test/org/apache/solr/client/solrj/beans/
    
lucene/solr/trunk/client/java/solrj/test/org/apache/solr/client/solrj/beans/TestDocumentObjectBinder.java
Modified:
    lucene/solr/trunk/CHANGES.txt
    
lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/util/ClientUtils.java

Modified: lucene/solr/trunk/CHANGES.txt
URL: 
http://svn.apache.org/viewvc/lucene/solr/trunk/CHANGES.txt?rev=663643&r1=663642&r2=663643&view=diff
==============================================================================
--- lucene/solr/trunk/CHANGES.txt (original)
+++ lucene/solr/trunk/CHANGES.txt Thu Jun  5 08:40:26 2008
@@ -286,6 +286,9 @@
     the global Lucene Similarity implementation.
     (ehatcher)
     
+51. SOLR-536: Add a DocumentObjectBinder to solrj that converts Objects to and
+    from SolrDocuments.  (Noble Paul via ryan)
+    
 Changes in runtime behavior
  1. SOLR-559: use Lucene updateDocument, deleteDocuments methods.  This
    removes the maxBufferedDeletes parameter added by SOLR-310 as Lucene

Added: 
lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/beans/DocumentObjectBinder.java
URL: 
http://svn.apache.org/viewvc/lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/beans/DocumentObjectBinder.java?rev=663643&view=auto
==============================================================================
--- 
lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/beans/DocumentObjectBinder.java
 (added)
+++ 
lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/beans/DocumentObjectBinder.java
 Thu Jun  5 08:40:26 2008
@@ -0,0 +1,256 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.beans;
+
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.SolrDocument;
+import org.apache.solr.common.SolrInputDocument;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Method;
+import java.lang.reflect.Array;
+import java.util.*;
+
+/**
+ * A class to map objects to and from solr documents.
+ * 
+ * @version $Id: ClientUtils.java 601900 2007-12-06 22:55:47Z ryan $
+ * @since solr 1.3
+ */
+public class DocumentObjectBinder {
+  private final Map<Class, List<DocField>> infocache = 
+    Collections.synchronizedMap( new HashMap<Class, List<DocField>>() );
+
+  public DocumentObjectBinder() {
+  }
+
+  public <T> List<T> getBeans(Class<T> clazz, SolrDocumentList solrDocList) {
+    List<DocField> fields = getDocFields( clazz );
+    List<T> result = new ArrayList<T>(solrDocList.size());
+
+    for(int j=0;j<solrDocList.size();j++) {
+      SolrDocument sdoc = solrDocList.get(j);
+
+      T obj = null;
+      try {
+        obj = clazz.newInstance();
+        result.add(obj);
+      } catch (Exception e) {
+        throw new RuntimeException("Could not instantiate object of " + 
clazz,e);
+      }
+      for (int i = 0; i < fields.size(); i++) {
+        DocField docField = fields.get(i);
+        docField.inject(obj, sdoc);
+      }
+    }
+    return result;
+  }
+  
+  public SolrInputDocument toSolrInputDocument( Object obj )
+  {
+    List<DocField> fields = getDocFields( obj.getClass() );
+    if( fields.isEmpty() ) {
+      throw new RuntimeException( "class: "+obj.getClass()+" does not define 
any fields." );
+    }
+    
+    SolrInputDocument doc = new SolrInputDocument();
+    for( DocField field : fields ) {
+      doc.setField( field.name, field.get( obj ), 1.0f );
+    }
+    return doc;
+  }
+  
+  private List<DocField> getDocFields( Class clazz )
+  {
+    List<DocField> fields = infocache.get(clazz);
+    if (fields == null) {
+      synchronized(infocache) {
+        infocache.put(clazz, fields = collectInfo(clazz));
+      }
+    }
+    return fields;
+  }
+
+  private List<DocField> collectInfo(Class clazz) {
+    List<DocField> fields = new ArrayList<DocField>();
+    Class superClazz = clazz;
+    ArrayList<AccessibleObject> members = new ArrayList<AccessibleObject>();
+    while (superClazz != null && superClazz != Object.class) {
+      members.addAll(Arrays.asList(superClazz.getDeclaredFields()));
+      members.addAll(Arrays.asList(superClazz.getDeclaredMethods()));
+      superClazz = superClazz.getSuperclass();
+    }
+    for (AccessibleObject member : members) {
+      if (member.isAnnotationPresent(Field.class)) {
+        member.setAccessible(true);
+        fields.add(new DocField(member));
+      }
+    }
+    return fields;
+  }
+
+  private static class DocField {
+    private String name;
+    private java.lang.reflect.Field field;
+    private Method setter;
+    private Method getter;
+    private Class type;
+    private boolean isArray = false, isList=false;
+
+    public DocField(AccessibleObject member) {
+      if (member instanceof java.lang.reflect.Field) {
+        field = (java.lang.reflect.Field) member;
+      } else {
+        setter = (Method) member;
+      }
+      Field annotation = member.getAnnotation(Field.class);
+      storeName(annotation);
+      storeType();
+      
+      // Look for a matching getter
+      if( setter != null ) {
+        String gname = setter.getName();
+        if( gname.startsWith("set") ) {
+          gname = "get" + gname.substring(3);
+          try {
+            getter = setter.getDeclaringClass().getMethod( gname, 
(Class[])null );
+          }
+          catch( Exception ex ) {
+            // no getter -- don't worry about it...
+            if( type == Boolean.class ) {
+              gname = "is" + setter.getName().substring( 3 );
+              try {
+                getter = setter.getDeclaringClass().getMethod( gname, 
(Class[])null );
+              }
+              catch( Exception ex2 ) {
+                // no getter -- don't worry about it...
+              }
+            }
+          }
+        }
+      }
+    }
+
+    private void storeName(Field annotation) {
+      if (annotation.value().equals(Field.DEFAULT)) {
+        if (field != null) {
+          name = field.getName();
+        } else {
+          String setterName = setter.getName();
+          if (setterName.startsWith("set") && setterName.length() > 3) {
+            name = setterName.substring(3, 4).toLowerCase() + 
setterName.substring(4);
+          } else {
+            name = setter.getName();
+          }
+        }
+      } else {
+        name = annotation.value();
+      }
+    }
+
+    private void storeType() {
+      if (field != null) {
+        type = field.getType();
+      } else {
+        Class[] params = setter.getParameterTypes();
+        if (params.length != 1)
+          throw new RuntimeException("Invalid setter method. Must have one and 
only one parameter");
+        type = params[0];
+      }
+      if(type == Collection.class || type == List.class || type == 
ArrayList.class) {
+        type = Object.class;
+        isList = true;
+        /*ParameterizedType parameterizedType = null;
+        if(field !=null){
+          if( field.getGenericType() instanceof ParameterizedType){
+            parameterizedType = (ParameterizedType) field.getGenericType();
+            Type[] types = parameterizedType.getActualTypeArguments();
+            if (types != null && types.length > 0) type = (Class) types[0];
+          }
+        }*/
+      } else if (type.isArray()) {
+        isArray = true;
+        type = type.getComponentType();
+      }
+    }
+
+    public <T> void inject(T obj, SolrDocument sdoc) {
+      Object val = sdoc.getFieldValue(name);
+      if(val == null) return;
+      if (isArray) {
+        if (val instanceof List) {
+          List collection = (List) val;
+          set(obj, collection.toArray((Object[]) 
Array.newInstance(type,collection.size())));
+        } else {
+          set(obj, new Object[]{val});
+        }
+      } else if (isList) {
+        if (val instanceof List) {
+          set(obj, val);
+        } else {
+          ArrayList l = new ArrayList();
+          l.add(val);
+          set(obj, l);
+        }
+      } else {
+        if (val instanceof List) {
+          List l = (List) val;
+          if(l.size()>0) 
+            set(obj, l.get(0));
+        } 
+        else {
+          set(obj,val) ;
+        }
+      }
+    }
+    
+    private void set(Object obj, Object v) {
+      try {
+        if (field != null) {
+          field.set(obj, v);
+        } else if (setter != null) {
+          setter.invoke(obj, v);
+        }
+      } 
+      catch (Exception e) {
+        throw new RuntimeException("Exception while setting value : "+v+" on " 
+ (field != null ? field : setter), e);
+      }
+    }
+    
+    public Object get( final Object obj )
+    {
+      if (field != null) {
+        try {
+          return field.get(obj);
+        } 
+        catch (Exception e) {        
+          throw new RuntimeException("Exception while getting value: " + 
field, e);
+        }
+      }
+      else if (getter == null) {
+        throw new RuntimeException( "Missing getter for field: "+name+" -- You 
can only call the 'get' for fields that have a field of 'get' method" );
+      }
+      
+      try {
+        return getter.invoke( obj, (Object[])null );
+      } 
+      catch (Exception e) {        
+        throw new RuntimeException("Exception while getting value: " + getter, 
e);
+      }
+    }
+  }
+}

Added: 
lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/beans/Field.java
URL: 
http://svn.apache.org/viewvc/lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/beans/Field.java?rev=663643&view=auto
==============================================================================
--- 
lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/beans/Field.java
 (added)
+++ 
lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/beans/Field.java
 Thu Jun  5 08:40:26 2008
@@ -0,0 +1,35 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.beans;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Target;
+import java.lang.annotation.Retention;
+
+
+/**
+ * @version $Id: ClientUtils.java 601900 2007-12-06 22:55:47Z ryan $
+ * @since solr 1.3
+ */
[EMAIL PROTECTED]({FIELD, METHOD, TYPE})
[EMAIL PROTECTED](RUNTIME)
+public @interface Field {
+  public static final String DEFAULT ="#default";
+  String value() default DEFAULT;
+}

Modified: 
lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/util/ClientUtils.java
URL: 
http://svn.apache.org/viewvc/lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/util/ClientUtils.java?rev=663643&r1=663642&r2=663643&view=diff
==============================================================================
--- 
lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/util/ClientUtils.java
 (original)
+++ 
lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/util/ClientUtils.java
 Thu Jun  5 08:40:26 2008
@@ -82,6 +82,19 @@
     }
     return doc;
   }
+
+  /**
+   * @param SolrInputDocument to convert
+   * @return a SolrDocument with the same fields and values as the 
SolrInputDocument
+   */
+  public static SolrDocument toSolrDocument( SolrInputDocument d )
+  {
+    SolrDocument doc = new SolrDocument();
+    for( SolrInputField field : d ) {
+      doc.setField( field.getName(), field.getValue() );
+    }
+    return doc;
+  }
   
   //------------------------------------------------------------------------
   //------------------------------------------------------------------------

Added: 
lucene/solr/trunk/client/java/solrj/test/org/apache/solr/client/solrj/beans/TestDocumentObjectBinder.java
URL: 
http://svn.apache.org/viewvc/lucene/solr/trunk/client/java/solrj/test/org/apache/solr/client/solrj/beans/TestDocumentObjectBinder.java?rev=663643&view=auto
==============================================================================
--- 
lucene/solr/trunk/client/java/solrj/test/org/apache/solr/client/solrj/beans/TestDocumentObjectBinder.java
 (added)
+++ 
lucene/solr/trunk/client/java/solrj/test/org/apache/solr/client/solrj/beans/TestDocumentObjectBinder.java
 Thu Jun  5 08:40:26 2008
@@ -0,0 +1,169 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.client.solrj.beans;
+
+import junit.framework.TestCase;
+
+import org.apache.solr.client.solrj.beans.DocumentObjectBinder;
+import org.apache.solr.client.solrj.beans.Field;
+import org.apache.solr.client.solrj.impl.XMLResponseParser;
+import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.client.solrj.util.ClientUtils;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.common.SolrInputField;
+import org.apache.solr.common.util.NamedList;
+import org.junit.Assert;
+
+import java.io.StringReader;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+public class TestDocumentObjectBinder extends TestCase 
+{
+  public void testSimple() throws Exception {
+    DocumentObjectBinder binder = new DocumentObjectBinder();
+    XMLResponseParser parser = new XMLResponseParser();
+    NamedList<Object> nl = null;
+    nl = parser.processResponse(new StringReader(xml));
+    QueryResponse res = new QueryResponse(nl);
+    SolrDocumentList solDocList = res.getResults();
+    List<Item> l = binder.getBeans(Item.class,res.getResults());
+    Assert.assertEquals(solDocList.size(), l.size());
+    Assert.assertEquals(solDocList.get(0).getFieldValue("features"), 
l.get(0).features);
+    
+    Item item = new Item();
+    item.id = "aaa";
+    item.categories = new String[] { "aaa", "bbb", "ccc" };
+    SolrInputDocument out = binder.toSolrInputDocument( item );
+
+    Assert.assertEquals( item.id, out.getFieldValue( "id" ) );
+    SolrInputField catfield = out.getField( "cat" );
+    Assert.assertEquals( 3, catfield.getValueCount() );
+    Assert.assertEquals( "[aaa, bbb, ccc]", catfield.getValue().toString() );
+  
+    // Test the error on not settable stuff...
+    NotGettableItem ng = new NotGettableItem();
+    ng.setInStock( false );
+    try {
+      out = binder.toSolrInputDocument( ng );
+      Assert.fail( "Should throw an error" );
+    }
+    catch( RuntimeException ex ) {
+      // ok -- this should happen...
+    }
+  }
+  
+  public void testToAndFromSolrDocument()
+  {
+    Item item = new Item();
+    item.id = "one";
+    item.inStock = false;
+    item.categories =  new String[] { "aaa", "bbb", "ccc" };
+    item.features = Arrays.asList( item.categories );
+    
+    DocumentObjectBinder binder = new DocumentObjectBinder();
+    SolrInputDocument doc = binder.toSolrInputDocument( item );
+    SolrDocumentList docs = new SolrDocumentList();
+    docs.add( ClientUtils.toSolrDocument(doc) );
+    Item out = binder.getBeans( Item.class, docs ).get( 0 );
+
+    // make sure it came out the same
+    Assert.assertEquals( item.id, out.id );
+    Assert.assertEquals( item.inStock, out.inStock );
+    Assert.assertEquals( item.categories.length, out.categories.length );
+    Assert.assertEquals( item.features, out.features );
+  }
+
+  public static class Item {
+    @Field
+    String id;
+
+    @Field("cat")
+    String[] categories;
+
+    @Field
+    List<String> features;
+
+    @Field
+    Date timestamp;
+
+    @Field("highway_mileage")
+    int mwyMileage;
+
+    boolean inStock = false;
+
+    @Field
+    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 NotGettableItem {
+    @Field
+    String id;
+
+    private boolean inStock;
+    private String aaa;
+
+    @Field
+    public void setInStock(Boolean b) {
+      inStock = b;
+    }
+
+    public String getAaa() {
+      return aaa;
+    }
+
+    @Field
+    public void setAaa(String aaa) {
+      this.aaa = aaa;
+    }
+  }
+
+  public static final String xml = 
+    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+    "<response>" +
+    "<lst name=\"responseHeader\"><int name=\"status\">0</int><int 
name=\"QTime\">0</int><lst name=\"params\"><str name=\"start\">0</str><str 
name=\"q\">*:*\n" +
+    "</str><str name=\"version\">2.2</str><str 
name=\"rows\">4</str></lst></lst><result name=\"response\" numFound=\"26\" 
start=\"0\"><doc><arr name=\"cat\">" +
+    "<str>electronics</str><str>hard drive</str></arr><arr 
name=\"features\"><str>7200RPM, 8MB cache, IDE Ultra ATA-133</str>" +
+    "<str>NoiseGuard, SilentSeek technology, Fluid Dynamic Bearing (FDB) 
motor</str></arr><str name=\"id\">SP2514N</str>" +
+    "<bool name=\"inStock\">true</bool><str name=\"manu\">Samsung Electronics 
Co. Ltd.</str><str name=\"name\">Samsung SpinPoint P120 SP2514N - hard drive - 
250 GB - ATA-133</str>" +
+    "<int name=\"popularity\">6</int><float name=\"price\">92.0</float><str 
name=\"sku\">SP2514N</str><date 
name=\"timestamp\">2008-04-16T10:35:57.078Z</date></doc>" +
+    "<doc><arr name=\"cat\"><str>electronics</str><str>hard 
drive</str></arr><arr name=\"features\"><str>SATA 3.0Gb/s, NCQ</str><str>8.5ms 
seek</str>" +
+    "<str>16MB cache</str></arr><str name=\"id\">6H500F0</str><bool 
name=\"inStock\">true</bool><str name=\"manu\">Maxtor Corp.</str>" +
+    "<str name=\"name\">Maxtor DiamondMax 11 - hard drive - 500 GB - 
SATA-300</str><int name=\"popularity\">6</int><float 
name=\"price\">350.0</float>" +
+    "<str name=\"sku\">6H500F0</str><date 
name=\"timestamp\">2008-04-16T10:35:57.109Z</date></doc><doc><arr 
name=\"cat\"><str>electronics</str>" +
+    "<str>connector</str></arr><arr name=\"features\"><str>car power adapter, 
white</str></arr><str name=\"id\">F8V7067-APL-KIT</str>" +
+    "<bool name=\"inStock\">false</bool><str name=\"manu\">Belkin</str><str 
name=\"name\">Belkin Mobile Power Cord for iPod w/ Dock</str>" +
+    "<int name=\"popularity\">1</int><float name=\"price\">19.95</float><str 
name=\"sku\">F8V7067-APL-KIT</str>" +
+    "<date name=\"timestamp\">2008-04-16T10:35:57.140Z</date><float 
name=\"weight\">4.0</float></doc><doc>" +
+    "<arr name=\"cat\"><str>electronics</str><str>connector</str></arr><arr 
name=\"features\">" +
+    "<str>car power adapter for iPod, white</str></arr><str 
name=\"id\">IW-02</str><bool name=\"inStock\">false</bool>" +
+    "<str name=\"manu\">Belkin</str><str name=\"name\">iPod &amp; iPod Mini 
USB 2.0 Cable</str>" +
+    "<int name=\"popularity\">1</int><float name=\"price\">11.5</float><str 
name=\"sku\">IW-02</str>" +
+    "<date name=\"timestamp\">2008-04-16T10:35:57.140Z</date><float 
name=\"weight\">2.0</float></doc></result>\n" +
+    "</response>";
+}


Reply via email to