Skip to content

Commit

Permalink
CAPI-571 Fix SolrJ Bind Nested Child DTO Fields
Browse files Browse the repository at this point in the history
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.

Add a new property to `@Field` interface to determine if
a `Field` annoted with `child=true` should be treated as
anonymous field or if it should be treated as a nested
child document. Where they should be nested child docs, each
nested object would be converted to its own `SolrInputDocument`
and added as a regular field to the parent `SolrInputDocument`.

Having it as a regular field with nested objects will result
in Solr indexing these child docs correctly, and it will populate
appropriate nested child docs fields during index time. We can
now have more that one child documents fields listed in the DTO
until they are set and `anonymizeChild=false`.
  • Loading branch information
yfernando-bw committed Feb 5, 2024
1 parent d06ab17 commit 9481840
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand All @@ -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) {
Expand All @@ -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<SolrInputDocument> 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<DocField> getDocFields(@SuppressWarnings({"rawtypes"})Class clazz) {
List<DocField> fields = infocache.get(clazz);
if (fields == null) {
Expand Down Expand Up @@ -146,7 +173,7 @@ private List<DocField> collectInfo(@SuppressWarnings({"rawtypes"})Class clazz) {
if (member.isAnnotationPresent(Field.class)) {
AccessController.doPrivileged((PrivilegedAction<Void>) () -> { 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;
Expand Down Expand Up @@ -334,7 +361,20 @@ private void populateChild(Type typ) {
@SuppressWarnings({"unchecked", "rawtypes"})
private Object getFieldValue(SolrDocument solrDocument) {
if (child != null) {
List<SolrDocument> children = solrDocument.getChildDocuments();
List<SolrDocument> 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<SolrDocument>) val;
} else {
children = new ArrayList<>();
children.add((SolrDocument) val);
}
}
if (children == null || children.isEmpty()) return null;
if (isList) {
ArrayList list = new ArrayList(children.size());
Expand Down
11 changes: 11 additions & 0 deletions solr/solrj/src/java/org/apache/solr/client/solrj/beans/Field.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<SolrDocument> 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) {
Expand Down Expand Up @@ -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> child;
}

public static class ArrayNestedChild {

@Field
String id;

@Field(child = true, anonymizeChild = false)
Child[] child;
}

public static class NotGettableItem {
@Field
Expand Down

0 comments on commit 9481840

Please sign in to comment.