mirror of https://github.com/apache/lucene.git
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:
parent
cf2aac0ca3
commit
303b6c3836
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 & iPod Mini USB 2.0 Cable</str>" +
|
"<str name=\"manu\">Belkin</str><str name=\"name\">iPod & 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>";
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue