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

@ -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

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
{
@ -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<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>";
}