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
This commit is contained in:
Noble Paul 2009-07-15 05:55:46 +00:00
parent cf2aac0ca3
commit 303b6c3836
3 changed files with 194 additions and 41 deletions

View File

@ -913,6 +913,8 @@ New Features
70. SOLR-716: Added support for properties in configuration files. Properties can be specified in 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 Changes in runtime behavior
1. SOLR-559: use Lucene updateDocument, deleteDocuments methods. This 1. SOLR-559: use Lucene updateDocument, deleteDocuments methods. This
removes the maxBufferedDeletes parameter added by SOLR-310 as Lucene removes the maxBufferedDeletes parameter added by SOLR-310 as Lucene

View File

@ -19,11 +19,9 @@ package org.apache.solr.client.solrj.beans;
import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.SolrInputDocument;
import java.lang.reflect.*;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.lang.reflect.Array;
import java.util.*; import java.util.*;
import java.util.regex.Pattern;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -49,7 +47,6 @@ public class DocumentObjectBinder {
T obj = null; T obj = null;
try { try {
obj = clazz.newInstance(); obj = clazz.newInstance();
result.add(obj);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Could not instantiate object of " + clazz,e); throw new RuntimeException("Could not instantiate object of " + clazz,e);
} }
@ -57,6 +54,7 @@ public class DocumentObjectBinder {
DocField docField = fields.get(i); DocField docField = fields.get(i);
docField.inject(obj, sdoc); docField.inject(obj, sdoc);
} }
result.add(obj);
} }
return result; return result;
} }
@ -112,6 +110,14 @@ public class DocumentObjectBinder {
private Class type; private Class type;
private boolean isArray = false, isList=false; 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<String, List<String>> foo", <code>isContainedInMap</code>
* is set to <code>TRUE</code> as well as <code>isList</code> is set to <code>TRUE</code>
*/
boolean isContainedInMap =false;
private Pattern dynamicFieldNamePatternMatcher;
public DocField(AccessibleObject member) { public DocField(AccessibleObject member) {
if (member instanceof java.lang.reflect.Field) { if (member instanceof java.lang.reflect.Field) {
field = (java.lang.reflect.Field) member; field = (java.lang.reflect.Field) member;
@ -158,6 +164,13 @@ public class DocumentObjectBinder {
name = setter.getName(); 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 { } else {
name = annotation.value(); name = annotation.value();
} }
@ -189,40 +202,150 @@ public class DocumentObjectBinder {
isArray = true; isArray = true;
type = type.getComponentType(); type = type.getComponentType();
} }
//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");
}
}
}
}
}
} }
public <T> void inject(T obj, SolrDocument sdoc) { /**
Object val = sdoc.getFieldValue(name); * Called by the {@link #inject} method to read the value(s) for a field
if(val == null) return; * This method supports reading of all "matching" fieldName's in the <code>SolrDocument</code>
if (isArray) { *
if (val instanceof List) { * Returns <code>SolrDocument.getFieldValue</code> for regular fields,
List collection = (List) val; * and <code>Map<String, List<Object>></code> for a dynamic field. The key is all matching fieldName's.
set(obj, collection.toArray((Object[]) Array.newInstance(type,collection.size()))); */
@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<String, Object> allValuesMap = null;
ArrayList allValuesList = null;
if(isContainedInMap){
allValuesMap = new HashMap<String, Object>();
} else { } else {
Object[] arr = (Object[]) Array.newInstance(type, 1); 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; arr[0] = val;
set(obj, arr); 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;
}
<T> 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;
} }
} else if (isList) {
if (val instanceof List) {
set(obj, val); set(obj, val);
} else { } else if(isContainedInMap){
ArrayList l = new ArrayList(); if (val instanceof Map) {
l.add(val); set(obj, val);
set(obj, l);
} }
} else { } else {
if (val instanceof List) { set(obj, val);
List l = (List) val;
if(l.size()>0)
set(obj, l.get(0));
}
else {
set(obj,val) ;
}
} }
} }
private void set(Object obj, Object v) { private void set(Object obj, Object v) {
if(v!= null && type == ByteBuffer.class && v.getClass()== byte[].class) { if(v!= null && type == ByteBuffer.class && v.getClass()== byte[].class) {
v = ByteBuffer.wrap((byte[])v); v = ByteBuffer.wrap((byte[])v);

View File

@ -18,8 +18,6 @@ package org.apache.solr.client.solrj.beans;
import junit.framework.TestCase; 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.impl.XMLResponseParser;
import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.util.ClientUtils; import org.apache.solr.client.solrj.util.ClientUtils;
@ -34,6 +32,7 @@ import java.io.StringReader;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map;
public class TestDocumentObjectBinder extends TestCase public class TestDocumentObjectBinder extends TestCase
{ {
@ -80,6 +79,20 @@ public class TestDocumentObjectBinder extends TestCase
} }
public void testDynamicFieldBinding(){
DocumentObjectBinder binder = new DocumentObjectBinder();
XMLResponseParser parser = new XMLResponseParser();
NamedList<Object> nl = parser.processResponse(new StringReader(xml));
QueryResponse res = new QueryResponse(nl, null);
List<Item> 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() public void testToAndFromSolrDocument()
{ {
Item item = new Item(); Item item = new Item();
@ -119,6 +132,20 @@ public class TestDocumentObjectBinder extends TestCase
boolean inStock = false; boolean inStock = false;
@Field("supplier_*")
Map<String, List<String>> supplier;
private String[] allSuppliers;
@Field("supplier_*")
public void setAllSuppliers(String[] allSuppliers){
this.allSuppliers = allSuppliers;
}
public String[] getAllSuppliers(){
return this.allSuppliers;
}
@Field @Field
public void setInStock(Boolean b) { public void setInStock(Boolean b) {
inStock = b; inStock = b;
@ -175,6 +202,7 @@ public class TestDocumentObjectBinder extends TestCase
"<str>car power adapter for iPod, white</str></arr><str name=\"id\">IW-02</str><bool name=\"inStock\">false</bool>" + "<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>" + "<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>" + "<int name=\"popularity\">1</int><float name=\"price\">11.5</float><str name=\"sku\">IW-02</str>" +
"<str name=\"supplier_1\">Mobile Store</str><str name=\"supplier_1\">iPod Store</str><str name=\"supplier_2\">CCTV Store</str>" +
"<date name=\"timestamp\">2008-04-16T10:35:57.140Z</date><float name=\"weight\">2.0</float></doc></result>\n" + "<date name=\"timestamp\">2008-04-16T10:35:57.140Z</date><float name=\"weight\">2.0</float></doc></result>\n" +
"</response>"; "</response>";
} }