Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a standard library/API/tool for implementing basic "rule chains" in Java?

Tags:

java

I am writing a servlet that will be conditionally modifying HTTP headers according to some user-definable rules. (Edit: these rules are defined in an XML file that is read at start-up.) For example, add "X-UA-Compatible: IE=edge,chrome=1" to a response header if it does not already exist and if the request specified a "User-Agent" header matching a known pattern. Not having any better ideas, I attempted to make my own POJOs representing these rules. It "works" but I feel like there must be a more standard or more flexible way to do this.

Are there general-purpose libraries or tools (whether built-in or 3rd-party) that would solve this problem? I have heard and read a little about "rules engines" but they seem like much more complex/heavy tools not meant for problems as simple as mine.

To illustrate what I'm trying to do, I've created a simplified program that applies "rules" to numbers based on "conditions" like "is an even number". Here it is, sorry it's a bit lengthy.

Main.java

package my.example;

import java.util.*;

import my.example.conditions.*;
import my.example.rules.*;

public class Main {
    
    public static void main(String args[]) {
        // Some sample objects to evaluate
        Collection<Integer> numbers = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8);
        print(numbers);
        
        // Define rules
        Collection<Rule<Integer>> rules = new ArrayList<Rule<Integer>>();
        rules.add(createRuleToMultiplyEvenNumbersBy4());
        rules.add(createRuleToAdd1ToEveryNumber());
        
        // Process the rules for each sample object
        Collection<Integer> newNumbers = new ArrayList<Integer>();
        for (Integer number : numbers) {
            Integer newNumber = number;
            for (Rule<Integer> rule : rules)
                newNumber = rule.apply(newNumber);
            newNumbers.add(newNumber);
        }

        print(newNumbers);
    }

    private static Rule<Integer> createRuleToMultiplyEvenNumbersBy4() {
        MultiplyNumberRule rule = new MultiplyNumberRule(true, 4);
        rule.addCondition(new NumberIsEvenCondition());
        return rule;
    }
    
    private static Rule<Integer> createRuleToAdd1ToEveryNumber() {
        AddNumberRule rule = new AddNumberRule(true, 1);
        rule.addCondition(new ConstantCondition<Integer>(true));
        return rule;
    }
    
    private static void print(Collection<Integer> numbers) {
        System.out.print("Numbers: ");
        for (Integer number : numbers) {
            System.out.print(number);
            System.out.print(" ");
        }
        System.out.print("\r\n");
    }   
    

}

Condition.java

package my.example.conditions;

public interface Condition<T> {
    boolean appliesTo(T obj);
}

ConstantCondition.java

package my.example.conditions;

public class ConstantCondition<T> implements Condition<T> {
    private boolean constant;
    
    public ConstantCondition(boolean alwaysReturnThisValue) {
        constant = alwaysReturnThisValue;
    }
    
    @Override
    public boolean appliesTo(T target) {
        return constant;
    }
}

NumberIsEvenCondition.java

package my.example.conditions;

public class NumberIsEvenCondition implements Condition<Integer> {
    @Override
    public boolean appliesTo(Integer i) {
        return (i % 2 == 0);
    }
}

Rule.java

package my.example.rules;

public interface Rule<T> {
    T apply(T target);
}

AbstractRule.java

package my.example.rules;

import java.util.*;

import my.example.conditions.Condition;
public abstract class AbstractRule<T> implements Rule<T> {
    private Collection<Condition<T>> conditions;
    private boolean requireAllConditions;
    
    public AbstractRule(boolean requireAllConditions) {
        conditions = new ArrayList<Condition<T>>();
        this.requireAllConditions = requireAllConditions;
    }

    public void addCondition(Condition<T> condition) {
        conditions.add(condition);
    }

    @Override
    public T apply(T target) {
        boolean isApplicable;
        if (requireAllConditions)
            isApplicable = allConditionsSatisfied(target);
        else
            isApplicable = atLeastOneConditionSatisfied(target);
        
        if (isApplicable)
            target = process(target);
        
        return target;
    }
    
    // Check if all conditions are met
    protected boolean allConditionsSatisfied(T target) {
        for (Condition<T> condition : conditions) {
            if (!condition.appliesTo(target))
                return false;
        }       
        return true;
    }
    
    // Check if any conditions are met
    protected boolean atLeastOneConditionSatisfied(T target) {
        for (Condition<T> condition : conditions) {
            if (condition.appliesTo(target))
                return true;
        }       
        return false;
    }

    abstract T process(T target);

}

AddNumberRule.java

package my.example.rules;

public class AddNumberRule extends AbstractRule<Integer> {
    private Integer addend;
    
    public AddNumberRule(boolean requireAllConditions) {
        this(requireAllConditions, 0);
    }
    
    public AddNumberRule(boolean requireAllConditions, Integer addend) {
        super(requireAllConditions);
        this.addend = addend;
    }
    
    @Override
    public Integer process(Integer i) {
        return i + addend;
    }
}

MultiplyNumberRule.java

package my.example.rules;

public class MultiplyNumberRule extends AbstractRule<Integer> {
    private Integer factor;
    
    public MultiplyNumberRule(boolean requireAllConditions) {
        this(requireAllConditions, 1);
    }
    
    public MultiplyNumberRule(boolean requireAllConditions, Integer factor) {
        super(requireAllConditions);
        this.factor = factor;
    }
    
    @Override
    public Integer process(Integer i) {
        return i * factor;
    }
}
like image 777
jacobq Avatar asked Nov 12 '22 23:11

jacobq


1 Answers

Well, I'd use Commons Chain

A popular technique for organizing the execution of complex processing flows is the "Chain of Responsibility" pattern, as described (among many other places) in the classic "Gang of Four" design patterns book. Although the fundamental API contracts required to implement this design patten are extremely simple, it is useful to have a base API that facilitates using the pattern, and (more importantly) encouraging composition of command implementations from multiple diverse sources.

it's a common Design Pattern, guess that fits your problem

like image 50
Luiz E. Avatar answered Nov 14 '22 21:11

Luiz E.