2

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
sam
  • 1,800
  • 1
  • 25
  • 47
  • Sounds like you're missing your jdbc driver. I'm not familiar with this particular 'bitronix' data source, but maybe try adding the appropriate JDBC driver dependency to your pom? Or if you don't want to use it update your config to something different: `jdbc/BitronixJTADataSource` – Roddy of the Frozen Peas Oct 07 '20 at 20:59
  • Your Configuration class is a bit messy. I would use a bean for datasource to be able to inject datasource in all services that needs it easilly and if you look at the end of your stacktrace : your datasource doesn't seems well configured so check your database configuration with a database client. – Gweltaz Niquel Oct 08 '20 at 14:08
  • Your "update" asks a completely different question. (Regarding that, have you considered creating the missing table? Looks like you're trying to use auto-generated ids from a sequence table but said sequence table doesn't exist.) – Roddy of the Frozen Peas Oct 09 '20 at 16:53
  • @RoddyoftheFrozenPeas was using the Mysql driver provided for spring boot. Used the spring boot jdbc driver as well. Still the same stacktrace. – sam Oct 09 '20 at 17:17
  • @RoddyoftheFrozenPeas the table will be autogenerated by Drools through hibernate. So I am using `` – sam Oct 09 '20 at 17:19
  • Thanks for this question! was searching everywhere for drools clustering , couldnt find any proper documentation or articles anywhere and this question saved me! – emilpmp Jan 17 '21 at 15:29

2 Answers2

2

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.

jccampanero
  • 50,989
  • 3
  • 20
  • 49
  • Thank you for correcting the mistake. I have a newer stacktrace. could you please have a look on it. I have updated the question as well. – sam Oct 09 '20 at 12:08
  • One possible problem is that Hibernate is unable to identify the catalog in which the Drools database items are defined. Please, include something like this in your ```persistence.xml``` file: ``````. Of course, replace ```your-schema``` with the right one. – jccampanero Oct 09 '20 at 15:27
  • I have tried the hibernate.defualt schema property. I am still getting the same stacktrace. Also Drools makes the required tables through hibernate with this `` – sam Oct 09 '20 at 17:23
  • Hibernate makes two different tables SessionInfo and WorkItemInfo. These two are Drools provided classes that we have mentioned in the persistence.xml file – sam Oct 09 '20 at 17:55
  • Please, can you include the following property in your ```persistence.xml``` file? `````` – jccampanero Oct 09 '20 at 18:16
  • Have included this property. Same stacktrace. However the stacktrace is having some unexpected errors which was shown in the very top when execution starts. Please see the update in the question. – sam Oct 09 '20 at 18:21
  • The problem is relates with the entity [```SessionInfo```](https://github.com/kiegroup/drools/blob/master/drools-persistence/drools-persistence-jpa/src/main/java/org/drools/persistence/info/SessionInfo.java) and the sequence used for ```id``` generation. Please, how is the ```id``` column being generated? With auto increment? Please,can you remove the ```spring-boot-starter-jpa``` dependency from your ```pom.xml```? Maybe it is providing an hibernate dependency that could be the cause of the problem – jccampanero Oct 09 '20 at 19:21
  • 1
    Please, you can try to change the Hibernate dialect to ```org.hibernate.dialect.MySQL5Dialect```. See [this](https://stackoverflow.com/questions/43716068/invalid-syntax-error-type-myisam-in-ddl-generated-by-hibernate) stack overflow question. – jccampanero Oct 09 '20 at 19:31
  • Solved it. The problem was in the table creation syntax by Drools via hibernate. It was using type=MyISAM which is not valid syntax. Separately created the tables from the syntax provided in the stacktrace removing the type and using the setting you provided ``. Now persistence is working fine. – sam Oct 09 '20 at 19:33
  • Nice @sam! Please, any way, can you try and change the Hibernate dialect to ```org.hibernate.dialect.MySQL5Dialect``` as suggested in my last comment? I think it could be of help. – jccampanero Oct 09 '20 at 19:35
  • `` This is the real cause. Now autoddl is also working. Thanks. Please update the answer with this. This one is the main answer. – sam Oct 09 '20 at 19:36
  • Also please upvote the question so that people facing this problem comes to this SO question for the solution. – sam Oct 09 '20 at 19:37
  • Thank you very much @sam. I modified my answer. I will improve the updated content later. I am very glad to know that the answer was helpful. – jccampanero Oct 09 '20 at 19:52
1

Did you configure jndi datasource in your container?

<Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource"
    maxTotal="100" maxIdle="30" maxWaitMillis="10000"
    username="javauser" password="javadude" driverClassName="com.mysql.jdbc.Driver"
    url="jdbc:mysql://localhost:3306/javatest"/>

Tomcat jndi data source example

Mike
  • 20,010
  • 25
  • 97
  • 140
  • I am using embedded Tomcat in Spring Boot. where should I put this content – sam Oct 09 '20 at 11:59
  • https://stackoverflow.com/questions/24941829/how-to-create-jndi-context-in-spring-boot-with-embedded-tomcat-container – Mike Oct 09 '20 at 14:13