/*
jBilling - The Enterprise Open Source Billing System
Copyright (C) 2003-2011 Enterprise jBilling Software Ltd. and Emiliano Conde
This file is part of jbilling.
jbilling is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
jbilling is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with jbilling. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Created on Apr 15, 2003
*
*/
package com.sapienter.jbilling.server.pluggableTask;
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.drools.KnowledgeBase;
import org.drools.agent.KnowledgeAgent;
import org.drools.agent.KnowledgeAgentFactory;
import org.drools.io.ResourceChangeScannerConfiguration;
import org.drools.io.ResourceFactory;
import org.drools.io.impl.ByteArrayResource;
import org.drools.runtime.StatefulKnowledgeSession;
import org.drools.runtime.rule.FactHandle;
import com.sapienter.jbilling.common.Util;
import com.sapienter.jbilling.server.pluggableTask.admin.ParameterDescription;
import com.sapienter.jbilling.server.pluggableTask.admin.PluggableTaskDTO;
import com.sapienter.jbilling.server.pluggableTask.admin.PluggableTaskException;
import com.sapienter.jbilling.server.pluggableTask.admin.PluggableTaskParameterDTO;
public abstract class PluggableTask {
public static final SimpleDateFormat PARAMETER_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd-HHmm");
protected Map<String, String> parameters = null;
private Integer entityId = null;
private PluggableTaskDTO task = null;
protected Hashtable<Object, FactHandle> handlers = null;
protected StatefulKnowledgeSession session = null;
private static final Logger LOG = Logger.getLogger(PluggableTask.class);
private static HashMap<Integer, KnowledgeAgent> knowledgeBasesCache = new HashMap<Integer, KnowledgeAgent>();
private static AtomicBoolean isRulesChangeScanerStarted = new AtomicBoolean(false);
public final List<ParameterDescription> descriptions = new ArrayList<ParameterDescription>();
public List<ParameterDescription> getParameterDescriptions() {
return descriptions;
}
protected Integer getEntityId() {
return entityId;
}
public void setEntityId(Integer entityId) {
this.entityId = entityId;
}
public Integer getTaskId() {
return task.getId();
}
public void initializeParamters(PluggableTaskDTO task)
throws PluggableTaskException {
Collection<PluggableTaskParameterDTO> DBparameters = task.getParameters();
parameters = new HashMap<String, String>();
entityId = task.getEntityId();
this.task = task;
if (DBparameters.size() <
task.getType().getMinParameters().intValue()) {
throw new PluggableTaskException("Type [" + task.getType().getClassName() + "] requires at least " +
task.getType().getMinParameters() + " parameters." +
DBparameters.size() + " found.");
}
if (DBparameters.isEmpty()) {
return;
}
for (PluggableTaskParameterDTO parameter : DBparameters) {
Object value = parameter.getIntValue();
if (value == null) {
value = parameter.getStrValue();
if (value == null) {
value = parameter.getFloatValue();
}
}
// change: all the parameters will be strings in jB3. TODO: drop the int_value, float_value columns
parameters.put(parameter.getName(), value.toString());
}
}
/**
* Returns the plug-in parameter value as a String if it exists, or
* returns the given default value if it doesn't
*
* @param key plug-in parameter name
* @param defaultValue default value if parameter not defined
* @return parameter value, or default if not defined
*/
protected String getParameter(String key, String defaultValue) {
String value = parameters.get(key);
return StringUtils.isNotBlank(value) ? value : defaultValue;
}
/**
* Returns the plug-in parameter value as an Integer if it exists, or
* returns the given default value if it doesn't
*
* @param key plug-in parameter name
* @param defaultValue default value if parameter not defined
* @return parameter value, or default if not defined
*/
protected Integer getParameter(String key, Integer defaultValue) throws PluggableTaskException {
String value = parameters.get(key);
try {
return StringUtils.isNotBlank(value) ? Integer.parseInt(value) : defaultValue;
} catch (NumberFormatException e) {
throw new PluggableTaskException(key + " could not be parsed as an integer!", e);
}
}
/**
* Returns the plug-in parameter value as a boolean value if it exists, or
* returns the given default value if it doesn't.
*
* "true" and "True" equals Boolean.TRUE, all other values equate to false.
*
* @param key plug-in parameter name
* @param defaultValue default value if parameter not defined
* @return parameter value, or default if not defined
*/
protected Boolean getParameter(String key, Boolean defaultValue) {
String value = parameters.get(key);
return StringUtils.isNotBlank(value) ? (value).equalsIgnoreCase("true") : defaultValue;
}
/**
* Returns the plug-in parameter value as a Date value if it exists, or
* returns the given default value if it doesn't.
*
* Parameter date strings must be in the format "yyyyMMdd-HHmm"
*
* @param key plug-in parameter name
* @param defaultValue default value if parameter not defined
* @return parameter value, or default if not defined
* @throws PluggableTaskException thrown if parameter could not be parsed as a date
*/
protected Date getParameter(String key, Date defaultValue) throws PluggableTaskException {
String value = parameters.get(key);
try {
return StringUtils.isNotBlank(value) ? PARAMETER_DATE_FORMAT.parse(value) : defaultValue;
} catch (ParseException e) {
throw new PluggableTaskException(key + " could not be parsed as a date!", e);
}
}
protected KnowledgeBase readKnowledgeBase() {
if (knowledgeBasesCache.containsKey(task.getId())) {
return knowledgeBasesCache.get(task.getId()).getKnowledgeBase();
}
// Creating agent with default KnowledgeAgentConfiguration for scanning files and directories
KnowledgeAgent kAgent = KnowledgeAgentFactory.newKnowledgeAgent("Knowledge agent for task#" + task.getId());
// Adding resources for observing by KnowledgeAgent and creating KnowledgeBase.
// Current version of api (5.0.1) does not implement adding resources from KnowledgeBase,
// that was mentioned in api documentation (may be bug in source code).
// So, we use other aproach for configuring KnowledgeAgent
// Now agent interface allowes defining resources and directories for observing
// only through ChangeSet from Resource (usually xml config file)
// We create needed configuration dynamically as string
// from task parameters information
kAgent.applyChangeSet(new ByteArrayResource(createChangeSetStringFromTaskParameters().getBytes()));
// Cache agent for further usage without recreation
knowledgeBasesCache.put(task.getId(), kAgent);
// Start scanning services for automatical updates of cached agents
startRulesScannerIfNeeded();
return kAgent.getKnowledgeBase();
}
public static void invalidateRuleCache(Integer taskId) {
knowledgeBasesCache.remove(taskId);
}
protected void executeStatefulRules(StatefulKnowledgeSession session, List context) {
handlers = new Hashtable<Object, FactHandle>();
for (Object o : context) {
if (o != null) {
LOG.debug("inserting object " + o);
handlers.put(o, session.insert(o));
} else {
LOG.warn("Attempted to insert a NULL object into the working memeory");
}
}
session.fireAllRules();
session.dispose();
handlers.clear();
handlers = null;
}
protected void removeObject(Object o) {
FactHandle h = handlers.get(o);
if (h != null) {
LOG.debug("removing object " + o + " hash " + o.hashCode());
session.retract(h);
handlers.remove(o);
}
}
/*
public void updateObject(Object oldO, Object newO)
throws TaskException {
removeObject(oldO);
LOG.debug("inserting object " + newO + "hash " + newO.hashCode());
handlers.put(newO, session.insert(newO));
// session.fireAllRules(); // could it lead to infinite recurring loop?
}
*/
/**
* Creating ChangeSet configuration from task parameters
* for obserivng KnowledgeBase
*
* @return xml-configuration string
*/
private String createChangeSetStringFromTaskParameters() {
// todo: may be some problems (messages in console) now with xml validation during parsing
// but it's recomended in api-documentation schemas
StringBuilder str = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<change-set xmlns='http://drools.org/drools-5.0/change-set' " +
" xmlns:xs='http://www.w3.org/2001/XMLSchema-instance' " +
" xs:schemaLocation='http://drools.org/drools-5.0/change-set drools-change-set-5.0.xsd' >");
str.append("<add>");
String defaultDir = Util.getSysProp("base_dir") + "rules";
String prefix;
for (String key : parameters.keySet()) {
String value = (String) parameters.get(key);
LOG.debug("processing parameter " + key + " value " + value);
if (StringUtils.isNotEmpty(value)) {
if (key.equals("file")) {
String[] files = com.sapienter.jbilling.server.util.Util
.csvSplitLine(value, ' ');
for (String file : files) {
prefix = "";
if (!(new File(file)).isAbsolute()) {
// prepend the default directory if file path is relative
prefix = defaultDir + File.separator;
}
LOG.debug("adding parameter " + file);
appendResource(str, "file:" + prefix + file, "PKG");
}
} else if (key.equals("dir")) {
String[] dirs = com.sapienter.jbilling.server.util.Util
.csvSplitLine(value, ' ');
for (String dir : dirs) {
prefix = "";
if (!new File(dir).isAbsolute()) {
// prepend the default directory if directory path is relative
prefix = defaultDir + File.separator;
}
LOG.debug("adding parameter " + dir);
appendResource(str, "file:" + prefix + dir, "PKG");
}
} else if (key.equals("url")) {
String[] urls = com.sapienter.jbilling.server.util.Util
.csvSplitLine(value, ' ');
for (String url : urls) {
LOG.debug("adding parameter " + url);
appendResource(str, url, "PKG");
}
} else {
//for other types of resources
LOG.warn("Resource for parameter " + key + "->" + value + " not supported");
}
}
}
if (parameters.isEmpty()) {
appendResource(str, "file:" + defaultDir, "PKG");
LOG.debug("No task parameters, using directory default:" + defaultDir);
}
str.append("</add>");
str.append("</change-set>");
return str.toString();
}
private void appendResource(StringBuilder builder, String source, String type) {
builder.append("<resource source='");
builder.append(source);
builder.append("' type='");
builder.append(type);
builder.append("' />");
}
private void startRulesScannerIfNeeded() {
if (!isRulesChangeScanerStarted.getAndSet(true)) {
// Set the interval on the ResourceChangeScannerService if it presented in configuration. Default value - 60s.
if (Util.getSysProp("rules_scanner_interval") != null) {
ResourceChangeScannerConfiguration sconf = ResourceFactory.getResourceChangeScannerService().newResourceChangeScannerConfiguration();
// set the disk scanning interval to 30s, default is 60s
sconf.setProperty("drools.resource.scanner.interval", Util.getSysProp("rules_scanner_interval"));
ResourceFactory.getResourceChangeScannerService().configure(sconf);
}
// Starting services for scanning rules updates
ResourceFactory.getResourceChangeNotifierService().start();
ResourceFactory.getResourceChangeScannerService().start();
}
}
public boolean validate() {
if (getParameterDescriptions() != null) {
// validate that those required are present
for (ParameterDescription param: getParameterDescriptions()) {
if (param.isRequired()) {
if(parameters == null || !parameters.containsKey(param.getName())) {
return false;
}
}
}
}
return true;
}
/**
* The getter and setter methods for the class field parameters
* is provided only for the sole purpose of injecting a pluggable
* task via spring configuration for tests that run without the
* jbilling running.
* @return
*/
public Map<String, String> getParameters() {
return parameters;
}
public void setParameters(Map<String, String> parameters) {
this.parameters = parameters;
}
}