package org.ff4j.drools; /* * #%L * ff4j-strategy-drools * %% * Copyright (C) 2013 - 2015 FF4J * %% * 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. * #L% */ import java.util.Arrays; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.StringJoiner; import org.ff4j.core.FeatureStore; import org.ff4j.core.FlippingExecutionContext; import org.ff4j.core.FlippingStrategy; import org.ff4j.strategy.AbstractFlipStrategy; /** * Proposition of {@link FlippingStrategy} delegating the evaluation of feature * toggling to the JBoss Drools Rule Engine. * * <p>The {@link FlippingExecutionContext} is embeded in a Request {@link FF4jDroolsRequest} and * insert as <code>Fact</code> in the Drools session. * * <p>The rules are expected to update the request and espacially the status boolean <code>toggled</code>. * You can initialize the drools Engine with both the KbaseName or a set of DRL files. * * <p>You can find a sample of rules below : * <pre>rule "f1_checkSampleThreshold" * dialect "mvel" * when * $req : FF4jDroolsRequest( featureName == "f1", evaluated == false ) * then * System.out.println("Evaluating F1 Strategy"); * System.out.println(store.getClass().getName()); * modify ($req) { * evaluated = true, * toggled = true * }; * end * </pre> * * @author Cedrick Lunven (@clunven)</a> */ public class FF4jDroolsFlippingStrategy extends AbstractFlipStrategy { /** key to be used in map initParam. */ private static final String KEY_BASE_NAME = "basename"; /** key to be used in map initParam. */ private static final String KEY_RULES_FILES = "ruleFiles"; /** (If initialized with the kmodule.xml file) State as the kSession name. */ private String basename; /** (If initialized with drl rule files), State as the kSession name. */ private Set<String> ruleFiles = new HashSet<>(); /** * Keep default constructor to allow dependency injection. */ public FF4jDroolsFlippingStrategy() { } /** * Constructor to work with kSession names. * * <p><pre><ksession name="ff4jDroolsStrategy"/></pre> * * @param baseName * Ksession name locate in META-INF/kmodule.xml (convention) */ public FF4jDroolsFlippingStrategy(String baseName) { this.basename = baseName; initParams.put(KEY_BASE_NAME, basename); // Do not initialize for each feature, it's a static contexte to be initialized once if (!FF4jDroolsService.isInitialized()) { FF4jDroolsService.initFromBaseName(basename); } } /** * Constructor to work with drl rule files. * * @param files * rules files can be DRL, RDRL... */ public FF4jDroolsFlippingStrategy(Set<String> files) { this.ruleFiles = files; initParams.put(KEY_RULES_FILES, getRulesFileAsString()); if (!FF4jDroolsService.isInitialized()) { FF4jDroolsService.initFromRulesFiles(files); } } /** * Generate InitParameters to be used in json serialization. * * @return * the init param map */ @Override public Map<String, String> getInitParams() { initParams.put(KEY_BASE_NAME, basename); initParams.put(KEY_RULES_FILES, getRulesFileAsString()); return initParams; } /** * Utility HashSet < String > values separated by comma. * * <p>New in Java8, Stringjoiner enhance string concatenation * * @return * hasehet to string easily */ private String getRulesFileAsString() { StringJoiner joiner = new StringJoiner(","); ruleFiles.forEach(e ->joiner.add(e)); return joiner.toString(); } /** * Initialization through initParam and not the constructor to be used by the * XML feature system. * * @param featureName * current feature unique identifier * @param initParam * attribute defined through XML configuration for instance */ @Override public void init(String featureName, Map<String, String> initParam) { super.init(featureName, initParam); if (!FF4jDroolsService.isInitialized()) { if (initParams.containsKey(KEY_BASE_NAME)) { this.basename = initParams.get(KEY_BASE_NAME); FF4jDroolsService.initFromBaseName(basename); } else if (initParams.containsKey(KEY_RULES_FILES)) { String exp = initParams.get(KEY_RULES_FILES); this.ruleFiles = new HashSet <> (Arrays.asList(exp.split(","))); FF4jDroolsService.initFromRulesFiles(ruleFiles); } else { throw new IllegalArgumentException("Init param '" + KEY_BASE_NAME + "' is required to fetch Drools settings"); } } } /** * To retrieve result for rules execution there are 2 ways: * - Modifed an existing fact * - Retrieve FacHandler from session like session.getFactHandles(filter) * * FF4J expects the fact {@link FF4JDroolsRequest} to be modified by the target rules. * By default the status is 'false'. */ @Override public boolean evaluate(String uid, FeatureStore store, FlippingExecutionContext ctx) { return FF4jDroolsService.getInstance().evaluate(new FF4jDroolsRequest(uid, store, ctx)); } /** * Getter accessor for attribute 'basename'. * * @return current value of 'basename' */ public String getBasename() { return basename; } /** * Setter accessor for attribute 'basename'. * * @param basename * new value for 'basename ' */ public void setBasename(String basename) { this.basename = basename; } /** * Getter accessor for attribute 'ruleFiles'. * * @return * current value of 'ruleFiles' */ public Set<String> getRuleFiles() { return ruleFiles; } /** * Setter accessor for attribute 'ruleFiles'. * @param ruleFiles * new value for 'ruleFiles ' */ public void setRuleFiles(Set<String> ruleFiles) { this.ruleFiles = ruleFiles; } }