Skip to content

Commit

Permalink
Added Spring context awareness support in rulebook-spring module (del…
Browse files Browse the repository at this point in the history
…iveredtechnologies#134)

* Added Spring context awareness support in rulebook-spring module

* Fixed checkstyle warnings

* Fixed errors. All unit tests pass now.

* Skipping rule needs continue not break

* SpringAwareRuleRunner - unit tests and javadocs

* Test to increase code coverage
  • Loading branch information
rizjoj authored and Clayton7510 committed May 8, 2018
1 parent e23cb40 commit 518a4c1
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,20 @@ public void run(NameValueReferableMap facts) {
RuleBook ruleBook = _prototypeClass.newInstance();
List<Class<?>> classes = getPojoRules();
for (Class<?> rule : classes) {
try {
getAnnotatedField(com.deliveredtechnologies.rulebook.annotation.Result.class, rule).ifPresent(field ->
ruleBook.setDefaultResult(_result.getValue())
);
String name = getAnnotation(com.deliveredtechnologies.rulebook.annotation.Rule.class, rule).name();
if (name.equals("None")) {
name = rule.getSimpleName();
}
Rule ruleInstance = new AuditableRule(new RuleAdapter(rule.newInstance()), name);
ruleBook.addRule(ruleInstance);
((Auditable)ruleInstance).setAuditor(this);
} catch (IllegalAccessException | InstantiationException ex) {
LOGGER.warn("Unable to create instance of rule using '" + rule + "'", ex);
getAnnotatedField(com.deliveredtechnologies.rulebook.annotation.Result.class, rule).ifPresent(field ->
ruleBook.setDefaultResult(_result.getValue())
);
String name = getAnnotation(com.deliveredtechnologies.rulebook.annotation.Rule.class, rule).name();
if (name.equals("None")) {
name = rule.getSimpleName();
}
Object ruleInstance = getRuleInstance(rule);
if (ruleInstance == null) {
continue;
}
Rule auditableRule = new AuditableRule(new RuleAdapter(ruleInstance), name);
ruleBook.addRule(auditableRule);
((Auditable)auditableRule).setAuditor(this);
}
ruleBook.run(facts);
Optional<Result> result = ruleBook.getResult();
Expand Down Expand Up @@ -99,6 +99,23 @@ public boolean hasRules() {
}
}

/**
* Returns a rule instance
*
* <p>For container aware contexts (like Spring, Guice, Weld, etc.) override this method and instantiate the rule
* via the container.
* @param rule The rule class
* @return The rule instance
*/
protected Object getRuleInstance(Class<?> rule) {
try {
return rule.newInstance();
} catch (InstantiationException | IllegalAccessException ex) {
LOGGER.warn("Unable to create instance of rule using '" + rule + "'", ex);
}
return null;
}

/**
* Gets the POJO Rules used in the RuleBook.
* @return A List of the POJO Rules used in a RuleBook.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.deliveredtechnologies.rulebook.spring;

import com.deliveredtechnologies.rulebook.model.runner.RuleBookRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
* Runs the POJO Rules that can be Spring aware beans in a specified package as a RuleBook.
*
* <p>POJO rule classes in the package may or may not have the @Component/@Service/etc. annotation. If they
* do, the bean will be fetched and @Autowired variables will be populated. If not, an instance of the
* POJO rule will be created and used.
*/
public class SpringAwareRuleBookRunner extends RuleBookRunner implements ApplicationContextAware {

private ApplicationContext _applicationContext;

private static Logger LOGGER = LoggerFactory.getLogger(SpringAwareRuleBookRunner.class);

public SpringAwareRuleBookRunner(String rulePackage) {
super(rulePackage);
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this._applicationContext = applicationContext;
}

@Override
protected Object getRuleInstance(Class<?> rule) {
if (_applicationContext == null) {
throw new IllegalStateException("Cannot instantiate RuleBookRunner because "
+ "Spring application context is not available.");
}

try {
// Spring bean POJO rule found
return _applicationContext.getBean(rule);
} catch (BeansException e) {
// POJO rule isn't a Spring bean
return super.getRuleInstance(rule);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.deliveredtechnologies.rulebook.spring;

import com.deliveredtechnologies.rulebook.FactMap;
import com.deliveredtechnologies.rulebook.NameValueReferableMap;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static com.deliveredtechnologies.rulebook.spring.SpringTestService.EXPECTED_RESULT;

@ContextConfiguration(classes = TestConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringAwareRuleBookRunnerTest {
@Autowired
private SpringAwareRuleBookRunner _ruleBook;

private NameValueReferableMap<String> _facts = new FactMap<>();

@Before
public void setUp() {
_facts.setValue("value1", "Value");
_facts.setValue("value2", "Value");
}

@Test
public void ruleBookShouldRunRulesInPackage() {
_ruleBook.run(_facts);
Assert.assertEquals(EXPECTED_RESULT, _facts.getValue("value2"));
}

@Test
public void ruleRunnerShouldReturnNullRuleClassIsInvalid() {
Class clazz = Class.class;
Assert.assertNull(_ruleBook.getRuleInstance(clazz));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.deliveredtechnologies.rulebook.spring;

import com.deliveredtechnologies.rulebook.Fact;
import com.deliveredtechnologies.rulebook.annotation.Given;
import com.deliveredtechnologies.rulebook.annotation.Rule;
import com.deliveredtechnologies.rulebook.annotation.Then;
import com.deliveredtechnologies.rulebook.annotation.When;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
@Rule(order = 3)
public class SpringAwareRuleWithoutResult {
@Autowired
private SpringTestService _springTestService;

@Given("value2")
private Fact<String> _value2;

@When
public boolean when() {
return _springTestService != null;
}

@Then
public void then() {
_value2.setValue(_springTestService.getValue());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,11 @@ public void springRuleBookRunnersShouldBeAuditable() {
facts.setValue("value1", "value2");
facts.setValue("value2", "value2");
_auditableRuleBook.run(facts);
Assert.assertEquals(2, auditor.getRuleStatusMap().size());
for (String key : auditor.getRuleStatusMap().keySet()) {
Assert.assertEquals(RuleStatus.EXECUTED, auditor.getRuleStatusMap().get(key));
}
Assert.assertEquals(3, auditor.getRuleStatusMap().size());
// If you add a new rule, increment the number above and add an assertion for that rule below!
Assert.assertEquals(RuleStatus.EXECUTED, auditor.getRuleStatusMap().get("SpringRuleWithResult"));
Assert.assertEquals(RuleStatus.EXECUTED, auditor.getRuleStatusMap().get("SpringRuleWithoutResult"));
Assert.assertEquals(RuleStatus.SKIPPED, auditor.getRuleStatusMap().get("SpringAwareRuleWithoutResult"));
Assert.assertTrue(_auditableRuleBook.getResult().isPresent());
_auditableRuleBook.getResult().ifPresent(result -> Assert.assertEquals("firstRule", result.toString()));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.deliveredtechnologies.rulebook.spring;

import org.springframework.stereotype.Component;

@Component
public class SpringTestService {
public static final String EXPECTED_RESULT = "Springified!";

public String getValue() {
return EXPECTED_RESULT;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,9 @@ public RuleBookFactoryBean ruleBookFactoryWithResult() {
public RuleBook ruleBook3() {
return new RuleBookRunner("com.deliveredtechnologies.rulebook.spring");
}

@Bean
public SpringAwareRuleBookRunner springAwareRuleBookRunner() {
return new SpringAwareRuleBookRunner("com.deliveredtechnologies.rulebook.spring");
}
}

0 comments on commit 518a4c1

Please sign in to comment.