mirror of https://github.com/apache/lucene.git
SOLR-1945: Add support for child docs in DocumentObjectBinder
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1659845 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
429588097c
commit
74387f8d68
|
@ -114,6 +114,8 @@ New Features
|
|||
* SOLR-6832: Queries be served locally rather than being forwarded to another replica.
|
||||
(Sachin Goyal, Timothy Potter)
|
||||
|
||||
* SOLR-1945 : Add support for child docs in DocumentObjectBinder (Noble Paul, Mark Miller)
|
||||
|
||||
Bug Fixes
|
||||
----------------------
|
||||
|
||||
|
|
|
@ -28,12 +28,12 @@ import java.nio.ByteBuffer;
|
|||
|
||||
/**
|
||||
* A class to map objects to and from solr documents.
|
||||
*
|
||||
*
|
||||
*
|
||||
* @since solr 1.3
|
||||
*/
|
||||
public class DocumentObjectBinder {
|
||||
|
||||
|
||||
private final Map<Class, List<DocField>> infocache = new ConcurrentHashMap<>();
|
||||
|
||||
public DocumentObjectBinder() {
|
||||
|
@ -52,7 +52,7 @@ public class DocumentObjectBinder {
|
|||
public <T> T getBean(Class<T> clazz, SolrDocument solrDoc) {
|
||||
return getBean(clazz, null, solrDoc);
|
||||
}
|
||||
|
||||
|
||||
private <T> T getBean(Class<T> clazz, List<DocField> fields, SolrDocument solrDoc) {
|
||||
if (fields == null) {
|
||||
fields = getDocFields(clazz);
|
||||
|
@ -68,7 +68,7 @@ public class DocumentObjectBinder {
|
|||
throw new BindingException("Could not instantiate object of " + clazz, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public SolrInputDocument toSolrInputDocument(Object obj) {
|
||||
List<DocField> fields = getDocFields(obj.getClass());
|
||||
if (fields.isEmpty()) {
|
||||
|
@ -86,12 +86,33 @@ public class DocumentObjectBinder {
|
|||
doc.setField(e.getKey(), e.getValue(), 1.0f);
|
||||
}
|
||||
} else {
|
||||
doc.setField(field.name, field.get(obj), 1.0f);
|
||||
if (field.child != null) {
|
||||
addChild(obj, field, doc);
|
||||
} else {
|
||||
doc.setField(field.name, field.get(obj), 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
|
||||
private void addChild(Object obj, DocField field, SolrInputDocument doc) {
|
||||
Object val = field.get(obj);
|
||||
if (val == null) return;
|
||||
if (val instanceof Collection) {
|
||||
Collection collection = (Collection) val;
|
||||
for (Object o : collection) {
|
||||
SolrInputDocument child = toSolrInputDocument(o);
|
||||
doc.addChildDocument(child);
|
||||
}
|
||||
} else if (val.getClass().isArray()) {
|
||||
Object[] objs = (Object[]) val;
|
||||
for (Object o : objs) doc.addChildDocument(toSolrInputDocument(o));
|
||||
} else {
|
||||
doc.addChildDocument(toSolrInputDocument(val));
|
||||
}
|
||||
}
|
||||
|
||||
private List<DocField> getDocFields(Class clazz) {
|
||||
List<DocField> fields = infocache.get(clazz);
|
||||
if (fields == null) {
|
||||
|
@ -112,17 +133,24 @@ public class DocumentObjectBinder {
|
|||
members.addAll(Arrays.asList(superClazz.getDeclaredMethods()));
|
||||
superClazz = superClazz.getSuperclass();
|
||||
}
|
||||
|
||||
boolean childFieldFound = false;
|
||||
for (AccessibleObject member : members) {
|
||||
if (member.isAnnotationPresent(Field.class)) {
|
||||
member.setAccessible(true);
|
||||
fields.add(new DocField(member));
|
||||
DocField df = new DocField(member);
|
||||
if (df.child != null) {
|
||||
if (childFieldFound)
|
||||
throw new BindingException(clazz.getName() + " cannot have more than one Field with child=true");
|
||||
childFieldFound = true;
|
||||
}
|
||||
fields.add(df);
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
private static class DocField {
|
||||
private class DocField {
|
||||
private Field annotation;
|
||||
private String name;
|
||||
private java.lang.reflect.Field field;
|
||||
private Method setter;
|
||||
|
@ -130,6 +158,7 @@ public class DocumentObjectBinder {
|
|||
private Class type;
|
||||
private boolean isArray;
|
||||
private boolean isList;
|
||||
private List<DocField> child;
|
||||
|
||||
/*
|
||||
* dynamic fields may use a Map based data structure to bind a given field.
|
||||
|
@ -145,10 +174,10 @@ public class DocumentObjectBinder {
|
|||
} else {
|
||||
setter = (Method) member;
|
||||
}
|
||||
Field annotation = member.getAnnotation(Field.class);
|
||||
annotation = member.getAnnotation(Field.class);
|
||||
storeName(annotation);
|
||||
storeType();
|
||||
|
||||
|
||||
// Look for a matching getter
|
||||
if (setter != null) {
|
||||
String gname = setter.getName();
|
||||
|
@ -172,7 +201,7 @@ public class DocumentObjectBinder {
|
|||
}
|
||||
|
||||
private void storeName(Field annotation) {
|
||||
if (annotation.value().equals(Field.DEFAULT)) {
|
||||
if (annotation.value().equals(DEFAULT)) {
|
||||
if (field != null) {
|
||||
name = field.getName();
|
||||
} else {
|
||||
|
@ -204,15 +233,24 @@ public class DocumentObjectBinder {
|
|||
type = params[0];
|
||||
}
|
||||
|
||||
if(type == Collection.class || type == List.class || type == ArrayList.class) {
|
||||
type = Object.class;
|
||||
if (type == Collection.class || type == List.class || type == ArrayList.class) {
|
||||
isList = true;
|
||||
if (annotation.child()) {
|
||||
populateChild(field.getGenericType());
|
||||
} else {
|
||||
type = Object.class;
|
||||
}
|
||||
} else if (type == byte[].class) {
|
||||
//no op
|
||||
} else if (type.isArray()) {
|
||||
isArray = true;
|
||||
type = type.getComponentType();
|
||||
if (annotation.child()) {
|
||||
populateChild(type.getComponentType());
|
||||
} else {
|
||||
type = type.getComponentType();
|
||||
}
|
||||
} else if (type == Map.class || type == HashMap.class) { //corresponding to the support for dynamicFields
|
||||
if (annotation.child()) throw new BindingException("Map should is not a valid type for a child document");
|
||||
isContainedInMap = true;
|
||||
//assigned a default type
|
||||
type = Object.class;
|
||||
|
@ -226,7 +264,7 @@ public class DocumentObjectBinder {
|
|||
//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) {
|
||||
if (types[1] == Collection.class || types[1] == List.class || types[1] == ArrayList.class) {
|
||||
type = Object.class;
|
||||
isList = true;
|
||||
} else {
|
||||
|
@ -234,8 +272,8 @@ public class DocumentObjectBinder {
|
|||
type = (Class) types[1];
|
||||
}
|
||||
} else if (types[1] instanceof ParameterizedType) { //Of all the Parameterized types, only List is supported
|
||||
Type rawType = ((ParameterizedType)types[1]).getRawType();
|
||||
if(rawType== Collection.class || rawType == List.class || rawType == ArrayList.class){
|
||||
Type rawType = ((ParameterizedType) types[1]).getRawType();
|
||||
if (rawType == Collection.class || rawType == List.class || rawType == ArrayList.class) {
|
||||
type = Object.class;
|
||||
isList = true;
|
||||
}
|
||||
|
@ -249,9 +287,32 @@ public class DocumentObjectBinder {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (annotation.child()) {
|
||||
populateChild(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void populateChild(Type typ) {
|
||||
if (typ == null) {
|
||||
throw new RuntimeException("no type information available for" + (field == null ? setter : field));
|
||||
}
|
||||
if (typ.getClass() == Class.class) {//of type class
|
||||
type = (Class) typ;
|
||||
} else if (typ instanceof ParameterizedType) {
|
||||
try {
|
||||
type = Class.forName(((ParameterizedType) typ).getActualTypeArguments()[0].getTypeName());
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new BindingException("Invalid type information available for" + (field == null ? setter : field));
|
||||
}
|
||||
} else {
|
||||
throw new BindingException("Invalid type information available for" + (field == null ? setter : field));
|
||||
|
||||
}
|
||||
child = getDocFields(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
|
@ -261,6 +322,26 @@ public class DocumentObjectBinder {
|
|||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object getFieldValue(SolrDocument solrDocument) {
|
||||
if (child != null) {
|
||||
List<SolrDocument> children = solrDocument.getChildDocuments();
|
||||
if (children == null || children.isEmpty()) return null;
|
||||
if (isList) {
|
||||
ArrayList list = new ArrayList(children.size());
|
||||
for (SolrDocument c : children) {
|
||||
list.add(getBean(type, child, c));
|
||||
}
|
||||
return list;
|
||||
} else if (isArray) {
|
||||
Object[] arr = (Object[]) Array.newInstance(type, children.size());
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
arr[i] = getBean(type, child, children.get(i));
|
||||
}
|
||||
return arr;
|
||||
|
||||
} else {
|
||||
return getBean(type, child, children.get(0));
|
||||
}
|
||||
}
|
||||
Object fieldValue = solrDocument.getFieldValue(name);
|
||||
if (fieldValue != null) {
|
||||
//this is not a dynamic field. so return the value
|
||||
|
@ -365,7 +446,7 @@ public class DocumentObjectBinder {
|
|||
} else if (setter != null) {
|
||||
setter.invoke(obj, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new BindingException("Exception while setting value : " + v + " on " + (field != null ? field : setter), e);
|
||||
}
|
||||
|
@ -389,4 +470,5 @@ public class DocumentObjectBinder {
|
|||
}
|
||||
}
|
||||
}
|
||||
public static final String DEFAULT = "#default";
|
||||
}
|
||||
|
|
|
@ -18,18 +18,21 @@ package org.apache.solr.client.solrj.beans;
|
|||
|
||||
import static java.lang.annotation.ElementType.*;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
import static org.apache.solr.client.solrj.beans.DocumentObjectBinder.DEFAULT;
|
||||
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.annotation.Retention;
|
||||
|
||||
|
||||
/**
|
||||
* This class can be used to annotate a field or a setter an any class
|
||||
* and SlrJ would help you convert to SolrInputDocument and from SolrDocument
|
||||
*
|
||||
* @since solr 1.3
|
||||
*/
|
||||
@Target({FIELD, METHOD})
|
||||
@Retention(RUNTIME)
|
||||
public @interface Field {
|
||||
public static final String DEFAULT ="#default";
|
||||
boolean child() default false;
|
||||
String value() default DEFAULT;
|
||||
}
|
||||
|
|
|
@ -83,11 +83,16 @@ public class ClientUtils
|
|||
* @param d SolrInputDocument to convert
|
||||
* @return a SolrDocument with the same fields and values as the SolrInputDocument
|
||||
*/
|
||||
public static SolrDocument toSolrDocument( SolrInputDocument d )
|
||||
{
|
||||
public static SolrDocument toSolrDocument(SolrInputDocument d) {
|
||||
SolrDocument doc = new SolrDocument();
|
||||
for( SolrInputField field : d ) {
|
||||
doc.setField( field.getName(), field.getValue() );
|
||||
for (SolrInputField field : d) {
|
||||
doc.setField(field.getName(), field.getValue());
|
||||
}
|
||||
if (d.getChildDocuments() != null) {
|
||||
for (SolrInputDocument in : d.getChildDocuments()) {
|
||||
doc.addChildDocument(toSolrDocument(in));
|
||||
}
|
||||
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ import org.apache.solr.common.SolrInputDocument;
|
|||
import org.apache.solr.common.SolrInputField;
|
||||
import org.apache.solr.common.SolrDocument;
|
||||
import org.apache.solr.common.util.NamedList;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.StringReader;
|
||||
|
@ -36,7 +35,6 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class TestDocumentObjectBinder extends LuceneTestCase {
|
||||
|
||||
|
@ -164,6 +162,52 @@ public class TestDocumentObjectBinder extends LuceneTestCase {
|
|||
assertEquals(supB, out1.supplier.get("supplier_supB"));
|
||||
}
|
||||
|
||||
public void testChild() throws Exception {
|
||||
SingleValueChild in = new SingleValueChild();
|
||||
in.id = "1";
|
||||
in.child = new Child();
|
||||
in.child.id = "1.0";
|
||||
in.child.name = "Name One";
|
||||
DocumentObjectBinder binder = new DocumentObjectBinder();
|
||||
SolrInputDocument doc = binder.toSolrInputDocument(in);
|
||||
assertEquals(1, doc.getChildDocuments().size());
|
||||
assertEquals(1, ClientUtils.toSolrDocument(doc).getChildDocuments().size());
|
||||
SingleValueChild out = binder.getBean(SingleValueChild.class, ClientUtils.toSolrDocument(doc));
|
||||
assertEquals(in.id, out.id);
|
||||
assertEquals(in.child.id, out.child.id);
|
||||
assertEquals(in.child.name, out.child.name);
|
||||
|
||||
ListChild listIn = new ListChild();
|
||||
listIn.id = "2";
|
||||
Child child = new Child();
|
||||
child.id = "1.1";
|
||||
child.name = "Name Two";
|
||||
listIn.child = Arrays.asList(in.child, child);
|
||||
doc = binder.toSolrInputDocument(listIn);
|
||||
assertEquals(2, doc.getChildDocuments().size());
|
||||
assertEquals(2, ClientUtils.toSolrDocument(doc).getChildDocuments().size());
|
||||
ListChild listOut = binder.getBean(ListChild.class, ClientUtils.toSolrDocument(doc));
|
||||
assertEquals(listIn.id, listOut.id);
|
||||
assertEquals(listIn.child.get(0).id, listOut.child.get(0).id);
|
||||
assertEquals(listIn.child.get(0).name, listOut.child.get(0).name);
|
||||
assertEquals(listIn.child.get(1).id, listOut.child.get(1).id);
|
||||
assertEquals(listIn.child.get(1).name, listOut.child.get(1).name);
|
||||
|
||||
ArrayChild arrIn = new ArrayChild();
|
||||
arrIn.id = "3";
|
||||
arrIn.child = new Child[]{in.child, child};
|
||||
doc = binder.toSolrInputDocument(arrIn);
|
||||
assertEquals(2, doc.getChildDocuments().size());
|
||||
assertEquals(2, ClientUtils.toSolrDocument(doc).getChildDocuments().size());
|
||||
ArrayChild arrOut = binder.getBean(ArrayChild.class, ClientUtils.toSolrDocument(doc));
|
||||
assertEquals(arrIn.id, arrOut.id);
|
||||
assertEquals(arrIn.child[0].id, arrOut.child[0].id);
|
||||
assertEquals(arrIn.child[0].name, arrOut.child[0].name);
|
||||
assertEquals(arrIn.child[1].id, arrOut.child[1].id);
|
||||
assertEquals(arrIn.child[1].name, arrOut.child[1].name);
|
||||
|
||||
}
|
||||
|
||||
public static class Item {
|
||||
@Field
|
||||
String id;
|
||||
|
@ -184,7 +228,7 @@ public class TestDocumentObjectBinder extends LuceneTestCase {
|
|||
|
||||
@Field("supplier_*")
|
||||
Map<String, List<String>> supplier;
|
||||
|
||||
|
||||
@Field("sup_simple_*")
|
||||
Map<String, String> supplier_simple;
|
||||
|
||||
|
@ -192,7 +236,7 @@ public class TestDocumentObjectBinder extends LuceneTestCase {
|
|||
|
||||
@Field("supplier_*")
|
||||
public void setAllSuppliers(String[] allSuppliers) {
|
||||
this.allSuppliers = allSuppliers;
|
||||
this.allSuppliers = allSuppliers;
|
||||
}
|
||||
|
||||
public String[] getAllSuppliers() {
|
||||
|
@ -203,12 +247,47 @@ public class TestDocumentObjectBinder extends LuceneTestCase {
|
|||
public void setInStock(Boolean b) {
|
||||
inStock = b;
|
||||
}
|
||||
|
||||
|
||||
// required if you want to fill SolrDocuments with the same annotaion...
|
||||
public boolean isInStock() {
|
||||
return inStock;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Child {
|
||||
@Field
|
||||
String id;
|
||||
|
||||
@Field
|
||||
String name;
|
||||
|
||||
}
|
||||
|
||||
public static class SingleValueChild {
|
||||
@Field
|
||||
String id;
|
||||
|
||||
@Field(child = true)
|
||||
Child child;
|
||||
}
|
||||
|
||||
public static class ListChild {
|
||||
@Field
|
||||
String id;
|
||||
|
||||
@Field(child = true)
|
||||
List<Child> child;
|
||||
|
||||
}
|
||||
|
||||
public static class ArrayChild {
|
||||
|
||||
@Field
|
||||
String id;
|
||||
|
||||
@Field(child = true)
|
||||
Child[] child;
|
||||
}
|
||||
|
||||
|
||||
public static class NotGettableItem {
|
||||
|
|
Loading…
Reference in New Issue