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
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
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.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<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) {
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 <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 {
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 <code>SolrDocument</code>
*
* Returns <code>SolrDocument.getFieldValue</code> for regular fields,
* and <code>Map<String, List<Object>></code> 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<String, Object> allValuesMap = null;
ArrayList allValuesList = null;
if(isContainedInMap){
allValuesMap = new HashMap<String, Object>();
} 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;
}
<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;
}
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);

View File

@ -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
{
@ -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()
{
Item item = new Item();
@ -119,6 +132,20 @@ public class TestDocumentObjectBinder extends TestCase
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
public void setInStock(Boolean 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 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>" +
"<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" +
"</response>";
}