GEODE-7208: FlatFormatSerializer should index on inherited fields (#4058)


(cherry picked from commit 4ec87419d456748a7d853e979c90ad4e301b2405)
diff --git a/geode-lucene/geode-lucene-test/src/main/java/org/apache/geode/cache/lucene/test/Customer.java b/geode-lucene/geode-lucene-test/src/main/java/org/apache/geode/cache/lucene/test/Customer.java
index c405f87..d213c06 100644
--- a/geode-lucene/geode-lucene-test/src/main/java/org/apache/geode/cache/lucene/test/Customer.java
+++ b/geode-lucene/geode-lucene-test/src/main/java/org/apache/geode/cache/lucene/test/Customer.java
@@ -18,10 +18,10 @@
 import java.util.Collection;
 
 public class Customer implements Serializable {
-  private String name;
-  private Collection<String> phoneNumbers;
-  private Collection<Person> contacts;
-  private Page[] myHomePages;
+  protected String name;
+  protected Collection<String> phoneNumbers;
+  protected Collection<Person> contacts;
+  protected Page[] myHomePages;
 
   public Customer(String name, Collection<String> phoneNumbers, Collection<Person> contacts,
       Page[] myHomePages) {
@@ -37,7 +37,8 @@
 
   @Override
   public String toString() {
-    return "Customer[name=" + name + ",phoneNumbers=" + phoneNumbers + ",contacts=" + contacts
+    return this.getClass() + "[name=" + name + ",phoneNumbers=" + phoneNumbers + ",contacts="
+        + contacts
         + ",homepage=" + myHomePages + "]";
   }
 }
diff --git a/geode-lucene/geode-lucene-test/src/main/java/org/apache/geode/cache/lucene/test/GrandSubCustomer.java b/geode-lucene/geode-lucene-test/src/main/java/org/apache/geode/cache/lucene/test/GrandSubCustomer.java
new file mode 100644
index 0000000..8870385
--- /dev/null
+++ b/geode-lucene/geode-lucene-test/src/main/java/org/apache/geode/cache/lucene/test/GrandSubCustomer.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.cache.lucene.test;
+
+import java.util.Collection;
+
+public class GrandSubCustomer extends SubCustomer {
+  public GrandSubCustomer(String name,
+      Collection<String> phoneNumbers,
+      Collection<Person> contacts,
+      Page[] myHomePages) {
+    super(name, phoneNumbers, contacts, myHomePages);
+  }
+}
diff --git a/geode-lucene/geode-lucene-test/src/main/java/org/apache/geode/cache/lucene/test/SubCustomer.java b/geode-lucene/geode-lucene-test/src/main/java/org/apache/geode/cache/lucene/test/SubCustomer.java
new file mode 100644
index 0000000..35b166a
--- /dev/null
+++ b/geode-lucene/geode-lucene-test/src/main/java/org/apache/geode/cache/lucene/test/SubCustomer.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.cache.lucene.test;
+
+import java.util.Collection;
+
+public class SubCustomer extends Customer {
+  public SubCustomer(String name,
+      Collection<String> phoneNumbers,
+      Collection<Person> contacts,
+      Page[] myHomePages) {
+    super(name, phoneNumbers, contacts, myHomePages);
+  }
+}
diff --git a/geode-lucene/src/integrationTest/java/org/apache/geode/cache/lucene/NestedObjectSeralizerIntegrationTest.java b/geode-lucene/src/integrationTest/java/org/apache/geode/cache/lucene/NestedObjectSeralizerIntegrationTest.java
index 8696560..cf256b8 100644
--- a/geode-lucene/src/integrationTest/java/org/apache/geode/cache/lucene/NestedObjectSeralizerIntegrationTest.java
+++ b/geode-lucene/src/integrationTest/java/org/apache/geode/cache/lucene/NestedObjectSeralizerIntegrationTest.java
@@ -20,6 +20,7 @@
 
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.concurrent.TimeUnit;
@@ -44,12 +45,22 @@
 @Category({LuceneTest.class})
 public class NestedObjectSeralizerIntegrationTest extends LuceneIntegrationTest {
 
-  private static int WAIT_FOR_FLUSH_TIME = 10000;
-  private static final Logger logger = LogService.getLogger();
+  protected static int WAIT_FOR_FLUSH_TIME = 10000;
+  protected static final Logger logger = LogService.getLogger();
   LuceneQuery<Integer, Object> query;
   PageableLuceneQueryResults<Integer, Object> results;
 
-  private Region createRegionAndIndex() {
+  protected CustomerFactory getCustomerFactory() {
+    return Customer::new;
+  }
+
+  protected static interface CustomerFactory {
+    public Customer create(String name, Collection<String> phoneNumbers,
+        Collection<Person> contacts,
+        Page[] myHomePages);
+  }
+
+  protected Region createRegionAndIndex() {
     luceneService.createIndexFactory().setLuceneSerializer(new FlatFormatSerializer())
         .addField("name").addField("phoneNumbers").addField("myHomePages.content")
         .addField("contacts.name").addField("contacts.email", new KeywordAnalyzer())
@@ -61,7 +72,7 @@
     return region;
   }
 
-  private Region createRegionAndIndexOnInvalidFields() {
+  protected Region createRegionAndIndexOnInvalidFields() {
     luceneService.createIndexFactory().setLuceneSerializer(new FlatFormatSerializer())
         .addField("name").addField("contacts").addField("contacts.page")
         .addField("contacts.missing").addField("missing2").create(INDEX_NAME, REGION_NAME);
@@ -70,7 +81,7 @@
     return region;
   }
 
-  private void feedSomeNestedObjects(Region region) throws InterruptedException {
+  protected void feedSomeNestedObjects(Region region) throws InterruptedException {
     Person contact1 = new Person("Tommi Jackson", new String[] {"5036330001", "5036330002"}, 1);
     Person contact2 = new Person("Tommi2 Skywalker", new String[] {"5036330003", "5036330004"}, 2);
     HashSet<Person> contacts1 = new HashSet();
@@ -80,7 +91,8 @@
     ArrayList<String> phoneNumbers = new ArrayList();
     phoneNumbers.add("5035330001");
     phoneNumbers.add("5035330002");
-    Customer customer13 = new Customer("Tommy Jackson", phoneNumbers, contacts1, myHomePages1);
+    Customer customer13 =
+        getCustomerFactory().create("Tommy Jackson", phoneNumbers, contacts1, myHomePages1);
     region.put("object-13", customer13);
 
     Person contact3 = new Person("Johnni Jackson", new String[] {"5036330005", "5036330006"}, 3);
@@ -92,7 +104,9 @@
     phoneNumbers.add("5035330003");
     phoneNumbers.add("5035330004");
     Page[] myHomePages2 = new Page[] {new Page(14), new Page(141)};
-    Customer customer14 = new Customer("Johnny Jackson", phoneNumbers, contacts2, myHomePages2);
+
+    Customer customer14 =
+        getCustomerFactory().create("Johnny Jackson", phoneNumbers, contacts2, myHomePages2);
     region.put("object-14", customer14);
 
     Person contact5 = new Person("Johnni Jackson2", new String[] {"5036330009", "5036330010"}, 5);
@@ -105,7 +119,9 @@
     phoneNumbers.add("5035330005");
     phoneNumbers.add("5035330006");
     Page[] myHomePages3 = new Page[] {new Page(15), new Page(151)};
-    Customer customer15 = new Customer("Johnny Jackson2", phoneNumbers, contacts3, myHomePages3);
+
+    Customer customer15 =
+        getCustomerFactory().create("Johnny Jackson2", phoneNumbers, contacts3, myHomePages3);
     region.put("object-15", customer15);
 
     Person contact7 = new Person("Johnni Jackson21", new String[] {"5036330013", "5036330014"}, 7);
@@ -118,7 +134,9 @@
     phoneNumbers.add("5035330007");
     phoneNumbers.add("5035330008");
     Page[] myHomePages4 = new Page[] {new Page(16), new Page(161)};
-    Customer customer16 = new Customer("Johnny Jackson21", phoneNumbers, contacts4, myHomePages4);
+
+    Customer customer16 =
+        getCustomerFactory().create("Johnny Jackson21", phoneNumbers, contacts4, myHomePages4);
     region.put("object-16", customer16);
 
     region.put("key-1", "region value 1");
@@ -432,7 +450,7 @@
     assertEquals(0, results.size());
   }
 
-  private void printResults(PageableLuceneQueryResults<Integer, Object> results) {
+  protected void printResults(PageableLuceneQueryResults<Integer, Object> results) {
     if (results.size() > 0) {
       while (results.hasNext()) {
         results.next().stream().forEach(struct -> {
@@ -444,7 +462,7 @@
     }
   }
 
-  private Region createRegionAndIndexForPdxObject() {
+  protected Region createRegionAndIndexForPdxObject() {
     luceneService.createIndexFactory().setLuceneSerializer(new FlatFormatSerializer())
         .addField("ID").addField("description").addField("status").addField("names")
         .addField("position1.country").addField("position1.secId").addField("positions.secId")
@@ -454,7 +472,7 @@
     return region;
   }
 
-  private void feedSomePdxObjects(Region region) throws InterruptedException {
+  protected void feedSomePdxObjects(Region region) throws InterruptedException {
     SimplePortfolioPdx.resetCounter();
     SimplePositionPdx.resetCounter();
     for (int i = 1; i < 10; i++) {
diff --git a/geode-lucene/src/integrationTest/java/org/apache/geode/cache/lucene/NestedObjectSerializerOnGrandSubclassIntegrationTest.java b/geode-lucene/src/integrationTest/java/org/apache/geode/cache/lucene/NestedObjectSerializerOnGrandSubclassIntegrationTest.java
new file mode 100644
index 0000000..8c0cfb9
--- /dev/null
+++ b/geode-lucene/src/integrationTest/java/org/apache/geode/cache/lucene/NestedObjectSerializerOnGrandSubclassIntegrationTest.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.cache.lucene;
+
+import org.apache.geode.cache.lucene.test.GrandSubCustomer;
+
+
+public class NestedObjectSerializerOnGrandSubclassIntegrationTest
+    extends NestedObjectSeralizerIntegrationTest {
+
+  protected CustomerFactory getCustomerFactory() {
+    return GrandSubCustomer::new;
+  }
+}
diff --git a/geode-lucene/src/integrationTest/java/org/apache/geode/cache/lucene/NestedObjectSerializerOnSubclassIntegrationTest.java b/geode-lucene/src/integrationTest/java/org/apache/geode/cache/lucene/NestedObjectSerializerOnSubclassIntegrationTest.java
new file mode 100644
index 0000000..af2ce5c
--- /dev/null
+++ b/geode-lucene/src/integrationTest/java/org/apache/geode/cache/lucene/NestedObjectSerializerOnSubclassIntegrationTest.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.cache.lucene;
+
+import org.apache.geode.cache.lucene.test.SubCustomer;
+
+public class NestedObjectSerializerOnSubclassIntegrationTest
+    extends NestedObjectSeralizerIntegrationTest {
+
+  protected CustomerFactory getCustomerFactory() {
+    return SubCustomer::new;
+  }
+
+}
diff --git a/geode-lucene/src/integrationTest/java/org/apache/geode/cache/lucene/ToplevelObjectSerializerOnGrandSubclassIntegrationTest.java b/geode-lucene/src/integrationTest/java/org/apache/geode/cache/lucene/ToplevelObjectSerializerOnGrandSubclassIntegrationTest.java
new file mode 100644
index 0000000..f0c86c3
--- /dev/null
+++ b/geode-lucene/src/integrationTest/java/org/apache/geode/cache/lucene/ToplevelObjectSerializerOnGrandSubclassIntegrationTest.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.cache.lucene;
+
+import static org.apache.geode.cache.lucene.test.LuceneTestUtilities.INDEX_NAME;
+import static org.apache.geode.cache.lucene.test.LuceneTestUtilities.REGION_NAME;
+import static org.apache.geode.internal.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.logging.log4j.Logger;
+import org.junit.Test;
+
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.RegionShortcut;
+import org.apache.geode.cache.lucene.test.GrandSubCustomer;
+import org.apache.geode.internal.logging.LogService;
+
+public class ToplevelObjectSerializerOnGrandSubclassIntegrationTest extends LuceneIntegrationTest {
+  protected static int WAIT_FOR_FLUSH_TIME = 10000;
+  protected static final Logger logger = LogService.getLogger();
+  LuceneQuery<Integer, Object> query;
+  PageableLuceneQueryResults<Integer, Object> results;
+
+  protected Region createRegionAndIndex() {
+    luceneService.createIndexFactory().addField("name").create(INDEX_NAME, REGION_NAME);
+
+    Region region = createRegion(REGION_NAME, RegionShortcut.PARTITION);
+    return region;
+  }
+
+  @Test
+  public void shouldIndexOnToplevelFieldUsingDefaultSerializer()
+      throws InterruptedException, LuceneQueryException {
+    Region region = createRegionAndIndex();
+    GrandSubCustomer grandSubCustomer = new GrandSubCustomer("Tommy Jackson", null, null, null);
+    region.put("key-1", grandSubCustomer);
+    luceneService.waitUntilFlushed(INDEX_NAME, REGION_NAME, WAIT_FOR_FLUSH_TIME,
+        TimeUnit.MILLISECONDS);
+
+    query = luceneService.createLuceneQueryFactory().create(INDEX_NAME, REGION_NAME, "Tommy*",
+        "name");
+    results = query.findPages();
+    assertEquals(1, results.size());
+
+    results.next().stream().forEach(struct -> {
+      assertTrue(struct.getValue() instanceof GrandSubCustomer);
+    });
+
+  }
+}
diff --git a/geode-lucene/src/main/java/org/apache/geode/cache/lucene/FlatFormatSerializer.java b/geode-lucene/src/main/java/org/apache/geode/cache/lucene/FlatFormatSerializer.java
index 62006db..a56b1a0 100644
--- a/geode-lucene/src/main/java/org/apache/geode/cache/lucene/FlatFormatSerializer.java
+++ b/geode-lucene/src/main/java/org/apache/geode/cache/lucene/FlatFormatSerializer.java
@@ -140,13 +140,17 @@
           && SerializerUtil.supportedPrimitiveTypes().contains(clazz)) {
         return value;
       }
-      try {
-        Field field = clazz.getDeclaredField(fieldName);
-        field.setAccessible(true);
-        return field.get(value);
-      } catch (Exception e) {
-        return null;
-      }
+
+      do {
+        try {
+          Field field = clazz.getDeclaredField(fieldName);
+          field.setAccessible(true);
+          return field.get(value);
+        } catch (Exception e) {
+          clazz = clazz.getSuperclass();
+        }
+      } while (clazz != null && !clazz.equals(Object.class));
+      return null;
     }
   }
 }
diff --git a/geode-lucene/src/test/java/org/apache/geode/cache/lucene/FlatFormatSerializerJUnitTest.java b/geode-lucene/src/test/java/org/apache/geode/cache/lucene/FlatFormatSerializerJUnitTest.java
index 89e7e16..a03f6c1 100644
--- a/geode-lucene/src/test/java/org/apache/geode/cache/lucene/FlatFormatSerializerJUnitTest.java
+++ b/geode-lucene/src/test/java/org/apache/geode/cache/lucene/FlatFormatSerializerJUnitTest.java
@@ -29,6 +29,7 @@
 
 import org.apache.geode.cache.lucene.internal.repository.serializer.SerializerTestHelper;
 import org.apache.geode.cache.lucene.test.Customer;
+import org.apache.geode.cache.lucene.test.GrandSubCustomer;
 import org.apache.geode.cache.lucene.test.Page;
 import org.apache.geode.cache.lucene.test.Person;
 import org.apache.geode.test.junit.categories.LuceneTest;
@@ -87,6 +88,24 @@
   }
 
   @Test
+  public void shouldIndexOnInheritedFields() {
+    String[] fields = new String[] {"myHomePages.content"};
+
+    FlatFormatSerializer serializer = new FlatFormatSerializer();
+
+    Page[] myHomePages1 = new Page[] {new Page(131), new Page(132)};
+    GrandSubCustomer customer = new GrandSubCustomer("Tommy Jackson", null, null, myHomePages1);
+    Document doc1 = SerializerTestHelper.invokeSerializer(serializer, customer, fields);
+
+    IndexableField[] fieldsInDoc = doc1.getFields("myHomePages.content");
+    Collection<Object> results = getResultCollection(fieldsInDoc, false);
+    assertEquals(2, results.size());
+    Object value = results.iterator().next();
+    assertTrue(results.contains("Hello world no 131"));
+    assertTrue(results.contains("Hello world no 132"));
+  }
+
+  @Test
   public void shouldQueryOnFieldInCollectionObject() {
     String[] fields = new String[] {"contacts.name"};