package org.infinispan.query.blackbox;

import static java.util.Arrays.asList;
import static org.infinispan.query.helper.TestQueryHelperFactory.createCacheQuery;
import static org.infinispan.query.helper.TestQueryHelperFactory.createQueryParser;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertTrue;

import java.util.List;
import java.util.NoSuchElementException;

import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.QueryWrapperFilter;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.hibernate.search.filter.FullTextFilter;
import org.infinispan.Cache;
import org.infinispan.cache.impl.CacheImpl;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.cache.Index;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.query.CacheQuery;
import org.infinispan.query.FetchOptions;
import org.infinispan.query.ResultIterator;
import org.infinispan.query.Search;
import org.infinispan.query.SearchManager;
import org.infinispan.query.helper.StaticTestingErrorHandler;
import org.infinispan.query.spi.SearchManagerImplementor;
import org.infinispan.query.test.AnotherGrassEater;
import org.infinispan.query.test.CustomKey3;
import org.infinispan.query.test.CustomKey3Transformer;
import org.infinispan.query.test.Person;
import org.infinispan.test.SingleCacheManagerTest;
import org.infinispan.test.fwk.TestCacheManagerFactory;
import org.testng.annotations.Test;

@Test(groups = {"functional", "smoke"}, testName = "query.blackbox.LocalCacheTest")
public class LocalCacheTest extends SingleCacheManagerTest {
   protected Person person1;
   protected Person person2;
   protected Person person3;
   protected Person person4;
   protected AnotherGrassEater anotherGrassEater;
   protected QueryParser queryParser;
   protected String key1 = "Navin";
   protected String key2 = "BigGoat";
   protected String key3 = "MiniGoat";
   protected String anotherGrassEaterKey = "anotherGrassEaterKey";

   public LocalCacheTest() {
      cleanup = CleanupPhase.AFTER_METHOD;
   }

   public void testSimple() throws ParseException {
      loadTestingData();
      CacheQuery<Person> cacheQuery = createCacheQuery(cache, "blurb", "playing" );

      List<Person> found = cacheQuery.list();

      int elems = found.size();
      assert elems == 1 : "Expected 1 but was " + elems;

      Person val = found.get(0);
      assert val.equals(person1) : "Expected " + person1 + " but was " + val;
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   public void testSimpleForNonField() throws ParseException {
      loadTestingData();
      CacheQuery<?> cacheQuery = createCacheQuery(cache, "nonSearchableField", "test1" );
      List<?> found = cacheQuery.list();

      int elems = found.size();
      assert elems == 0 : "Expected 0 but was " + elems;
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   public void testEagerIterator() throws ParseException {
      loadTestingData();
      CacheQuery<?> cacheQuery = createCacheQuery(cache, "blurb", "playing");

      ResultIterator<?> found = cacheQuery.iterator(new FetchOptions().fetchMode(FetchOptions.FetchMode.EAGER));

      try {
         assert found.hasNext();
         found.next();
         assert !found.hasNext();
      } finally {
         found.close();
      }
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   @Test(expectedExceptions = UnsupportedOperationException.class)
   public void testEagerIteratorRemove() throws ParseException {
      loadTestingData();
      CacheQuery<?> cacheQuery = createCacheQuery(cache, "blurb", "playing");

      ResultIterator<?> found = cacheQuery.iterator(new FetchOptions().fetchMode(FetchOptions.FetchMode.EAGER));

      try {
         assert found.hasNext();
         found.remove();
      } finally {
         found.close();
      }
   }

   @Test(expectedExceptions = NoSuchElementException.class)
   public void testEagerIteratorExCase() throws ParseException {
      loadTestingData();
      CacheQuery<?> cacheQuery = createCacheQuery(cache, "blurb", "playing");

      ResultIterator<?> found = cacheQuery.iterator(new FetchOptions().fetchMode(FetchOptions.FetchMode.EAGER));

      try {
         assert found.hasNext();
         found.next();
         assert !found.hasNext();
         found.next();
      } finally {
         found.close();
      }
   }

   public void testMultipleResults() throws ParseException {
      loadTestingData();
      queryParser = createQueryParser("name");

      Query luceneQuery = queryParser.parse("goat");
      CacheQuery<Person> cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);
      List<Person> found = cacheQuery.list();

      assert found.size() == 2;
      assertTrue(found.contains(person2));
      assertTrue(found.contains(person3));
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   public void testModified() throws ParseException {
      loadTestingData();
      queryParser = createQueryParser("blurb");
      Query luceneQuery = queryParser.parse("playing");
      CacheQuery<Person> cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);

      List<Person> found = cacheQuery.list();

      assert found.size() == 1;
      assert found.get(0).equals(person1);

      person1.setBlurb("Likes pizza");
      cache.put(key1, person1);

      queryParser = createQueryParser("blurb");
      luceneQuery = queryParser.parse("pizza");
      cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);

      found = cacheQuery.list();

      assert found.size() == 1;
      assert found.get(0).equals(person1);
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   public void testAdded() throws ParseException {
      loadTestingData();
      queryParser = createQueryParser("name");

      Query luceneQuery = queryParser.parse("Goat");
      CacheQuery<Person> cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);
      List<Person> found = cacheQuery.list();

      assert found.size() == 2 : "Size of list should be 2";
      assert found.contains(person2);
      assert found.contains(person3);
      assert !found.contains(person4) : "This should not contain object person4";

      person4 = new Person();
      person4.setName("Mighty Goat");
      person4.setBlurb("Also eats grass");

      cache.put("mighty", person4);

      luceneQuery = queryParser.parse("Goat");
      cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);
      found = cacheQuery.list();

      assert found.size() == 3 : "Size of list should be 3";
      assert found.contains(person2);
      assert found.contains(person3);
      assert found.contains(person4) : "This should now contain object person4";
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   public void testRemoved() throws ParseException {
      loadTestingData();
      queryParser = createQueryParser("name");

      Query luceneQuery = queryParser.parse("Goat");
      CacheQuery<Person> cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);
      List<Person> found = cacheQuery.list();

      assert found.size() == 2;
      assert found.contains(person2);
      assert found.contains(person3) : "This should still contain object person3";

      cache.remove(key3);

      luceneQuery = queryParser.parse("Goat");
      cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);
      found = cacheQuery.list();

      assert found.size() == 1;
      assert found.contains(person2);
      assert !found.contains(person3) : "The search should not return person3";
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   public void testUpdated() throws ParseException {
      loadTestingData();
      queryParser = createQueryParser("name");

      Query luceneQuery = queryParser.parse("Goat");
      CacheQuery<Person> cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);
      List<Person> found = cacheQuery.list();

      assert found.size() == 2 : "Size of list should be 2";
      assert found.contains(person2) : "The search should have person2";

      cache.put(key2, person1);

      luceneQuery = queryParser.parse("Goat");
      cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);
      found = cacheQuery.list();

      assert found.size() == 1 : "Size of list should be 1";
      assert !found.contains(person2) : "Person 2 should not be found now";
      assert !found.contains(person1) : "Person 1 should not be found because it does not meet the search criteria";
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   public void testSetSort() throws ParseException {
      loadTestingData();

      Sort sort = new Sort(new SortField("age", SortField.Type.INT));

      queryParser = createQueryParser("name");

      Query luceneQuery = queryParser.parse("Goat");
      {
         CacheQuery<Person> cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);
         List<Person> found = cacheQuery.list();

         assert found.size() == 2;

         cacheQuery.sort(sort);

         found = cacheQuery.list();

         assert found.size() == 2;
         assert found.get(0).equals(person3); // person3 is 25 and named Goat
         assert found.get(1).equals(person2); // person2 is 30 and named Goat
      }
      StaticTestingErrorHandler.assertAllGood(cache);

      //Now change the stored values:
      person2.setAge(10);
      cache.put(key2, person2);

      {
         CacheQuery<Person> cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);
         List<Person> found = cacheQuery.list();

         assert found.size() == 2;

         cacheQuery.sort(sort);

         found = cacheQuery.list();

         assert found.size() == 2;
         assert found.get(0).equals(person2); // person2 is 10 and named Goat
         assert found.get(1).equals(person3); // person3 is 25 and named Goat
      }
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   public void testSetFilter() throws ParseException {
      loadTestingData();
      queryParser = createQueryParser("name");

      Query luceneQuery = queryParser.parse("goat");
      CacheQuery<?> cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);
      List<?> found = cacheQuery.list();

      assert found.size() == 2;

      BooleanQuery filter = new BooleanQuery.Builder().add(new PrefixQuery(new Term("blurb", "cheese")), Occur.FILTER).build();

      cacheQuery.filter(new QueryWrapperFilter(filter));

      found = cacheQuery.list();

      assert found.size() == 1;
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   public void testLazyIterator() throws ParseException {
      loadTestingData();
      queryParser = createQueryParser("blurb");
      Query luceneQuery = queryParser.parse("playing");
      CacheQuery<?> cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);

      ResultIterator<?> found = cacheQuery.iterator(new FetchOptions().fetchMode(FetchOptions.FetchMode.LAZY));

      try {
         assert found.hasNext();
         found.next();
         assert !found.hasNext();
      } finally {
         found.close();
      }
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "Unknown FetchMode null")
   public void testUnknownFetchModeIterator() throws ParseException {
      loadTestingData();
      queryParser = createQueryParser("blurb");
      Query luceneQuery = queryParser.parse("playing");
      CacheQuery<?> cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);

      ResultIterator<?> found = cacheQuery.iterator(new FetchOptions(){
         public FetchOptions fetchMode(FetchMode fetchMode) {
            return null;
         }
      });

      try {
         assert found.hasNext();
         found.next();
         assert !found.hasNext();
      } finally {
         found.close();
      }
   }

   public void testIteratorWithDefaultOptions() throws ParseException {
      loadTestingData();
      queryParser = createQueryParser("blurb");
      Query luceneQuery = queryParser.parse("playing");
      CacheQuery<?> cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);

      ResultIterator<?> found = cacheQuery.iterator();

      try {
         assert found.hasNext();
         found.next();
         assert !found.hasNext();
      } finally {
         found.close();
      }
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   public void testExplain() throws ParseException {
      loadTestingData();
      queryParser = createQueryParser("blurb");
      Query luceneQuery = queryParser.parse("Eats");
      CacheQuery<?> cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);

      int matchCounter = 0;
      int i = 0;

      //The implementation is changed to this way as in case of NRT index manager the number of created documents may
      //differ comparing to the simple configuration.
      while (true) {
         try {
            Explanation found = cacheQuery.explain(i);

            if (found.isMatch())
               matchCounter++;

            i++;
            if (i >= 10 || matchCounter == 3)
                break;
         } catch(ArrayIndexOutOfBoundsException ex) {
            break;
         }
      }

      assertEquals(3, matchCounter);
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   public void testFullTextFilterOnOff() throws ParseException {
      loadTestingData();
      queryParser = createQueryParser("blurb");
      Query luceneQuery = queryParser.parse("Eats");

      CacheQuery<Person> query = Search.getSearchManager(cache).getQuery(luceneQuery);
      FullTextFilter filter = query.enableFullTextFilter("personFilter");
      filter.setParameter("blurbText", "cheese");

      assertEquals(1, query.getResultSize());
      List<Person> result = query.list();

      Person person = result.get(0);
      assertEquals("Mini Goat", person.getName());
      assertEquals("Eats cheese", person.getBlurb());

      //Disabling the fullTextFilter.
      query.disableFullTextFilter("personFilter");
      assertEquals(3, query.getResultSize());
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   @Test(expectedExceptions = UnsupportedOperationException.class)
   public void testIteratorRemove() throws ParseException {
      loadTestingData();

      queryParser = createQueryParser("blurb");
      Query luceneQuery = queryParser.parse("Eats");

      CacheQuery<?> cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);
      ResultIterator<?> iterator = cacheQuery.iterator();
      try {
         if (iterator.hasNext()) {
            iterator.next();
            iterator.remove();
         }
      } finally {
         iterator.close();
      }
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   public void testLazyIteratorWithOffset() throws ParseException {
      loadTestingData();
      queryParser = createQueryParser("blurb");
      Query luceneQuery = queryParser.parse("Eats");
      CacheQuery<?> cacheQuery = Search.getSearchManager(cache).<Person>getQuery(luceneQuery).firstResult(1);

      ResultIterator<?> iterator = cacheQuery.iterator(new FetchOptions().fetchMode(FetchOptions.FetchMode.LAZY));
      try {
         assertEquals(2, countElements(iterator));
      } finally {
         iterator.close();
      }
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   @Test(expectedExceptions = IllegalArgumentException.class)
   public void testSearchManagerWithNullCache() throws ParseException {
      loadTestingData();
      queryParser = createQueryParser("blurb");
      Query luceneQuery = queryParser.parse("fish");
      Search.getSearchManager(null).getQuery(luceneQuery).firstResult(1);
   }

   @Test(expectedExceptions = IllegalArgumentException.class)
   public void testLazyIteratorWithInvalidFetchSize() throws ParseException {
      loadTestingData();
      queryParser = createQueryParser("blurb");
      Query luceneQuery = queryParser.parse("Eats");
      CacheQuery<?> cacheQuery = Search.getSearchManager(cache).<Person>getQuery(luceneQuery).firstResult(1);

      cacheQuery.iterator(new FetchOptions().fetchMode(FetchOptions.FetchMode.LAZY).fetchSize(0));
   }

   @Test(expectedExceptions = NoSuchElementException.class)
   public void testLazyIteratorWithNoElementsFound() throws ParseException {
      loadTestingData();
      queryParser = createQueryParser("blurb");
      Query luceneQuery = queryParser.parse("fish");
      CacheQuery<?> cacheQuery = Search.getSearchManager(cache).<Person>getQuery(luceneQuery).firstResult(1);

      ResultIterator<?> found = cacheQuery.iterator(new FetchOptions().fetchMode(FetchOptions.FetchMode.LAZY));

      try {
         found.next();
      } finally {
         found.close();
      }
   }

   @Test(expectedExceptions = IllegalArgumentException.class)
   public void testIteratorWithNullFetchMode() throws ParseException {
      loadTestingData();
      queryParser = createQueryParser("blurb");
      Query luceneQuery = queryParser.parse("Eats");
      CacheQuery<?> cacheQuery = Search.getSearchManager(cache).<Person>getQuery(luceneQuery).firstResult(1);

      ResultIterator<?> found = cacheQuery.iterator(new FetchOptions().fetchMode(null));

      try {
         found.next();
      } finally {
         found.close();
      }
   }

   public void testSearchKeyTransformer() throws ParseException {
      SearchManagerImplementor manager = (SearchManagerImplementor) Search.getSearchManager(cache);
      manager.registerKeyTransformer(CustomKey3.class, CustomKey3Transformer.class);

      loadTestingDataWithCustomKey();
      queryParser = createQueryParser("blurb");
      Query luceneQuery = queryParser.parse("Eats");

      CacheQuery<?> cacheQuery = manager.getQuery(luceneQuery);

      ResultIterator<?> iterator = cacheQuery.iterator(new FetchOptions().fetchMode(FetchOptions.FetchMode.LAZY));
      try {
         assertEquals(3, countElements(iterator));
      } finally {
         iterator.close();
      }
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   @Test(expectedExceptions = IllegalArgumentException.class)
   public void testSearchWithWrongCache() throws ParseException {
      Cache cache = mock(CacheImpl.class);
      when(cache.getAdvancedCache()).thenReturn(null);

      SearchManager manager = Search.getSearchManager(cache);
   }

   //Another test just for covering Search.java instantiation, although it is unnecessary. As well as covering the
   //valueOf() method of FetchMode, again just for adding coverage.
   public void testSearchManagerWithInstantiation() throws ParseException {
      loadTestingData();
      queryParser = createQueryParser("blurb");
      Query luceneQuery = queryParser.parse("playing");

      CacheQuery<?> cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);

      ResultIterator<?> found = cacheQuery.iterator(new FetchOptions().fetchMode(FetchOptions.FetchMode.valueOf("LAZY")));

      try {
         assert found.hasNext();
         found.next();
         assert !found.hasNext();
      } finally {
         found.close();
      }
   }

   public void testGetResultSize() throws ParseException {
      loadTestingData();
      queryParser = createQueryParser("blurb");
      Query luceneQuery = queryParser.parse("playing");
      CacheQuery<?> cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);

      assert cacheQuery.getResultSize() == 1;
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   public void testMaxResults() throws ParseException {
      loadTestingData();

      queryParser = createQueryParser("blurb");
      Query luceneQuery = queryParser.parse("eats");

      CacheQuery<?> cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery)
            .maxResults(1);

      assertEquals(3, cacheQuery.getResultSize());   // NOTE: getResultSize() ignores pagination (maxResults, firstResult)
      assertEquals(1, cacheQuery.list().size());
      ResultIterator<?> eagerIterator = cacheQuery.iterator(new FetchOptions().fetchMode(FetchOptions.FetchMode.EAGER));
      try {
         assertEquals(1, countElements(eagerIterator));
      } finally {
         eagerIterator.close();
      }
      ResultIterator<?> lazyIterator = cacheQuery.iterator(new FetchOptions().fetchMode(FetchOptions.FetchMode.LAZY));
      try {
         assertEquals(1, countElements(lazyIterator));
      } finally {
         lazyIterator.close();
      }
      ResultIterator<?> defaultIterator = cacheQuery.iterator();
      try {
         assertEquals(1, countElements(defaultIterator));
      } finally {
         defaultIterator.close();
      }
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   private int countElements(ResultIterator<?> iterator) {
      int count = 0;
      while (iterator.hasNext()) {
         iterator.next();
         count++;
      }
      return count;
   }

   public void testClear() {
      loadTestingData();

      // Create a term that will return me everyone called Navin.
      Term navin = new Term("name", "navin");

      // Create a term that I know will return me everything with name goat.
      Term goat = new Term("name", "goat");

      BooleanQuery luceneQuery = new BooleanQuery.Builder()
              .add(new TermQuery(goat), Occur.SHOULD)
              .add(new TermQuery(navin), Occur.SHOULD)
              .build();
      CacheQuery<Person> cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);

      // We know that we've got all 3 hits.
      assert cacheQuery.getResultSize() == 3 : "Expected 3, got " + cacheQuery.getResultSize();

      cache.clear();

      cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);

      assert cacheQuery.getResultSize() == 0;
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   public void testTypeFiltering() throws ParseException {
      loadTestingData();
      queryParser = createQueryParser("blurb");
      Query luceneQuery = queryParser.parse("grass");
      CacheQuery<?> cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery);

      List<?> found = cacheQuery.list();

      assert found.size() == 2;
      assert found.containsAll(asList(person2, anotherGrassEater));

      queryParser = createQueryParser("blurb");
      luceneQuery = queryParser.parse("grass");
      cacheQuery = Search.getSearchManager(cache).getQuery(luceneQuery, AnotherGrassEater.class);

      found = cacheQuery.list();

      assert found.size() == 1;
      assert found.get(0).equals(anotherGrassEater);
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   @Override
   protected EmbeddedCacheManager createCacheManager() throws Exception {
      ConfigurationBuilder cfg = getDefaultStandaloneCacheConfig(true);
      cfg
         .indexing()
            .index(Index.ALL)
            .addIndexedEntity(Person.class)
            .addIndexedEntity(AnotherGrassEater.class)
            .addProperty("default.directory_provider", "ram")
            .addProperty("error_handler", "org.infinispan.query.helper.StaticTestingErrorHandler")
            .addProperty("lucene_version", "LUCENE_CURRENT");
      enhanceConfig(cfg);
      return TestCacheManagerFactory.createCacheManager(cfg);
   }

   protected void loadTestingData() {
      prepareTestingData();

      cache.put(key1, person1);

      // person2 is verified as number of matches in multiple tests,
      // so verify duplicate insertion doesn't affect result counts:
      cache.put(key2, person2);
      cache.put(key2, person2);
      cache.put(key2, person2);

      cache.put(key3, person3);
      cache.put(anotherGrassEaterKey, anotherGrassEater);
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   protected void loadTestingDataWithCustomKey() {
      prepareTestingData();

      CustomKey3 customeKey1 = new CustomKey3(key1);
      CustomKey3 customeKey2 = new CustomKey3(key2);
      CustomKey3 customeKey3 = new CustomKey3(key3);
      cache.put(customeKey1, person1);

      // person2 is verified as number of matches in multiple tests,
      // so verify duplicate insertion doesn't affect result counts:
      cache.put(customeKey2, person2);
      cache.put(customeKey2, person2);
      cache.put(customeKey2, person2);

      cache.put(customeKey3, person3);
      cache.put(anotherGrassEaterKey, anotherGrassEater);
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   protected void prepareTestingData() {
      person1 = new Person();
      person1.setName("Navin Surtani");
      person1.setAge(20);
      person1.setBlurb("Likes playing WoW");
      person1.setNonSearchableField("test1");

      person2 = new Person();
      person2.setName("Big Goat");
      person2.setAge(30);
      person2.setBlurb("Eats grass");
      person2.setNonSearchableField("test2");

      person3 = new Person();
      person3.setName("Mini Goat");
      person3.setAge(25);
      person3.setBlurb("Eats cheese");
      person3.setNonSearchableField("test3");

      anotherGrassEater = new AnotherGrassEater("Another grass-eater", "Eats grass");
      StaticTestingErrorHandler.assertAllGood(cache);
   }

   protected void enhanceConfig(ConfigurationBuilder c) {
      // no op, meant to be overridden
   }

}
