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.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.util.HashSet; import java.util.Set; import org.drools.core.ClockType; import org.kie.api.KieServices; import org.kie.api.conf.EventProcessingOption; import org.kie.api.io.ResourceType; import org.kie.api.runtime.KieContainer; import org.kie.api.runtime.KieSession; import org.kie.api.runtime.KieSessionConfiguration; import org.kie.api.runtime.conf.ClockTypeOption; import org.kie.api.runtime.rule.FactHandle; import org.kie.internal.KnowledgeBaseFactory; import org.kie.internal.utils.KieHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Singleton pattern to instanciate drool Session once and be reused for each feature. * * <p>It can be initialized in 2 ways, from kbase name or a list of drl files. * * @author Cedrick Lunven (@clunven)</a> */ public final class FF4jDroolsService implements Serializable { /** Serial. */ private static final long serialVersionUID = -4732368029311891671L; /** logger provide by drools. */ private static final Logger LOGGER = LoggerFactory.getLogger(FF4jDroolsFlippingStrategy.class); /** Protected instance. */ private static FF4jDroolsService _instance; /** Drools services first level. */ private KieServices kieServices; /** Container for sessions. */ private KieContainer kieContainer; /** Working Session. */ private KieSession ksession; /** base name coming from strategy. */ private String basename; /** drl files coming from strategy. */ private Set<String> ruleFiles = new HashSet<>(); /** * Implementation of singleton pattern (Hide Constructor). */ private FF4jDroolsService() { } /** * Session must be initialized once. * * @return * singleton already created. */ public static synchronized boolean isInitialized() { return _instance != null && _instance.ksession != null; } /** * Implementation of singleton pattern. * * @return */ public static synchronized FF4jDroolsService getInstance() { if (!isInitialized()) { throw new IllegalStateException("The service has not been initialized yet, " + "please init with initFromBaseName() or initFromRulesFiles()"); } return _instance; } /** * Drools expects to find the 'kmodule.xml' file in src/main/resources/META-INF. * It must contain a definition of kbase and kession with same base name exemple : * * <?xml version="1.0" encoding="UTF-8"?> * <kmodule xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://jboss.org/kie/6.0.0/kmodule"> * <kbase name="whatever_you_want" packages="org..."> * <ksession name="ff4jDroolsStrategy"/> * </kbase> * </kmodule> */ public static synchronized void initFromBaseName(String baseName) { if (isInitialized()) { throw new IllegalStateException("This Factory has already be initialized once"); } _instance = new FF4jDroolsService(); _instance.basename = baseName; _instance.kieServices = KieServices.Factory.get(); _instance.kieContainer = _instance.kieServices.newKieClasspathContainer(); _instance.ksession = _instance.kieContainer.newKieSession(baseName); if (_instance.ksession == null) { throw new IllegalArgumentException("Cannot find kName " + baseName + " , check kmodule.xml file."); } } /** * Initialisation of Drools stateful session without convention and kodmule files. * * @param ruleFiles * DRL files */ public static synchronized void initFromRulesFiles(Set < String > ruleFiles) { if (isInitialized()) { throw new IllegalStateException("This Factory has already be initialized once"); } _instance = new FF4jDroolsService(); _instance.ruleFiles = ruleFiles; KieHelper helper = new KieHelper(); KieSessionConfiguration sessionConfig = KnowledgeBaseFactory.newKnowledgeSessionConfiguration(); sessionConfig.setOption(ClockTypeOption.get(ClockType.PSEUDO_CLOCK.getId())); //ruleFiles.stream().forEach(drlFile -> helper.addContent(loadResourceAsString(drlFile), ResourceType.determineResourceType(drlFile))); for (String drlFile : ruleFiles) { String fileContent = loadResourceAsString(drlFile); ResourceType typeFile = ResourceType.determineResourceType(drlFile); helper.addContent(fileContent, typeFile); } _instance.ksession = helper.build(EventProcessingOption.STREAM).newKieSession(sessionConfig, null); } /** {@inheritDoc} */ public boolean evaluate(FF4jDroolsRequest request) { /* * To retrieve result for rules execution there are 2 ways: - Modifed an existing fact - Retrieve FacHandler from session * : session.getFactHandles(filter) * * FF4J expects the fact {@link FF4JDroolsRequest} to be modified by the target rules. By default the status is 'false'. */ _instance.ksession.setGlobal("store", request.getFeatureStore()); // FactHandle drHandler = ksession.insert(droolsRequest); FactHandle requestHandle = _instance.ksession.insert(request); // Execute the rules ksession.fireAllRules(); // clean session, note that retract() is deprecated _instance.ksession.delete(requestHandle); //_instance.ksession.dispose(); LOGGER.debug("Evaluating feature " + request.getFeatureName() + " to " + request.isToggled()); return request.isToggled(); } /** * Load classpath resource as String (here DRL) * * @param resourceName * target resources * @return * file content as string */ private final static String loadResourceAsString(final String resourceName) { InputStream rin = null; try { rin = ClassLoader.getSystemResourceAsStream(resourceName); StringBuilder out = new StringBuilder(); byte[] b = new byte[4096]; int n = 0; while (n != -1) { out.append(new String(b, 0, n)); n = rin.read(b); } return out.toString(); } catch (IOException ioe) { throw new IllegalArgumentException("Cannot read resource " + resourceName, ioe); } finally { if(rin != null) { try { rin.close(); } catch (IOException e) { LOGGER.error("Exception while closing resource", e); } } } } /** * Getter accessor for attribute 'basename'. * * @return * current value of 'basename' */ public String getBasename() { return basename; } /** * Getter accessor for attribute 'kieServices'. * * @return * current value of 'kieServices' */ public KieServices getKieServices() { return kieServices; } /** * Getter accessor for attribute 'ruleFiles'. * * @return * current value of 'ruleFiles' */ public Set<String> getRuleFiles() { return ruleFiles; } }