javaspringspring-bootdroolsdrools-guvnor

Cannot implement Drools KieSession Persistence in Spring Boot


I was trying to implement Drools with the persistence feature of KieSession, in a Spring Boot Maven project. Followed this documentation for the implementation. Was able to do that in a normal Java application but I am getting exceptions while trying to do that in a Spring Boot application.

Below is the implementation.

The project structure

enter image description here

Configuration class

@Configuration
public class PersistentDroolConfig {

    public static Long KIE_SESSION_ID;
    private final KieServices kieServices = KieServices.Factory.get();

    @Bean
    public KieSession kieSession() {
        KieSession kieSession = kieServices.getStoreServices().newKieSession(getKieBase(), null, getEnv());
        PersistentDroolConfig.KIE_SESSION_ID = kieSession.getIdentifier();
        return kieSession;
    }

    public KieServices getKieServices() {
        initDataSource();
        return kieServices;
    }

    public KieBase getKieBase() {
        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
        kieFileSystem.write(ResourceFactory.newClassPathResource("rules/rules.drl"));
        final KieRepository kieRepository = kieServices.getRepository();
        kieRepository.addKieModule(kieRepository::getDefaultReleaseId);
        KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
        kb.buildAll();
        KieModule kieModule = kb.getKieModule();
        KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
        return kieContainer.getKieBase();
    }

    public Environment getEnv() {
        Environment env = kieServices.newEnvironment();
        env.set(EnvironmentName.ENTITY_MANAGER_FACTORY, Persistence.createEntityManagerFactory("org.drools.persistence.jpa"));
        env.set(EnvironmentName.TRANSACTION_MANAGER, TransactionManagerServices.getTransactionManager());
        return env;
    }

    private void initDataSource() {
        PoolingDataSource ds = new PoolingDataSource();
        ds.setUniqueName("jdbc/BitronixJTADataSource");
        ds.setClassName("com.mysql.cj.jdbc.MysqlXADataSource");
        ds.setMaxPoolSize(3);
        ds.setAllowLocalTransactions(true);
        ds.getDriverProperties().put("user", "root");
        ds.getDriverProperties().put("password", "1234");
        ds.getDriverProperties().put("URL", "jdbc:mysql://localhost:3306/drool_demo");
        ds.init();
    }
}

Controller class

@RestController
public class OfferController {

    @Autowired
    private KieSession kieSession;

    @GetMapping("/order/{card-type}/{price}")
    public Order order(@PathVariable("card-type") String cardType, @PathVariable int price) {
        Order order = new Order(cardType, price);
        kieSession.insert(order);
        kieSession.fireAllRules();
        return order;
    }
}

Fact Class

public class Order implements Serializable {

    private String name;
    private String cardType;
    private int discount;
    private int price;

    public Order(String cardType, int price) {
        this.cardType = cardType;
        this.price = price;
    }

    // setters and getters

    // toString()
}

persistence.xml

<persistence version="2.0"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd
                      http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_2_0.xsd"
    xmlns:orm="http://java.sun.com/xml/ns/persistence/orm"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/persistence">
    <persistence-unit name="org.drools.persistence.jpa"
        transaction-type="JTA">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <jta-data-source>jdbc/BitronixJTADataSource</jta-data-source>
        <class>org.drools.persistence.info.SessionInfo</class>
        <class>org.drools.persistence.info.WorkItemInfo</class>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
            <property name="hibernate.max_fetch_depth" value="3" />
            <property name="hibernate.hbm2ddl.auto" value="create" />
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.BTMTransactionManagerLookup" />
        </properties>
    </persistence-unit>
</persistence>

The dependencies included in pom.xml file is as follows:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.github.marcus-nl.btm</groupId>
            <artifactId>btm</artifactId>
            <version>3.0.0-mk1</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-decisiontables</artifactId>
            <version>${drools-version}</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-core</artifactId>
            <version>${drools-version}</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
            <version>${drools-version}</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-persistence-jpa</artifactId>
            <version>${drools-version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

The error stacktrace:

Caused by: org.hibernate.engine.jndi.JndiException: Unable to lookup JNDI name [jdbc/BitronixJTADataSource]

Caused by: javax.naming.NameNotFoundException: unable to find a bound object at name 'jdbc/BitronixJTADataSource'

The project also can be found here in this repository.

UPDATE 1:

After implementing @jccampanero 's answer, I have a newer stacktrace:

Caused by: org.hibernate.HibernateException: Unable to perform isolated work

Caused by: java.sql.SQLSyntaxErrorException: Table 'drool_demo.sessioninfo_id_seq' doesn't exist

UPDATE 2:

After digging further, I have seen Drools isn't making the necessary tables because of some syntax error. Have posted only the important exception messages here since Stackoverflow has a text limit. Here's it is:

Hibernate: drop table if exists SessionInfo
Hibernate: drop table if exists WorkItemInfo
Hibernate: create table SessionInfo (id bigint not null auto_increment, lastModificationDate datetime, rulesByteArray longblob, startDate datetime, OPTLOCK integer, primary key (id)) type=MyISAM
2020-10-09 23:49:59.554  WARN 11376 --- [           main] o.h.t.s.i.ExceptionHandlerLoggedImpl     : GenerationTarget encountered exception accepting command : Error executing DDL "create table SessionInfo (id bigint not null auto_increment, lastModificationDate datetime, rulesByteArray longblob, startDate datetime, OPTLOCK integer, primary key (id)) type=MyISAM" via JDBC Statement


org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "create table SessionInfo (id bigint not null auto_increment, lastModificationDate datetime, rulesByteArray longblob, startDate datetime, OPTLOCK integer, primary key (id)) type=MyISAM" via JDBC Statement

Caused by: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'type=MyISAM' at line 1

Hibernate: create table WorkItemInfo (workItemId bigint not null auto_increment, creationDate datetime, name varchar(255), processInstanceId bigint not null, state bigint not null, OPTLOCK integer, workItemByteArray longblob, primary key (workItemId)) type=MyISAM
2020-10-09 23:49:59.556  WARN 11376 --- [           main] o.h.t.s.i.ExceptionHandlerLoggedImpl     : GenerationTarget encountered exception accepting command : Error executing DDL "create table WorkItemInfo (workItemId bigint not null auto_increment, creationDate datetime, name varchar(255), processInstanceId bigint not null, state bigint not null, OPTLOCK integer, workItemByteArray longblob, primary key (workItemId)) type=MyISAM" via JDBC Statement

org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "create table WorkItemInfo (workItemId bigint not null auto_increment, creationDate datetime, name varchar(255), processInstanceId bigint not null, state bigint not null, OPTLOCK integer, workItemByteArray longblob, primary key (workItemId)) type=MyISAM" via JDBC Statement

Caused by: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'type=MyISAM' at line 1

Solution

  • I think there is a problem in your configuration. The getKieServices method of the PersistentDroolConfig class is never invoked and, as a consequence, neither is the method initDataSource which initializes your datasource.

    Maybe you can try to modify your PersistentDroolConfig, something like this:

    @Configuration
    public class PersistentDroolConfig {
    
        public static Long KIE_SESSION_ID;
    
        private KieServices kieServices;
    
        @PostContruct
        private void init() {
            this.initDataSource();
            this.kieServices = KieServices.Factory.get();
        }
    
        @Bean
        public KieSession kieSession() {
            KieSession kieSession;
            if (KIE_SESSION_ID == null) {
                kieSession = createNewKieSession();
                KIE_SESSION_ID = kieSession.getIdentifier();
                return kieSession;
            } else {
                kieSession = getPersistentKieSession();
                KIE_SESSION_ID = kieSession.getIdentifier();
                return kieSession;
            }
        }
    
        public KieBase getKieBase() {
            KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
            kieFileSystem.write(ResourceFactory.newClassPathResource("rules/rules.drl"));
            final KieRepository kieRepository = kieServices.getRepository();
            kieRepository.addKieModule(new KieModule() {
                @Override
                public ReleaseId getReleaseId() {
                    return kieRepository.getDefaultReleaseId();
                }
            });
            KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
            kb.buildAll();
            KieModule kieModule = kb.getKieModule();
            KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
            KieBase kieBase = kieContainer.getKieBase();
            return kieBase;
        }
    
        public Environment getEnv() {
            Environment env = kieServices.newEnvironment();
            env.set(EnvironmentName.ENTITY_MANAGER_FACTORY, Persistence.createEntityManagerFactory("org.drools.persistence.jpa"));
            env.set(EnvironmentName.TRANSACTION_MANAGER, TransactionManagerServices.getTransactionManager());
            return env;
        }
    
        private KieSession createNewKieSession() {
            KieSession kieSession = kieServices.getStoreServices().newKieSession(getKieBase(), null, getEnv());
            PersistentDroolConfig.KIE_SESSION_ID = kieSession.getIdentifier();
            return kieSession;
        }
    
        private KieSession getPersistentKieSession() {
            return kieServices.getStoreServices().loadKieSession(KIE_SESSION_ID, getKieBase(), null, getEnv());
        }
    
        private void initDataSource() {
            PoolingDataSource ds = new PoolingDataSource();
            ds.setUniqueName("jdbc/BitronixJTADataSource");
            ds.setClassName("com.mysql.cj.jdbc.MysqlXADataSource");
            ds.setMaxPoolSize(3);
            ds.setAllowLocalTransactions(true);
            ds.getDriverProperties().put("user", "root");
            ds.getDriverProperties().put("password", "1234");
            ds.getDriverProperties().put("URL", "jdbc:mysql://localhost:3306/drool_demo");
            ds.init();
        }
    }
    

    UPDATE

    As stated in the different comments, after making these changes, a problem arose related to the SESSIONINFO_ID_SEQ sequence used to generate the values of the field id of the entity SessionInfo.

    The problem seemed to be related to the version of Hibernate and MySQL used.

    To solve the problem, it is necessary to make several changes to the persistence.xml file.

    First, the following property should be included:

    <property name="hibernate.id.new_generator_mappings" value="false" />
    

    It is often used in the Drools examples and test cases. See this great Vlad Mihalcea article for an in depth explanation.

    The inclusion of this configuration property generated a new problem related to the MySQL dialect used. It should be changed to:

    <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
    

    With this configuration, the application should run smoothly.