/**
* Copyright 2010 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.drools.factconstraints.server.predefined;
import java.util.Arrays;
import java.util.List;
import org.drools.factconstraints.client.ArgumentNotSetException;
import org.drools.factconstraints.client.ConstraintConfiguration;
import org.drools.factconstraints.client.ValidationResult;
import org.drools.factconstraints.server.Constraint;
/**
*
* @author esteban.aliverti@gmail.com
*/
public class RangeConstraint implements Constraint {
private static final long serialVersionUID = 501l;
public static final String NAME = "RangeConstraint";
public static final String RANGE_CONSTRAINT_MIN = "Min.value";
public static final String RANGE_CONSTRAINT_MAX = "Max.value";
private static String template;
static {
StringBuilder rules = new StringBuilder();
rules.append("package org.drools.verifier.consequence\n");
rules.append("import org.drools.verifier.components.*;\n");
rules.append("import java.util.Map;\n");
rules.append("import java.util.HashMap;\n");
rules.append("import org.drools.verifier.report.components.VerifierMessage;\n");
rules.append("import org.drools.verifier.data.VerifierReport;\n");
rules.append("import org.drools.verifier.report.components.Severity;\n");
rules.append("import org.drools.verifier.report.components.MessageType;\n");
rules.append("import org.drools.base.evaluators.Operator;\n");
rules.append("global VerifierReport result;\n");
rules.append("declare RangeConstraintCandidate{0}\n");
rules.append(" restriction : NumberRestriction\n");
rules.append(" greaterValue : double\n");
rules.append(" lessValue : double\n");
rules.append("end\n");
rules.append("function void addResult{0}(VerifierReport report, NumberRestriction restriction, Severity severity, String message){\n");
rules.append(" Map<String,String> impactedRules = new HashMap<String,String>();\n");
rules.append(" report.add(new VerifierMessage(\n");
rules.append(" impactedRules,\n");
rules.append(" severity,\n");
rules.append(" MessageType.NOT_SPECIFIED,\n");
rules.append(" restriction,\n");
rules.append(" message ) );\n");
rules.append("}\n");
rules.append("rule \"Range_Field_Constraint_Base_{0}\"\n");
rules.append(" when\n");
rules.append(" $field :Field(\n");
rules.append(" objectTypeName == \"{1}\",\n");
rules.append(" name == \"{2}\"\n");
rules.append(" )\n");
rules.append(" then\n");
rules.append("end\n");
rules.append("/* Single operators */\n");
rules.append("rule \"Range_Field_Constraint_==_{0}\" extends \"Range_Field_Constraint_Base_{0}\"\n");
rules.append(" when\n");
rules.append(" ($restriction :NumberRestriction(\n");
rules.append(" fieldPath == $field.path,\n");
rules.append(" operator == Operator.EQUAL,\n");
rules.append(" (value < {3} || > {4}))\n");
rules.append(" )\n");
rules.append(" then\n");
rules.append(" addResult{0}(result, $restriction, Severity.ERROR, \"The value must be between {3} and {4}\");\n");
rules.append("end\n");
rules.append("rule \"Range_Field_Constraint_!=_{0}\" extends \"Range_Field_Constraint_Base_{0}\"\n");
rules.append(" when\n");
rules.append(" ($restriction :NumberRestriction(\n");
rules.append(" fieldPath == $field.path,\n");
rules.append(" operator == Operator.NOT_EQUAL,\n");
rules.append(" (value < {3} || > {4}))\n");
rules.append(" )\n");
rules.append(" then\n");
rules.append(" addResult{0}(result, $restriction, Severity.WARNING, \"Impossible value. Possible values are from {3} to {4}\");\n");
rules.append("end\n");
rules.append("rule \"Range_Field_Constraint_>_{0}\" extends \"Range_Field_Constraint_Base_{0}\"\n");
rules.append(" when\n");
rules.append(" ($restriction :NumberRestriction(\n");
rules.append(" fieldPath == $field.path,\n");
rules.append(" $rulePath: rulePath,\n");
rules.append(" (operator == Operator.GREATER || == Operator.GREATER_OR_EQUAL))\n");
rules.append(" )\n");
rules.append(" not (NumberRestriction(\n");
rules.append(" fieldPath == $field.path,\n");
rules.append(" rulePath == $rulePath,\n");
rules.append(" (operator == Operator.LESS || == Operator.LESS_OR_EQUAL)\n");
rules.append(" )\n");
rules.append(" )\n");
rules.append(" then\n");
rules.append(" addResult{0}(result, $restriction, Severity.WARNING, \"Missing range\");\n");
rules.append("end\n");
rules.append("rule \"Range_Field_Constraint_<_{0}\" extends \"Range_Field_Constraint_Base_{0}\"\n");
rules.append(" when\n");
rules.append(" ($restriction :NumberRestriction(\n");
rules.append(" fieldPath == $field.path,\n");
rules.append(" $rulePath: rulePath,\n");
rules.append(" (operator == Operator.LESS || == Operator.LESS_OR_EQUAL))\n");
rules.append(" )\n");
rules.append(" not (NumberRestriction(\n");
rules.append(" fieldPath == $field.path,\n");
rules.append(" rulePath == $rulePath,\n");
rules.append(" (operator == Operator.GREATER || == Operator.GREATER_OR_EQUAL)\n");
rules.append(" )\n");
rules.append(" )\n");
rules.append(" then\n");
rules.append(" addResult{0}(result, $restriction, Severity.WARNING, \"Missing range\");\n");
rules.append("end\n");
rules.append("/* Multi operator */\n");
rules.append("rule \"identifyRangeConstraintCandidate{0}\" extends \"Range_Field_Constraint_Base_{0}\"\n");
rules.append("when\n");
rules.append(" ($restriction1 :NumberRestriction(\n");
rules.append(" $rulePath: rulePath,\n");
rules.append(" fieldPath == $field.path,\n");
rules.append(" (operator == Operator.GREATER || == Operator.GREATER_OR_EQUAL),\n");
rules.append(" $op1: operator,\n");
rules.append(" $value1: value))\n");
rules.append(" ($restriction2 :NumberRestriction(\n");
rules.append(" fieldPath == $field.path,\n");
rules.append(" rulePath == $rulePath,\n");
rules.append(" (operator == Operator.LESS || == Operator.LESS_OR_EQUAL),\n");
rules.append(" $op2: operator,\n");
rules.append(" $value2: value))\n");
rules.append("then\n");
rules.append(" RangeConstraintCandidate{0} rcc = new RangeConstraintCandidate{0}();\n");
rules.append(" rcc.setRestriction($restriction1);\n");
rules.append(" rcc.setGreaterValue($value1.doubleValue());\n");
rules.append(" rcc.setLessValue($value2.doubleValue());\n");
rules.append(" insert (rcc);\n");
rules.append("end\n");
rules.append("/*\n");
rules.append(" GM = the value is greater than max ( > max)\n");
rules.append(" LM = the value is less than min (< min)\n");
rules.append(" VV = the value is inside the range (>= min && <= max)\n");
rules.append("*/\n");
rules.append("rule \"processGMGM{0}\"\n");
rules.append("when\n");
rules.append(" $r: RangeConstraintCandidate{0}(greaterValue > {4} && lessValue > {4})\n");
rules.append("then\n");
rules.append(" addResult{0}(result, $r.getRestriction(), Severity.WARNING, \"Both sides are outside the range\");\n");
rules.append(" retract ($r);\n");
rules.append("end\n");
rules.append("rule \"processGMVV{0}\"\n");
rules.append("when\n");
rules.append(" $r: RangeConstraintCandidate{0}(greaterValue > {4} && lessValue >= {3} && lessValue <={4})\n");
rules.append("then\n");
rules.append(" addResult{0}(result, $r.getRestriction(), Severity.ERROR, \"Impossible condition\");\n");
rules.append(" retract ($r);\n");
rules.append("end\n");
rules.append("rule \"processGMLM{0}\"\n");
rules.append("when\n");
rules.append(" $r: RangeConstraintCandidate{0}(greaterValue > {4} && lessValue < {3})\n");
rules.append("then\n");
rules.append(" addResult{0}(result, $r.getRestriction(), Severity.ERROR, \"Impossible condition\");\n");
rules.append(" retract ($r);\n");
rules.append("end\n");
rules.append("rule \"processVVGM{0}\"\n");
rules.append("when\n");
rules.append(" $r: RangeConstraintCandidate{0}(greaterValue >= {3} && greaterValue <={4} && lessValue > {4})\n");
rules.append("then\n");
rules.append(" addResult{0}(result, $r.getRestriction(), Severity.WARNING, \"Right side is outside the range\");\n");
rules.append(" retract ($r);\n");
rules.append("end\n");
rules.append("rule \"processVVLM{0}\"\n");
rules.append("when\n");
rules.append(" $r: RangeConstraintCandidate{0}(greaterValue >= {3} && greaterValue <={4} && lessValue < {3})\n");
rules.append("then\n");
rules.append(" addResult{0}(result, $r.getRestriction(), Severity.ERROR, \"Impossible condition\");\n");
rules.append(" retract ($r);\n");
rules.append("end\n");
rules.append("rule \"processLMGM{0}\"\n");
rules.append("when\n");
rules.append(" $r: RangeConstraintCandidate{0}(greaterValue < {3} && lessValue > {4})\n");
rules.append("then\n");
rules.append(" addResult{0}(result, $r.getRestriction(), Severity.WARNING, \"Both sides are outside the range\");\n");
rules.append(" retract ($r);\n");
rules.append("end\n");
rules.append("rule \"processLMVV{0}\"\n");
rules.append("when\n");
rules.append(" $r: RangeConstraintCandidate{0}(greaterValue < {3} && lessValue >= {3} && lessValue <={4})\n");
rules.append("then\n");
rules.append(" addResult{0}(result, $r.getRestriction(), Severity.WARNING, \"Left side is outside the range\");\n");
rules.append(" retract ($r);\n");
rules.append("end\n");
rules.append("rule \"processLMLM{0}\"\n");
rules.append("when\n");
rules.append(" $r: RangeConstraintCandidate{0}(greaterValue < {3} && lessValue < {3})\n");
rules.append("then\n");
rules.append(" addResult{0}(result, $r.getRestriction(), Severity.WARNING, \"Both sides are outside the range\");\n");
rules.append(" retract ($r);\n");
rules.append("end\n");
template = rules.toString();
}
public String getConstraintName() {
return NAME;
}
public String getVerifierRule(ConstraintConfiguration config) {
return template.replaceAll("\\{0\\}", String.valueOf(System.nanoTime())).replaceAll("\\{1\\}", config.getFactType()).replaceAll("\\{2\\}", config.getFieldName()).replaceAll("\\{3\\}", this.getMin(config)).replaceAll("\\{4\\}", this.getMax(config));
}
public ValidationResult validate(Object value, ConstraintConfiguration config) {
ValidationResult result = new ValidationResult();
try {
if (value == null || !(value instanceof Number || value instanceof String)) {
result.setSuccess(false);
if (value == null) {
result.setMessage("The value is null"); // TODO: I18N
} else {
result.setMessage("Invalid value type " + value.getClass().getName()); // TODO:
// I18N
}
} else {
double min = Double.parseDouble(getMin(config));
double max = Double.parseDouble(getMax(config));
double d = Double.parseDouble(value.toString());
result.setSuccess(d > min && d < max);
if (!result.isSuccess()) {
result.setMessage("The value should be between " + min + " and " + max); // TODO:
// I18N
}
}
} catch (Throwable t) {
result.setSuccess(false);
result.setMessage(t.getMessage()); // TODO: I18N
}
return result;
}
public String getMin(ConstraintConfiguration conf) {
try {
return (String) this.getMandatoryArgument(RANGE_CONSTRAINT_MIN, conf);
} catch (ArgumentNotSetException e) {
throw new IllegalStateException(e);
}
}
public String getMax(ConstraintConfiguration conf) {
try {
return (String) this.getMandatoryArgument(RANGE_CONSTRAINT_MAX, conf);
} catch (ArgumentNotSetException e) {
throw new IllegalStateException(e);
}
}
public List<String> getArgumentKeys() {
return Arrays.asList(new String[]{RANGE_CONSTRAINT_MIN, RANGE_CONSTRAINT_MAX});
}
private Object getMandatoryArgument(String key, ConstraintConfiguration conf) throws ArgumentNotSetException {
if (!conf.containsArgument(key)) {
throw new ArgumentNotSetException("The argument " + key + " doesn't exist.");
}
Object value = conf.getArgumentValue(key);
if (value == null) {
throw new ArgumentNotSetException("The argument " + key + " is null.");
}
return value;
}
}