Sunday, October 12, 2014

Drools 6 integration with Spring MVC 4- Part 1

Drools is great, Drools 6 is even better but its sad that there are very little documentation and tutorials when it comes to integration of Drools 6 with the most used java MVC framework, Spring.

So here it goes Drools 6.1 integration with Spring 4



The example project source code can be found here on Github

Part 1

We will be using the fire alarm drools example found here for this demo. We will build a Spring MVC 4 application, configured completely with JavaConfig, to monitor fire alarms and to control sprinklers for configured rooms. We will use Drools to write the rule logic for the application.

Note: Basic knowledge of Spring MVC and Drools is expected to follow the below.

1. Pom.xml - Maven config

Lets setup the project first, Here is the maven configuration for the project, refer the full pom.xml in the downloaded source code.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.technorage</groupId>
 <artifactId>demo</artifactId>
 <name>Demo</name>
 <version>1.0.0-SNAPSHOT</version>
 <packaging>war</packaging>
 <description>Drools Demo Project.</description>
 <properties>
  <!-- Generic properties -->
  <java.version>1.7</java.version>
  <!-- Web -->
  <jsp.version>2.2</jsp.version>
  <jstl.version>1.2</jstl.version>
  <servlet.version>3.0.1</servlet.version>
  <!-- Spring -->
  <spring-framework.version>4.0.0.RELEASE</spring-framework.version>
  <!-- Drools -->
  <drools.version>6.1.0.Final</drools.version>
  <!-- Logging -->
  <logback.version>1.0.13</logback.version>
  <slf4j.version>1.7.5</slf4j.version>
  <!-- Test -->
  <junit.version>4.11</junit.version>
 </properties>
 <dependencies>
  <!-- Custom drools-tools jar -->
  <dependency>
   <groupId>com.technorage</groupId>
   <artifactId>drools-tools</artifactId>
   <version>1.0.0-SNAPSHOT</version>
  </dependency>
  <!-- Spring MVC -->
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>${spring-framework.version}</version>
  </dependency>
  <!-- Other Web dependencies -->
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>jstl</artifactId>
   <version>${jstl.version}</version>
  </dependency>
  <dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>javax.servlet-api</artifactId>
   <version>${servlet.version}</version>
   <scope>provided</scope>
  </dependency>
  <dependency>
   <groupId>javax.servlet.jsp</groupId>
   <artifactId>jsp-api</artifactId>
   <version>${jsp.version}</version>
   <scope>provided</scope>
  </dependency>
  <dependency>
   <groupId>javax</groupId>
   <artifactId>javaee-api</artifactId>
   <version>7.0</version>
  </dependency>
  <dependency>
   <groupId>commons-io</groupId>
   <artifactId>commons-io</artifactId>
   <version>2.4</version>
  </dependency>
  <dependency>
   <groupId>commons-beanutils</groupId>
   <artifactId>commons-beanutils</artifactId>
   <version>1.8.3</version>
  </dependency>
  <dependency>
   <groupId>commons-httpclient</groupId>
   <artifactId>commons-httpclient</artifactId>
   <version>3.1</version>
  </dependency>
  <dependency>
   <groupId>commons-codec</groupId>
   <artifactId>commons-codec</artifactId>
   <version>1.8</version>
  </dependency>
  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-validator</artifactId>
   <version>5.0.3.Final</version>
  </dependency>
  <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-validator-annotation-processor</artifactId>
   <version>5.0.3.Final</version>
  </dependency>
  <!-- Spring and Transactions -->
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-tx</artifactId>
   <version>${spring-framework.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-expression</artifactId>
   <version>${spring-framework.version}</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-beans</artifactId>
   <version>${spring-framework.version}</version>
  </dependency>
  <!-- Drools -->
  <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>
  <!-- Logging with SLF4J & LogBack -->
  <dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-api</artifactId>
   <version>${slf4j.version}</version>
   <scope>compile</scope>
  </dependency>
  <dependency>
   <groupId>ch.qos.logback</groupId>
   <artifactId>logback-classic</artifactId>
   <version>${logback.version}</version>
   <scope>runtime</scope>
  </dependency>
  <!-- Test Artifacts -->
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-test</artifactId>
   <version>${spring-framework.version}</version>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>${junit.version}</version>
   <scope>test</scope>
  </dependency>
 </dependencies>
 <build>
  <resources>
 ...
  </resources>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-install-plugin</artifactId>
    <version>2.4</version>
    <executions>
     <execution>
      <phase>initialize</phase>
      <goals>
       <goal>install-file</goal>
      </goals>
      <configuration>
       <groupId>com.technorage</groupId>
       <artifactId>drools-tools</artifactId>
       <version>1.0.0-SNAPSHOT</version>
       <packaging>jar</packaging>
       <file>${basedir}/lib/drools-tools-1.0.0-SNAPSHOT.jar</file>
      </configuration>
     </execution>
    </executions>
   </plugin>
   ... 
  </plugins>
  <pluginManagement>
  ....
  </pluginManagement>
 </build>
</project>



We are using a custom utility jar in the lib folder for drools configuration (Source for the same can be found in the Github repo here). In the pom.xml notice how we include it as dependency after installing it using maven-install-plugin in the plugins section of the POM

<dependencies>
  <!-- Custom drools-tools jar -->
  <dependency>
   <groupId>com.technorage</groupId>
   <artifactId>drools-tools</artifactId>
   <version>1.0.0-SNAPSHOT</version>
  </dependency>
  ...
 </dependencies>
 <build>
  <resources>
 ...
  </resources>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-install-plugin</artifactId>
    <version>2.4</version>
    <executions>
     <execution>
      <phase>initialize</phase>
      <goals>
       <goal>install-file</goal>
      </goals>
      <configuration>
       <groupId>com.technorage</groupId>
       <artifactId>drools-tools</artifactId>
       <version>1.0.0-SNAPSHOT</version>
       <packaging>jar</packaging>
       <file>${basedir}/lib/drools-tools-1.0.0-SNAPSHOT.jar</file>
      </configuration>
     </execution>
    </executions>
   </plugin>
   ... 
  </plugins>
  <pluginManagement>
  ....
  </pluginManagement>
 </build>

Once the project is setup using maven, update the the project from the Eclipse maven menu to update the workspace dependencies.

2. Project Structure

The entire project structure will look like this


3. Web app config

Since we are doing an XML less setup lets start with the web configuration. Find the WebInitializer.java class under src --> web --> config

public class WebInitializer implements WebApplicationInitializer {

@Override
public void onStartup(ServletContext container) {
  // Create the dispatcher servlet's Spring application context
  AnnotationConfigWebApplicationContext dispatcherServlet = new AnnotationConfigWebApplicationContext();
  dispatcherServlet.register(DemoWebConfig.class);

  // Register and map the dispatcher servlet
  ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher",
      new DispatcherServlet(dispatcherServlet));
  dispatcher.setLoadOnStartup(1);
  dispatcher.addMapping("/");
}
}

We will configure the dispatcher servlet to load DemoWebConfig class where we will enable webMVC and import other config classes as below.

@Configuration
// Marks this class as configuration
@Import(DemoServicesConfig.class) // This is just an empty config class, Drools config is loaded here in DemoKieConfig
// Specifies which package to scan
@ComponentScan("com.technorage.demo")
// Enables Spring's annotations
@EnableWebMvc
public class DemoWebConfig extends WebMvcConfigurerAdapter {

@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
  configurer.enable();
}

@Bean
public InternalResourceViewResolver jspViewResolver() {
  InternalResourceViewResolver bean = new InternalResourceViewResolver();
  bean.setPrefix("/WEB-INF/views/");
  bean.setSuffix(".jsp");
  return bean;
}
...
}

4. Drools config

The drools configuration is loaded by the DemoKieConfig class imported in DemoServicesConfig class as below

@Configuration
public class DemoKieConfig {

 @Bean(name = "demoKieServices")
 public KieServicesBean kieServices() throws KieBuildException {
   DroolsResource[] resources = new DroolsResource[] { new DroolsResource(
       "rules/demo-rules.drl", ResourcePathType.CLASSPATH, ResourceType.DRL) };
   KieServicesBean bean = new DefaultKieServicesBean(resources);
   return bean;
 }
 
 @Bean(name = "demoKieContainer")
 public KieContainerBean kieContainer(KieServicesBean kieServices) {
   KieContainerBean bean = new DefaultKieContainerBean(kieServices);
   return bean;
 }
}

The drools session will be created using the KieServicesBean and KieContainerBean injected by spring as per above config. The knowledge base is built using the drl files provided in the resources directory as configured above.

Now lets move on and use the drools session in a spring service class.


@Service
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON, proxyMode = ScopedProxyMode.INTERFACES)
public class DemoRuleServiceImpl<T> implements DemoRuleService<T>, Serializable {

private KieSessionBean kieSession;
private TrackingAgendaEventListener agendaEventListener;
private TrackingWorkingMemoryEventListener workingMemoryEventListener;
private Map<String, Room> name2room = new HashMap<String, Room>();
private Map<String, FactHandle> fact2fire = new HashMap<String, FactHandle>();
private FactFinder<Alarm> findAlarms = new FactFinder<>(Alarm.class);
private FactFinder<Sprinkler> findSprinklers = new FactFinder<>(Sprinkler.class);

@Autowired
public DemoRuleServiceImpl(@Qualifier("demoKieContainer") KieContainerBean kieContainer,@Qualifier("demoKieServices") KieServicesBean kieServices) {

  kieSession = new DefaultKieSessionBean(kieServices, kieContainer);
  agendaEventListener = new TrackingAgendaEventListener();
  workingMemoryEventListener = new TrackingWorkingMemoryEventListener();
  kieSession.addEventListener(agendaEventListener);
  kieSession.addEventListener(workingMemoryEventListener);
}

@Override
public Collection<Alarm> addFire(String[] fires) {

  for (String fire : fires) {
    if (!fact2fire.containsKey(fire)) {
      Fire roomFire = new Fire(name2room.get(fire));
      FactHandle roomFireHandle = kieSession.insert(roomFire);
      fact2fire.put(fire, roomFireHandle);
    }
  }
  kieSession.fireAllRules();
  Collection<Alarm> result = findAlarms.findFacts(kieSession);
  return result;
}

@Override
public Collection<Alarm> checkForFire() {

  Collection<Alarm> result = findAlarms.findFacts(kieSession);
  return result;
}
.... other methods
}

Drools service and container is injected in the constructor to create KieSession, the agendaEventListener and workingMemoryEventListener are utilities for tracking and logging rules activation. FactFinder class is used to retrive Facts from active KieSession for update and to retreat. All these utility classes and beans are loaded from the drools-tools.jar and can be reused as it is for any drools project.

Now the configured service can be autowired from any spring MVC controllers.

Read on for the fire alarm web demo part 2
Follow Us

© None! Feel free to copy content :) | Home | QuizMaster Theme Designed by Blogger Templates