From 5da8927947da4568d933eb642b3bb13f8b92e827 Mon Sep 17 00:00:00 2001 From: Yohan Fernando Date: Fri, 2 Feb 2024 21:30:49 +0000 Subject: [PATCH] CAPI-571 Fix SolrJ Convert Child Docs Correctly The current solrj Document DTO to SolrInputDocument binder treat all child docs as anonymous rather than prefix them with the given path. This will result in the document being indexed without the `_nest_path_` field populated which is required for the ChildDocumentTransformer (`fl=[child]`) to work. In order to fix this if the Document DTO contain any fields which are also a DTO or a List/Array of DTOs, they should be converted as SolrInputDocuments on its own and nested as sub document(s) within the parent document. --- .../solrj/beans/DocumentObjectBinder.java | 79 +++++++++++- .../solrj/beans/TestDocumentObjectBinder.java | 113 +++++++++++++++++- 2 files changed, 188 insertions(+), 4 deletions(-) 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..47e6ef9ae4c7 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 @@ -94,13 +94,66 @@ public SolrInputDocument toSolrInputDocument(Object obj) { if (field.child != null) { addChild(obj, field, doc); } else { - doc.setField(field.name, field.get(obj)); + addField(obj, field, doc); } } } return doc; } + private void addField(Object obj, DocField field, SolrInputDocument doc) { + Object val = field.get(obj); + if (val != null && isMappedObject(val)) { + 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 boolean isMappedObject(Object obj) { + Object first; + if (obj instanceof Collection) { + @SuppressWarnings({"rawtypes"}) + Collection collection = (Collection) obj; + if (collection.isEmpty()) { + return false; + } + first = collection.iterator().next(); + } else if (obj.getClass().isArray()) { + Object[] objs = (Object[]) obj; + if (objs.length == 0) { + return false; + } + first = objs[0]; + } else { + first = obj; + } + + return isMappedClass(first.getClass()); + } + + private boolean isMappedClass(Class clazz) { + return !getDocFields(clazz).isEmpty(); + } + private void addChild(Object obj, DocField field, SolrInputDocument doc) { Object val = field.get(obj); if (val == null) return; @@ -249,6 +302,16 @@ private void storeType() { if (annotation.child()) { populateChild(field.getGenericType()); } else { + try { + Class parameterizedType = Class.forName(((ParameterizedType) field.getGenericType()) + .getActualTypeArguments()[0].getTypeName()); + if (isMappedClass(parameterizedType)) { + type = parameterizedType; + return; + } + } catch (ClassNotFoundException e) { + throw new BindingException("Invalid type information available for " + (field == null ? setter : field)); + } type = Object.class; } } else if (type == byte[].class) { @@ -333,8 +396,18 @@ private void populateChild(Type typ) { */ @SuppressWarnings({"unchecked", "rawtypes"}) private Object getFieldValue(SolrDocument solrDocument) { - if (child != null) { - List children = solrDocument.getChildDocuments(); + if (child != null || isMappedClass(type)) { + List children = null; + if(solrDocument.hasChildDocuments()){ + children = solrDocument.getChildDocuments(); + } else if (solrDocument.getFieldValue(name) != null){ + if (isList || isArray) { + children = (List) solrDocument.getFieldValue(name); + } else { + children = new ArrayList<>(); + children.add((SolrDocument) solrDocument.getFieldValue(name)); + } + } if (children == null || children.isEmpty()) return null; if (isList) { ArrayList list = new ArrayList(children.size()); 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..67bc95ecb8db 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,94 @@ public void testChild() throws Exception { assertEquals(arrIn.child[1].name, arrOut.child[1].name); } + public void testMappedChild() throws Exception { + SingleValueMappedChild in = new SingleValueMappedChild(); + in.id = "1"; + in.child = new Child(); + in.child.id = "1.0"; + in.child.name = "Name One"; + DocumentObjectBinder binder = new DocumentObjectBinder(); + SolrInputDocument solrInputDoc = binder.toSolrInputDocument(in); + SolrDocument solrDoc = toSolrDocument(solrInputDoc); + assertNull(solrInputDoc.getChildDocuments()); + assertNull(solrDoc.getChildDocuments()); + SingleValueMappedChild out = binder.getBean(SingleValueMappedChild.class, solrDoc); + assertEquals(in.id, out.id); + assertEquals(in.child.id, out.child.id); + assertEquals(in.child.name, out.child.name); + + SingleValueMappedChild inNoChild = new SingleValueMappedChild(); + in.id = "1-no-child"; + SolrInputDocument solrInputDocNoChild = binder.toSolrInputDocument(inNoChild); + SolrDocument solrDocNoChild = toSolrDocument(solrInputDocNoChild); + assertNull(solrInputDocNoChild.getChildDocuments()); + assertNull(solrDocNoChild.getChildDocuments()); + SingleValueMappedChild outNoChild = binder.getBean(SingleValueMappedChild.class, solrDocNoChild); + assertEquals(inNoChild.id, outNoChild.id); + assertNull(inNoChild.child); + assertNull(outNoChild.child); + + ListMappedChild listIn = new ListMappedChild(); + listIn.id = "2"; + Child child = new Child(); + child.id = "1.1"; + child.name = "Name Two"; + listIn.child = Arrays.asList(in.child, child); + solrInputDoc = binder.toSolrInputDocument(listIn); + solrDoc = toSolrDocument(solrInputDoc); + assertNull(solrInputDoc.getChildDocuments()); + assertNull(solrDoc.getChildDocuments()); + ListMappedChild listOut = binder.getBean(ListMappedChild.class, solrDoc); + 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); + + ArrayMappedChild arrIn = new ArrayMappedChild(); + arrIn.id = "3"; + arrIn.child = new Child[]{in.child, child}; + solrInputDoc = binder.toSolrInputDocument(arrIn); + solrDoc = toSolrDocument(solrInputDoc); + assertNull(solrInputDoc.getChildDocuments()); + assertNull(solrDoc.getChildDocuments()); + ArrayMappedChild arrOut = binder.getBean(ArrayMappedChild.class, solrDoc); + 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); + } 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 +331,32 @@ public static class ArrayChild { @Field(child = true) Child[] child; } - + + public static class SingleValueMappedChild { + @Field + String id; + + @Field + Child child; + } + + public static class ListMappedChild { + + @Field + String id; + + @Field + List child; + } + + public static class ArrayMappedChild { + + @Field + String id; + + @Field + Child[] child; + } public static class NotGettableItem { @Field