Uploaded image for project: 'Drools'
  1. Drools
  2. DROOLS-1082

Querying for objects before any have been inserted prevents Class Aware Object Store from working properly

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Done
    • Icon: Major Major
    • 6.4.0.CR2, 7.0.0.Final
    • 6.3.0.Final
    • None
    • None
    • Hide

      Please see this test code to reproduce:

      import org.drools.core.ClassObjectFilter;
      import org.junit.After;
      import org.junit.Assert;
      import org.junit.Before;
      import org.junit.Test;
      import org.kie.api.KieBaseConfiguration;
      import org.kie.api.KieServices;
      import org.kie.api.builder.KieBuilder;
      import org.kie.api.builder.KieFileSystem;
      import org.kie.api.builder.ReleaseId;
      import org.kie.api.conf.EqualityBehaviorOption;
      import org.kie.api.runtime.KieSession;
      
      import java.io.IOException;
      import java.util.ArrayList;
      import java.util.Collection;
      
      public class QueryingCorruptsClassStoreMapTest {
      
          KieSession dataLoadDestination;
      
          @Test
          public void worksIfWeInsertBeforeQuery() throws Exception {
              dataLoadDestination.insert(new BaseClass());
              final DerivedClass element = new DerivedClass();
              dataLoadDestination.insert(element);
              assertContains(queryFor(BaseClass.class), element);
          }
      
          @Test
          public void doesntWorkIfWeQueryBeforeInsert() throws Exception {
              dataLoadDestination.insert(new BaseClass());
      
              queryFor(DerivedClass.class); // Creates non-concrete class store
      
              final DerivedClass element = new DerivedClass();
              dataLoadDestination.insert(element); // Makes concrete but doesn't resolve dependent stores
      
              assertContains(queryFor(BaseClass.class), element);
      
          }
      
      /////////////////// Everything below here is just setup
      
          @SuppressWarnings("unchecked")
          private <T> Collection<T> queryFor(Class<T> clazz) {
              return new ArrayList(dataLoadDestination.getObjects(classFilter(clazz)));
          }
      
          private <T> void assertContains(Iterable<? extends T> iterable, T item) {
              for (T t : iterable) {
                  if (t == item) {
                      return;
                  }
              }
              Assert.fail("Item not found");
          }
      
          private ClassObjectFilter classFilter(Class<?> clazz) {
              return new ClassObjectFilter(clazz);
          }
      
          public  static class BaseClass {
      
          }
      
          public static class DerivedClass extends BaseClass {
      
          }
      
          @Before
          public void setUp() throws Exception {
              dataLoadDestination = knowledgeSession();
          }
      
          @After
          public void tearDown() throws Exception {
              dataLoadDestination.dispose();
          }
      
          /** This can't be the easiest way to set up a KieSession... */
          public KieSession knowledgeSession() throws IOException {
              KieServices ks = KieServices.Factory.get();
              KieFileSystem kieFileSystem = ks.newKieFileSystem();
      
              ReleaseId rid = ks.newReleaseId("com.acme.company", "rules", "1.0");
              kieFileSystem.generateAndWritePomXML(rid);
      
              KieBuilder kieBuilder = ks.newKieBuilder(kieFileSystem);
              kieBuilder.buildAll();
      
              KieBaseConfiguration kieBaseConfiguration = ks.newKieBaseConfiguration();
              kieBaseConfiguration.setOption(EqualityBehaviorOption.EQUALITY);
              return ks.newKieContainer(rid).newKieBase(kieBaseConfiguration).newKieSession();
          }
      }
      
      Show
      Please see this test code to reproduce: import org.drools.core.ClassObjectFilter; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.kie.api.KieBaseConfiguration; import org.kie.api.KieServices; import org.kie.api.builder.KieBuilder; import org.kie.api.builder.KieFileSystem; import org.kie.api.builder.ReleaseId; import org.kie.api.conf.EqualityBehaviorOption; import org.kie.api.runtime.KieSession; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; public class QueryingCorruptsClassStoreMapTest { KieSession dataLoadDestination; @Test public void worksIfWeInsertBeforeQuery() throws Exception { dataLoadDestination.insert( new BaseClass()); final DerivedClass element = new DerivedClass(); dataLoadDestination.insert(element); assertContains(queryFor(BaseClass.class), element); } @Test public void doesntWorkIfWeQueryBeforeInsert() throws Exception { dataLoadDestination.insert( new BaseClass()); queryFor(DerivedClass.class); // Creates non-concrete class store final DerivedClass element = new DerivedClass(); dataLoadDestination.insert(element); // Makes concrete but doesn't resolve dependent stores assertContains(queryFor(BaseClass.class), element); } /////////////////// Everything below here is just setup @SuppressWarnings( "unchecked" ) private <T> Collection<T> queryFor( Class <T> clazz) { return new ArrayList(dataLoadDestination.getObjects(classFilter(clazz))); } private <T> void assertContains(Iterable<? extends T> iterable, T item) { for (T t : iterable) { if (t == item) { return ; } } Assert.fail( "Item not found" ); } private ClassObjectFilter classFilter( Class <?> clazz) { return new ClassObjectFilter(clazz); } public static class BaseClass { } public static class DerivedClass extends BaseClass { } @Before public void setUp() throws Exception { dataLoadDestination = knowledgeSession(); } @After public void tearDown() throws Exception { dataLoadDestination.dispose(); } /** This can't be the easiest way to set up a KieSession... */ public KieSession knowledgeSession() throws IOException { KieServices ks = KieServices.Factory.get(); KieFileSystem kieFileSystem = ks.newKieFileSystem(); ReleaseId rid = ks.newReleaseId( "com.acme.company" , "rules" , "1.0" ); kieFileSystem.generateAndWritePomXML(rid); KieBuilder kieBuilder = ks.newKieBuilder(kieFileSystem); kieBuilder.buildAll(); KieBaseConfiguration kieBaseConfiguration = ks.newKieBaseConfiguration(); kieBaseConfiguration.setOption(EqualityBehaviorOption.EQUALITY); return ks.newKieContainer(rid).newKieBase(kieBaseConfiguration).newKieSession(); } }
    • Hide

      It's not ideal, but we can avoid all the business with SingleClassStores & the need to link between them by side-stepping the whole ClassObjectFilter optimization; for the time being we've just created a custom ObjectFilter that does the exact same job (but doesn't hit the filter instanceOf ClassObjectFilter checks). This solves the problem for us, but at the cost of throwing away the optimization.

          @Test
          public void worksIfWeAvoidTheOptimization() throws Exception {
              dataLoadDestination.insert(new BaseClass());
      
              queryWithoutOptimizationFor(DerivedClass.class); // Creates non-concrete class store
      
              final DerivedClass element = new DerivedClass();
              dataLoadDestination.insert(element); // Makes concrete but doesn't resolve dependent stores
      
              assertContains(queryFor(BaseClass.class), element);
      
          }
      
          @SuppressWarnings("unchecked")
          private <T> Collection<T> queryWithoutOptimizationFor(Class<T> clazz) {
              return new ArrayList(dataLoadDestination.getObjects(clazz::isInstance));
          }
      
      Show
      It's not ideal, but we can avoid all the business with SingleClassStores & the need to link between them by side-stepping the whole ClassObjectFilter optimization; for the time being we've just created a custom ObjectFilter that does the exact same job (but doesn't hit the filter instanceOf ClassObjectFilter checks). This solves the problem for us, but at the cost of throwing away the optimization. @Test public void worksIfWeAvoidTheOptimization() throws Exception { dataLoadDestination.insert( new BaseClass()); queryWithoutOptimizationFor(DerivedClass.class); // Creates non-concrete class store final DerivedClass element = new DerivedClass(); dataLoadDestination.insert(element); // Makes concrete but doesn't resolve dependent stores assertContains(queryFor(BaseClass.class), element); } @SuppressWarnings( "unchecked" ) private <T> Collection<T> queryWithoutOptimizationFor( Class <T> clazz) { return new ArrayList(dataLoadDestination.getObjects(clazz::isInstance)); }
    • NEW
    • NEW

      In the presence of a class heirarchy, the ClassAwareObjectStore doesn't properly manage the relationships between SingleClassStores - specifically, it can become permanently corrupt if the following order of events occurs:

      • Insert object of type BaseClass
      • Query for objects of type SubClass
        • Note that it's important this query is fully evaluated (e.g. copy it into a list)
      • Insert object of type SubClass

      Subsequent queries for objects of type BaseClass will not return objects of type SubClass. If the intervening query for SubClass is left out, they would.

      The problem's in ClassAwareObjectStore#getOrCreateConcreteClass, where it tries to concretize an existing (non-concrete) store. It should go through the same "linking" process that gets done in the existingStore == null case (and half of which is done already in #getOrCreateClassStore).

              mfusco@redhat.com Mario Fusco
              jelford James Elford (Inactive)
              Votes:
              1 Vote for this issue
              Watchers:
              2 Start watching this issue

                Created:
                Updated:
                Resolved: