From 303b6c383652a941db85fc8e6206036051c20a88 Mon Sep 17 00:00:00 2001 From: Noble Paul Date: Wed, 15 Jul 2009 05:55:46 +0000 Subject: [PATCH] SOLR-1129 Support binding dynamic fields to beans in SolrJ git-svn-id: https://svn.apache.org/repos/asf/lucene/solr/trunk@794144 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES.txt | 4 +- .../solrj/beans/DocumentObjectBinder.java | 195 ++++++++++++++---- .../solrj/beans/TestDocumentObjectBinder.java | 36 +++- 3 files changed, 194 insertions(+), 41 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 7763d731b17..b454e16b4a8 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -911,7 +911,9 @@ New Features per-handler basis (shalin) 70. SOLR-716: Added support for properties in configuration files. Properties can be specified in - solr.xml and can be used in solrconfig.xml and schema.xml (Henri Biestro, hossman, ryan, shalin) + solr.xml and can be used in solrconfig.xml and schema.xml (Henri Biestro, hossman, ryan, shalin) + +71. SOLR-1129 : Support binding dynamic fields to beans in SolrJ (Avlesh Singh , noble) Changes in runtime behavior 1. SOLR-559: use Lucene updateDocument, deleteDocuments methods. This diff --git a/src/solrj/org/apache/solr/client/solrj/beans/DocumentObjectBinder.java b/src/solrj/org/apache/solr/client/solrj/beans/DocumentObjectBinder.java index 99090975fa1..acfba050504 100644 --- a/src/solrj/org/apache/solr/client/solrj/beans/DocumentObjectBinder.java +++ b/src/solrj/org/apache/solr/client/solrj/beans/DocumentObjectBinder.java @@ -19,11 +19,9 @@ 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.lang.reflect.*; import java.util.*; +import java.util.regex.Pattern; import java.util.concurrent.ConcurrentHashMap; import java.nio.ByteBuffer; @@ -49,7 +47,6 @@ public class DocumentObjectBinder { T obj = null; try { obj = clazz.newInstance(); - result.add(obj); } catch (Exception e) { throw new RuntimeException("Could not instantiate object of " + clazz,e); } @@ -57,6 +54,7 @@ public class DocumentObjectBinder { DocField docField = fields.get(i); docField.inject(obj, sdoc); } + result.add(obj); } return result; } @@ -112,6 +110,14 @@ public class DocumentObjectBinder { private Class type; private boolean isArray = false, isList=false; + /* + * dynamic fields may use a Map based data structure to bind a given field. + * if a mapping is done using, "Map> foo", isContainedInMap + * is set to TRUE as well as isList is set to TRUE + */ + boolean isContainedInMap =false; + private Pattern dynamicFieldNamePatternMatcher; + public DocField(AccessibleObject member) { if (member instanceof java.lang.reflect.Field) { field = (java.lang.reflect.Field) member; @@ -158,6 +164,13 @@ public class DocumentObjectBinder { name = setter.getName(); } } + } + //dynamic fields are annotated as @Field("categories_*") + else if(annotation.value().indexOf('*') >= 0){ + //if the field was annotated as a dynamic field, convert the name into a pattern + //the wildcard (*) is supposed to be either a prefix or a suffix, hence the use of replaceFirst + name = annotation.value().replaceFirst("\\*", "\\.*"); + dynamicFieldNamePatternMatcher = Pattern.compile("^"+name+"$"); } else { name = annotation.value(); } @@ -189,40 +202,150 @@ public class DocumentObjectBinder { isArray = true; type = type.getComponentType(); } - } - - public 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 { - Object[] arr = (Object[]) Array.newInstance(type, 1); - arr[0] = val; - set(obj, arr); - } - } 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) ; + //corresponding to the support for dynamicFields + else if (type == Map.class || type == HashMap.class) { + isContainedInMap = true; + //assigned a default type + type = Object.class; + if(field != null){ + if(field.getGenericType() instanceof ParameterizedType){ + //check what are the generic values + ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType(); + Type[] types = parameterizedType.getActualTypeArguments(); + if(types != null && types.length == 2 && types[0] == String.class){ + //the key should always be String + //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){ + type = Object.class; + isList = true; + }else{ + //else assume it is a primitive and put in the source type itself + type = (Class) types[1]; + } + } + //Of all the Parameterized types, only List is supported + else if(types[1] instanceof ParameterizedType){ + Type rawType = ((ParameterizedType)types[1]).getRawType(); + if(rawType== Collection.class || rawType == List.class || rawType == ArrayList.class){ + type = Object.class; + isList = true; + } + } + //Array types + else if(types[1] instanceof GenericArrayType){ + type = (Class) ((GenericArrayType) types[1]).getGenericComponentType(); + isArray = true; + } + //Throw an Exception if types are not known + else{ + throw new RuntimeException("Allowed type for values of mapping a dynamicField are : " + + "Object, Object[] and List"); + } + } + } } } } - + + /** + * 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 SolrDocument + * + * Returns SolrDocument.getFieldValue for regular fields, + * and Map> for a dynamic field. The key is all matching fieldName's. + */ + @SuppressWarnings("unchecked") + private Object getFieldValue(SolrDocument sdoc){ + Object fieldValue = sdoc.getFieldValue(name); + if(fieldValue != null) { + //this is not a dynamic field. so return te value + return fieldValue; + } + //reading dynamic field values + if(dynamicFieldNamePatternMatcher != null){ + Map allValuesMap = null; + ArrayList allValuesList = null; + if(isContainedInMap){ + allValuesMap = new HashMap(); + } else { + allValuesList = new ArrayList(); + } + for(String field : sdoc.getFieldNames()){ + if(dynamicFieldNamePatternMatcher.matcher(field).find()){ + Object val = sdoc.getFieldValue(field); + if(val == null) continue; + if(isContainedInMap){ + if(isList){ + if (!(val instanceof List)) { + ArrayList al = new ArrayList(); + al.add(val); + val = al; + } + } else if(isArray){ + if (!(val instanceof List)) { + Object[] arr= (Object[]) Array.newInstance(type,1); + arr[0] = val; + val= arr; + } else { + val = Array.newInstance(type,((List)val).size()); + } + } + allValuesMap.put(field, val); + }else { + if (val instanceof Collection) { + allValuesList.addAll((Collection) val); + } else { + allValuesList.add(val); + } + } + } + } + if (isContainedInMap) { + return allValuesMap.isEmpty() ? null : allValuesMap; + } else { + return allValuesList.isEmpty() ? null : allValuesList; + } + } + return null; + } + void inject(T obj, SolrDocument sdoc) { + Object val = getFieldValue(sdoc); + if(val == null) { + System.out.println("val null for "+ name); + return; + } + if(isArray && !isContainedInMap){ + List list = null; + if(val.getClass().isArray()){ + set(obj,val); + return; + } else if (val instanceof List) { + list = (List) val; + } else{ + list = new ArrayList(); + list.add(val); + } + set(obj, list.toArray((Object[]) Array.newInstance(type,list.size()))); + } else if(isList && !isContainedInMap){ + if (!(val instanceof List)) { + ArrayList list = new ArrayList(); + list.add(val); + val = list; + } + set(obj, val); + } else if(isContainedInMap){ + if (val instanceof Map) { + set(obj, val); + } + } else { + set(obj, val); + } + + } + + private void set(Object obj, Object v) { if(v!= null && type == ByteBuffer.class && v.getClass()== byte[].class) { v = ByteBuffer.wrap((byte[])v); diff --git a/src/test/org/apache/solr/client/solrj/beans/TestDocumentObjectBinder.java b/src/test/org/apache/solr/client/solrj/beans/TestDocumentObjectBinder.java index b0577bddb89..9d995d40786 100644 --- a/src/test/org/apache/solr/client/solrj/beans/TestDocumentObjectBinder.java +++ b/src/test/org/apache/solr/client/solrj/beans/TestDocumentObjectBinder.java @@ -18,8 +18,6 @@ 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; @@ -34,6 +32,7 @@ import java.io.StringReader; import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Map; public class TestDocumentObjectBinder extends TestCase { @@ -57,7 +56,7 @@ public class TestDocumentObjectBinder extends TestCase 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 ); @@ -79,7 +78,21 @@ public class TestDocumentObjectBinder extends TestCase Assert.assertEquals("hello", l.get(0).categories[0]); } - + + public void testDynamicFieldBinding(){ + DocumentObjectBinder binder = new DocumentObjectBinder(); + XMLResponseParser parser = new XMLResponseParser(); + NamedList nl = parser.processResponse(new StringReader(xml)); + QueryResponse res = new QueryResponse(nl, null); + List l = binder.getBeans(Item.class,res.getResults()); + Assert.assertArrayEquals(new String[]{"Mobile Store","iPod Store","CCTV Store"}, l.get(3).getAllSuppliers()); + Assert.assertTrue(l.get(3).supplier.containsKey("supplier_1")); + Assert.assertTrue(l.get(3).supplier.containsKey("supplier_2")); + Assert.assertEquals(2, l.get(3).supplier.size()); + Assert.assertEquals("[Mobile Store, iPod Store]", l.get(3).supplier.get("supplier_1").toString()); + Assert.assertEquals("[CCTV Store]", l.get(3).supplier.get("supplier_2").toString()); + } + public void testToAndFromSolrDocument() { Item item = new Item(); @@ -119,6 +132,20 @@ public class TestDocumentObjectBinder extends TestCase boolean inStock = false; + @Field("supplier_*") + Map> supplier; + + private String[] allSuppliers; + + @Field("supplier_*") + public void setAllSuppliers(String[] allSuppliers){ + this.allSuppliers = allSuppliers; + } + + public String[] getAllSuppliers(){ + return this.allSuppliers; + } + @Field public void setInStock(Boolean b) { inStock = b; @@ -175,6 +202,7 @@ public class TestDocumentObjectBinder extends TestCase "car power adapter for iPod, whiteIW-02false" + "BelkiniPod & iPod Mini USB 2.0 Cable" + "111.5IW-02" + + "Mobile StoreiPod StoreCCTV Store" + "2008-04-16T10:35:57.140Z2.0\n" + ""; }