Overview
There are several ways to create a new Module Type:
Defining a Simple JSON Module Type
Let's see a simple JSON module type definition and describe its structure:
{
"conditions":[
{
"uid":"CompareCondition",
"label":"Demo Condition",
"description":"Comares a value from it's input wit a given constraint value, by given constraint operator.",
"configDescriptions":[
{
"name":"operator",
"type":"TEXT",
"description":"Valid operators are =,>,<,!=",
"required":true
},
{
"name":"constraint",
"type":"INTEGER",
"description":"Right operand which is compared with the input.",
"required":true
}
],
"inputs":[
{
"name":"inputValue",
"type":"java.lang.Integer",
"label":"inputValue",
"description":"Input value which will be evaluated.",
"required":true
}
]
}
]
}
The json above defines a condition that performs a basic comparisons over integer values.
The UID of the module type is "CompareCondition" and must be unique.
Also "label" and "description" fields are set. The purpose of these fields is to provide a human readable information about this module type.
This module type has a configuration that defines it's configuration properties. The Configuration properties are used to set static values that are required for the purposes of the handler. The module type definition above has two configuration properties: "operator" and "constraint". The value of "operator" configuration property is used as an operator for the comparison. The value of the "constraint" configuration property is used as right operand for the expression.
Modules communicate between them by virtue of their inputs and outputs. The module type definition above has one input. From this input comes the left operand for the expression.
Defining a Composite JSON Module Type
Let's see a composite JSON module type definition and describe it's structure:
The composite module type wraps one or more already defined modules and can redefine some of their configuration properties.
Let's define a composite module type over 'CompareCondition'. We will call it EqualsComparator and we will configure it's 'operator' configuration property to be '=':
{
"conditions":[
{
"uid":"EqualsComparator",
"label":"Equals Comparator",
"description":"This module type performs a check for equality between the value that comes on its input and a given configured value.",
"configDescriptions":[
{
"name":"constraint",
"type":"INTEGER",
"label":"The value that will be the right operand for the comparison.",
"description":"The value must be an integer.",
"required":true
}
],
"inputs":[
{
"name":"inputValue",
"type":"java.lang.Integer",
"label":"inputValue",
"description":"The value that will be evaluated.",
"required":true
}
],
"children":[
{
"id":"CompareCondition",
"type":"CompareCondition",
"configuration":{
"operator":"=",
"constraint":"${constraint}"
},
"inputs":{
"inputValue":"${inputValue}"
}
}
]
}
]
}
The configuration properties and the inputs of the module type over which the composite module type is defined are connected to these in the new module type.
Creating a Module Handler
To create and add a new Module Type is not enough. The new Module Type to become functional a Module Handler implementation which is the logic behind the module type should be provided. Let's see an example with handler implementation for the module type that is defined above:
public class CompareCondition extends BaseModuleHandler<Condition> implements ConditionHandler {
/**
* This field is used by {@link HandlerFactory} to create a correct handler instance. It must be the same as in
* JSON definition of the module type.
*/
public final static String UID = "CompareCondition";
/**
* Describes all possible operations that this handler can perform
*
* @author Plamen Peev
*
*/
private static enum Operator {
EQUAL("="),
NOT_EQUAL("!="),
LESS("<"),
GREATER(">");
private final String operation;
private Operator(String operation) {
this.operation = operation;
}
public String getOperation() {
return operation;
}
public static Operator getOperatorByStr(String str) {
.................................
}
}
/**
* This constant is used to get the value of the 'operator' property from {@link Condition}'s {@link Configuration}.
*/
public static final String OPERATOR = "operator";
/**
* This constant is used to get the value of the 'constraint' property from {@link Condition}'s
* {@link Configuration}.
*/
public static final String CONSTRAINT = "constraint";
/**
* This constant contains the name of the input for this {@link Condition} handler.
*/
private static final String INPUT_NAME = "inputValue";
/**
* This field contains the constraint value which will be used in the evaluation of te condition.
*/
private final int constraint;
/**
* This field caches the operator for the comparisons.
*/
private final Operator operator;
/**
* Constructs a {@link CompareCondition} instance.
*
* @param module - the {@link Condition} for which the instance is created.
*/
public CompareCondition(Condition module) {
super(module);
.................................
final Number constraint = (Number) configuration.get(CONSTRAINT);
if (constraint == null) {
throw new IllegalArgumentException("'constraint' can not be null.");
}
this.constraint = constraint.intValue();
final String operatorAsString = (String) configuration.get(OPERATOR);
if (operatorAsString == null) {
throw new IllegalArgumentException("'operator' can not be null.");
}
this.operator = Operator.getOperatorByStr(operatorAsString);
}
/**
* This method is used in the evaluation of this {@link Condition} handler.
* It compares the value from the input with the value from {@link Condition}'s configuration.
*/
public boolean isSatisfied(Map<String, Object> inputs) {
final Integer value = (Integer) inputs.get(INPUT_NAME);
if (value == null) {
return false;
}
switch (operator) {
case EQUAL:
return constraint == value.intValue();
case NOT_EQUAL:
return constraint != value.intValue();
case GREATER:
return constraint > value.intValue();
case LESS:
return constraint < value.intValue();
default:
return false;
}
}
}
Creating a Module Handler Factory:
To be your Module Handler available for usage, you have to create a Module Handler Factory that creates instances of this handler implementation. Let's see an example Module Handler Factory implementation:
public class HandlerFactory extends BaseModuleHandlerFactory implements ModuleHandlerFactory {
/**
* This field contains the types that are supported by this factory.
*/
private final static Collection<String> types;
/**
* This method must deliver the correct handler if this factory can create it or log an error otherwise.
* It recognizes the correct type by {@link Module}'s uid.
*/
@Override
protected ModuleHandler internalCreate(Module module, String ruleUID) {
if (CompareCondition.UID.equals(module.getTypeUID())) {
return new CompareCondition((Condition) module);
} else {
logger.error(MODULE_HANDLER_FACTORY_NAME + "Not supported moduleHandler: {}", module.getTypeUID());
}
return null;
}
/**
* Returns a {@link Collection} that contains the UIDs of the module types that are supported by this factory.
*/
public Collection<String> getTypes() {
return types;
}
}