diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/beans/DocumentObjectBinder.java b/solr/solrj/src/java/org/apache/solr/client/solrj/beans/DocumentObjectBinder.java index ef7f736eff15..c68d78fbf759 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/beans/DocumentObjectBinder.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/beans/DocumentObjectBinder.java @@ -91,8 +91,10 @@ public SolrInputDocument toSolrInputDocument(Object obj) { doc.setField(e.getKey(), e.getValue()); } } else { - if (field.child != null) { - addChild(obj, field, doc); + if (field.child != null && field.annotation.anonymizeChild()) { + addAnonymousChild(obj, field, doc); + } else if (field.child != null) { + addNestedChild(obj, field, doc); } else { doc.setField(field.name, field.get(obj)); } @@ -101,7 +103,7 @@ public SolrInputDocument toSolrInputDocument(Object obj) { return doc; } - private void addChild(Object obj, DocField field, SolrInputDocument doc) { + private void addAnonymousChild(Object obj, DocField field, SolrInputDocument doc) { Object val = field.get(obj); if (val == null) return; if (val instanceof Collection) { @@ -119,6 +121,31 @@ private void addChild(Object obj, DocField field, SolrInputDocument doc) { } } + private void addNestedChild(Object obj, DocField field, SolrInputDocument doc) { + Object val = field.get(obj); + if (val == null) return; + if (val instanceof Collection) { + @SuppressWarnings({"rawtypes"}) + Collection collection = (Collection) val; + List docs = new ArrayList<>(collection.size()); + for (final Object o : collection) { + final SolrInputDocument inpDoc = toSolrInputDocument(o); + docs.add(inpDoc); + } + val = docs; + } else if (val.getClass().isArray()) { + Object[] objs = (Object[]) val; + SolrInputDocument[] docs = (SolrInputDocument[]) Array.newInstance(SolrInputDocument.class, objs.length); + for (int i = 0; i < objs.length; i++) { + docs[i] = toSolrInputDocument(objs[i]); + } + val = docs; + } else { + val = toSolrInputDocument(val); + } + doc.addField(field.name, val); + } + private List getDocFields(@SuppressWarnings({"rawtypes"})Class clazz) { List fields = infocache.get(clazz); if (fields == null) { @@ -146,7 +173,7 @@ private List collectInfo(@SuppressWarnings({"rawtypes"})Class clazz) { if (member.isAnnotationPresent(Field.class)) { AccessController.doPrivileged((PrivilegedAction) () -> { member.setAccessible(true); return null; }); DocField df = new DocField(member); - if (df.child != null) { + if (df.child != null && df.annotation.anonymizeChild()) { if (childFieldFound) throw new BindingException(clazz.getName() + " cannot have more than one Field with child=true"); childFieldFound = true; @@ -334,7 +361,20 @@ private void populateChild(Type typ) { @SuppressWarnings({"unchecked", "rawtypes"}) private Object getFieldValue(SolrDocument solrDocument) { if (child != null) { - List children = solrDocument.getChildDocuments(); + List children = null; + if(solrDocument.hasChildDocuments()){ + children = solrDocument.getChildDocuments(); + } else if (!annotation.anonymizeChild()){ + final Object val = solrDocument.getFieldValue(name); + if(val == null){ + return null; + } else if (isList || isArray) { + children = (List) val; + } else { + children = new ArrayList<>(); + children.add((SolrDocument) val); + } + } if (children == null || children.isEmpty()) return null; if (isList) { ArrayList list = new ArrayList(children.size()); diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/beans/Field.java b/solr/solrj/src/java/org/apache/solr/client/solrj/beans/Field.java index 39f675246058..ddcb0004897a 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/beans/Field.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/beans/Field.java @@ -34,5 +34,16 @@ @Retention(RUNTIME) public @interface Field { boolean child() default false; + + /** + * When converting fields with the `@Field(child=true)` annotation, this will + * determine if they should be converted as an anonymized nested child document + * or as a regular (mapped) nested child document. + * + * If intending to use `AtomicUpdates` or `ChildDocumentTransformer`, this + * should be set to `false`. + */ + boolean anonymizeChild() default true; + String value() default DEFAULT; } diff --git a/solr/solrj/src/test/org/apache/solr/client/solrj/beans/TestDocumentObjectBinder.java b/solr/solrj/src/test/org/apache/solr/client/solrj/beans/TestDocumentObjectBinder.java index 4894c7476b9f..0c832c277f09 100644 --- a/solr/solrj/src/test/org/apache/solr/client/solrj/beans/TestDocumentObjectBinder.java +++ b/solr/solrj/src/test/org/apache/solr/client/solrj/beans/TestDocumentObjectBinder.java @@ -27,7 +27,9 @@ import org.junit.Test; import java.io.StringReader; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; @@ -151,10 +153,97 @@ public void testChild() throws Exception { assertEquals(arrIn.child[1].name, arrOut.child[1].name); } + public void testMappedChild() throws Exception { + DocumentObjectBinder binder = new DocumentObjectBinder(); + + + SingleValueNestedChild nestedNullChildIn = new SingleValueNestedChild(); + nestedNullChildIn.id = "1-null-child"; + nestedNullChildIn.child = null; + SolrInputDocument nestedNullSolrInputDoc = binder.toSolrInputDocument(nestedNullChildIn); + SolrDocument nestedNullSolrDoc = toSolrDocument(nestedNullSolrInputDoc); + assertNull(nestedNullSolrInputDoc.getChildDocuments()); + assertNull(nestedNullSolrDoc.getChildDocuments()); + SingleValueNestedChild nestedNullChildOut = binder.getBean(SingleValueNestedChild.class, nestedNullSolrDoc); + assertEquals(nestedNullChildIn.id, nestedNullChildOut.id); + assertNull(nestedNullChildIn.child); + assertNull(nestedNullChildOut.child); + + SingleValueNestedChild singleNestedIn = new SingleValueNestedChild(); + singleNestedIn.id = "1"; + singleNestedIn.child = new Child(); + singleNestedIn.child.id = "1.1"; + singleNestedIn.child.name = "Name One"; + SolrInputDocument singleNestedSolrInputDoc = binder.toSolrInputDocument(singleNestedIn); + SolrDocument singleNestedSolrDoc = toSolrDocument(singleNestedSolrInputDoc); + assertNull(singleNestedSolrInputDoc.getChildDocuments()); + assertNull(singleNestedSolrDoc.getChildDocuments()); + SingleValueNestedChild singleNestedOut = binder.getBean(SingleValueNestedChild.class, singleNestedSolrDoc); + assertEquals(singleNestedIn.id, singleNestedOut.id); + assertEquals(singleNestedIn.child.id, singleNestedOut.child.id); + assertEquals(singleNestedIn.child.name, singleNestedOut.child.name); + + ListNestedChild listNestedIn = new ListNestedChild(); + listNestedIn.id = "2"; + Child child = new Child(); + child.id = "1.2"; + child.name = "Name Two"; + listNestedIn.child = Arrays.asList(singleNestedIn.child, child); + SolrInputDocument listNestedSolrInputDoc = binder.toSolrInputDocument(listNestedIn); + SolrDocument listNestedSolrDoc = toSolrDocument(listNestedSolrInputDoc); + assertNull(listNestedSolrInputDoc.getChildDocuments()); + assertNull(listNestedSolrDoc.getChildDocuments()); + ListNestedChild listNestedOut = binder.getBean(ListNestedChild.class, listNestedSolrDoc); + assertEquals(listNestedIn.id, listNestedOut.id); + assertEquals(listNestedIn.child.get(0).id, listNestedOut.child.get(0).id); + assertEquals(listNestedIn.child.get(0).name, listNestedOut.child.get(0).name); + assertEquals(listNestedIn.child.get(1).id, listNestedOut.child.get(1).id); + assertEquals(listNestedIn.child.get(1).name, listNestedOut.child.get(1).name); + + ArrayNestedChild arrayNestedIn = new ArrayNestedChild(); + arrayNestedIn.id = "3"; + arrayNestedIn.child = new Child[]{singleNestedIn.child, child}; + SolrInputDocument arrayNestedSolrInputDoc = binder.toSolrInputDocument(arrayNestedIn); + SolrDocument arrayNestedSolrDoc = toSolrDocument(arrayNestedSolrInputDoc); + assertNull(arrayNestedSolrInputDoc.getChildDocuments()); + assertNull(arrayNestedSolrDoc.getChildDocuments()); + ArrayNestedChild arrayNestedOut = binder.getBean(ArrayNestedChild.class, arrayNestedSolrDoc); + assertEquals(arrayNestedIn.id, arrayNestedOut.id); + assertEquals(arrayNestedIn.child[0].id, arrayNestedOut.child[0].id); + assertEquals(arrayNestedIn.child[0].name, arrayNestedOut.child[0].name); + assertEquals(arrayNestedIn.child[1].id, arrayNestedOut.child[1].id); + assertEquals(arrayNestedIn.child[1].name, arrayNestedOut.child[1].name); + } private static SolrDocument toSolrDocument(SolrInputDocument d) { SolrDocument doc = new SolrDocument(); for (SolrInputField field : d) { + if (field.getValue() != null) { + if (field.getValue() instanceof Collection) { + Collection values = (Collection) field.getValue(); + if (!values.isEmpty() && values.iterator().next() instanceof SolrInputDocument) { + List docs = new ArrayList<>(values.size()); + for (Object value : values) { + docs.add(toSolrDocument((SolrInputDocument) value)); + } + doc.setField(field.getName(), docs); + continue; + } + } else if (field.getValue().getClass().isArray()) { + Object[] values = (Object[]) field.getValue(); + if (values.length > 0 && values[0] instanceof SolrInputDocument) { + SolrDocument[] docs = new SolrDocument[values.length]; + for (int i = 0; i < values.length; i++) { + docs[i] = toSolrDocument((SolrInputDocument) values[i]); + } + doc.setField(field.getName(), docs); + continue; + } + } else if (field.getValue() instanceof SolrInputDocument) { + doc.setField(field.getName(), toSolrDocument((SolrInputDocument) field.getValue())); + continue; + } + } doc.setField(field.getName(), field.getValue()); } if (d.getChildDocuments() != null) { @@ -245,7 +334,32 @@ public static class ArrayChild { @Field(child = true) Child[] child; } - + + public static class SingleValueNestedChild { + @Field + String id; + + @Field(child = true, anonymizeChild = false) + Child child; + } + + public static class ListNestedChild { + + @Field + String id; + + @Field(child = true, anonymizeChild = false) + List child; + } + + public static class ArrayNestedChild { + + @Field + String id; + + @Field(child = true, anonymizeChild = false) + Child[] child; + } public static class NotGettableItem { @Field