/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and others contributors as indicated
* by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* (C) 2005-2010
*/
package org.jboss.internal.soa.esb.services.rules;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.RULE_EVENT_PROCESSING_TYPE;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.StringValue.CLOUD;
import static org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.StringValue.STREAM;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Map.Entry;
import org.apache.log4j.Logger;
import org.dom4j.DocumentHelper;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.drools.ChangeSet;
import org.drools.agent.RuleAgent;
import org.drools.builder.ResourceType;
import org.drools.builder.conf.ClassLoaderCacheOption;
import org.drools.conf.EventProcessingOption;
import org.drools.conf.MaxThreadsOption;
import org.drools.conf.MultithreadEvaluationOption;
import org.drools.xml.ChangeSetSemanticModule;
import org.drools.xml.SemanticModules;
import org.drools.xml.XmlChangeSetReader;
import org.jboss.internal.soa.esb.services.rules.util.RulesClassLoader;
import org.jboss.internal.soa.esb.util.StreamUtils;
import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.services.rules.RuleInfo;
import org.jboss.soa.esb.services.rules.RuleServicePropertiesNames.StringValue;
import org.jboss.soa.esb.util.ClassUtil;
import org.xml.sax.SAXException;
/**
* Helper class which converts old RuleAgent properties to new KnowledgeAgent properties and change-set.
*
* @author dward at jboss.org
*/
public class DroolsRuleAgentHelper
{
private static final Logger logger = Logger.getLogger(DroolsRuleAgentHelper.class);
// unfortunately, there is no public KnowledgeAgent.NEW_INSTANCE
private static final String KnowledgeAgent_NEW_INSTANCE = "drools.agent.newInstance";
private static final Map<String,ResourceType> resExt_to_resType = new HashMap<String,ResourceType>();
static
{
resExt_to_resType.put(".drl", ResourceType.DRL);
resExt_to_resType.put(".xdrl", ResourceType.XDRL);
resExt_to_resType.put(".dsl", ResourceType.DSL);
resExt_to_resType.put(".dslr", ResourceType.DSLR);
resExt_to_resType.put(".drf", ResourceType.DRF);
resExt_to_resType.put(".xls", ResourceType.DTABLE);
resExt_to_resType.put(".csv", ResourceType.DTABLE);
resExt_to_resType.put(".pkg", ResourceType.PKG);
resExt_to_resType.put(".brl", ResourceType.BRL);
resExt_to_resType.put(".xml", ResourceType.CHANGE_SET);
}
private static final ResourceType getResourceType(String resource)
{
ResourceType type = null;
String resource_tlc = resource.trim().toLowerCase();
for (Entry<String,ResourceType> entry : resExt_to_resType.entrySet())
{
if (resource_tlc.endsWith(entry.getKey()))
{
type = entry.getValue();
break;
}
}
return type;
}
private final Properties properties = new Properties();
private final ClassLoader classLoader;
private String name = null;
private final ChangeSet changeSet;
private Boolean basicAuthentication = null;
private String username = null;
private String password = null;
public DroolsRuleAgentHelper(final RuleInfo ruleInfo) throws RuleServiceException
{
final String ruleAgentProperties = ruleInfo.getRuleSource();
classLoader = new RulesClassLoader(ruleAgentProperties);
String contents;
InputStream is = null;
try
{
is = ClassUtil.getResourceAsStream("/" + ruleAgentProperties, getClass());
if (is != null)
{
// we'll most likely find it here as a classloader resource
contents = StreamUtils.readStreamString(is, "UTF-8");
}
else
{
// we'll reach here if ruleAgentProperties is a File path or URL location
contents = StreamUtils.getResourceAsString(ruleAgentProperties, "UTF-8");
}
}
catch (UnsupportedEncodingException uee)
{
// should never happen with UTF-8
throw new RuleServiceException(uee);
}
catch (ConfigurationException ce)
{
throw new RuleServiceException(ce);
}
finally
{
try { if (is != null) is.close(); } catch (Throwable t) {}
}
if (ruleAgentProperties.endsWith(".xml") && contents.indexOf("<change-set") != -1)
{
// change-set
changeSet = readChangeSet(contents);
}
else
{
// properties
Properties source_props = new Properties();
InputStream source_is = null;
try
{
source_is = new ByteArrayInputStream(contents.getBytes());
source_props.load(source_is);
}
catch (IOException ioe)
{
throw new RuleServiceException(ioe);
}
finally
{
try { if (source_is != null) source_is.close(); } catch (Throwable t) {}
}
// name
name = source_props.getProperty(RuleAgent.CONFIG_NAME);
// newInstance
String newInstance_value = source_props.getProperty(KnowledgeAgent_NEW_INSTANCE);
if (newInstance_value == null)
{
newInstance_value = source_props.getProperty(RuleAgent.NEW_INSTANCE);
}
if (newInstance_value != null)
{
properties.setProperty(KnowledgeAgent_NEW_INSTANCE, newInstance_value);
}
// BASIC Auth
String eba = source_props.getProperty(RuleAgent.ENABLE_BASIC_AUTHENTICATION);
if (eba != null)
{
basicAuthentication = Boolean.valueOf(eba);
if (basicAuthentication.booleanValue())
{
username = source_props.getProperty(RuleAgent.USER_NAME);
password = source_props.getProperty(RuleAgent.PASSWORD);
}
}
// CEP
StringValue eventProcessingType = RULE_EVENT_PROCESSING_TYPE.getStringValue(ruleInfo.getEventProcessingType());
if (STREAM.equals(eventProcessingType))
{
properties.setProperty(EventProcessingOption.PROPERTY_NAME, EventProcessingOption.STREAM.getMode());
}
else if (CLOUD.equals(eventProcessingType))
{
properties.setProperty(EventProcessingOption.PROPERTY_NAME, EventProcessingOption.CLOUD.getMode());
}
Boolean multithreadEvaluation = ruleInfo.getMultithreadEvaluation();
if (multithreadEvaluation != null) {
MultithreadEvaluationOption meo = multithreadEvaluation.booleanValue() ? MultithreadEvaluationOption.YES : MultithreadEvaluationOption.NO;
properties.setProperty(MultithreadEvaluationOption.PROPERTY_NAME, meo.name());
// only pertinent if multithreadEvaluation == true
Integer maxThreads = ruleInfo.getMaxThreads();
if (maxThreads != null) {
properties.setProperty(MaxThreadsOption.PROPERTY_NAME, maxThreads.toString());
}
}
// leftover properties
Enumeration<?> source_prop_names = source_props.propertyNames();
while (source_prop_names.hasMoreElements())
{
String source_prop_name = (String)source_prop_names.nextElement();
String source_prop_value = source_props.getProperty(source_prop_name);
if (source_prop_name.startsWith("drools.") && !properties.containsKey(source_prop_name))
{
properties.setProperty(source_prop_name, source_prop_value);
}
else if (source_prop_name.equals(RuleAgent.POLL_INTERVAL) || source_prop_name.equals(RuleAgent.LOCAL_URL_CACHE))
{
logger.warn("RuleAgent property [" + source_prop_name + "=" + source_prop_value + "] is unsupported by KnowledgeAgent in properties [" + ruleAgentProperties + "]");
}
}
// change-set
StringBuffer sb = new StringBuffer();
sb.append("<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\">");
addResourceDirectories(list(source_props.getProperty(RuleAgent.DIRECTORY)), sb);
addResourceFiles(list(source_props.getProperty(RuleAgent.FILES)), sb);
addResourceURLs(list(source_props.getProperty(RuleAgent.URLS)), sb);
sb.append("</change-set>");
String xml = sb.toString();
if (logger.isDebugEnabled())
{
try
{
StringWriter sw = new StringWriter();
OutputFormat of = OutputFormat.createPrettyPrint();
of.setSuppressDeclaration(true);
new XMLWriter(sw, of).write(DocumentHelper.parseText(xml.replaceAll("&", "&")));
logger.debug("created ChangeSet XML [" + sw.toString().replaceAll("&", "&") + "]");
}
catch (Exception e)
{
logger.warn("problem pretty-printing ChangeSet: " + e.getMessage());
logger.debug("created ChangeSet XML [" + xml + "]");
}
}
changeSet = readChangeSet(xml);
}
// If this isn't false, then all rules' LHS object conditions will not match on .esb redeploys!
// (since objects are only equal if they're classloaders are also equal - and they're not on redploys)
properties.setProperty(ClassLoaderCacheOption.PROPERTY_NAME, Boolean.FALSE.toString());
}
private ChangeSet readChangeSet(String xml) throws RuleServiceException
{
SemanticModules semanticModules = new SemanticModules();
semanticModules.addSemanticModule(new ChangeSetSemanticModule());
XmlChangeSetReader reader = new XmlChangeSetReader(semanticModules);
reader.setClassLoader(classLoader, null);
try
{
return reader.read(new StringReader(xml));
}
catch (SAXException se)
{
throw new RuleServiceException(se);
}
catch (IOException ioe)
{
throw new RuleServiceException(ioe);
}
}
private void addResourceDirectories(List<String> prop_values, StringBuffer sb) throws RuleServiceException
{
for (String prop_value : prop_values)
{
File dir = new File(prop_value.trim());
if (dir.isDirectory())
{
Set<ResourceType> res_types = new HashSet<ResourceType>();
List<String> res_list = new ArrayList<String>();
for (File file : dir.listFiles())
{
if (file.isFile())
{
String path = file.getAbsolutePath();
ResourceType type = getResourceType(path);
if (type != null)
{
res_types.add(type);
res_list.add(path);
}
else
{
logger.warn("could not determine ResourceType for file: " + path);
}
}
}
int res_types_size = res_types.size();
if (res_types_size == 1)
{
// homogenous
URL url;
try
{
url = dir.toURL();
}
catch (MalformedURLException mue)
{
throw new RuleServiceException(mue);
}
addResource(url.toString(), res_types.iterator().next(), sb);
}
else if (res_types_size > 1)
{
// heterogenous
addResourceFiles(res_list, sb);
}
}
}
}
private void addResourceFiles(List<String> prop_values, StringBuffer sb) throws RuleServiceException
{
for (String prop_value : prop_values)
{
File file = new File(prop_value.trim());
if (file.isFile())
{
String path = file.getAbsolutePath();
ResourceType type = getResourceType(path);
if (type != null)
{
URL url;
try
{
url = file.toURL();
}
catch (MalformedURLException mue)
{
throw new RuleServiceException(mue);
}
addResource(url.toString(), type, sb);
}
else
{
logger.warn("could not determine ResourceType for file: " + path);
}
}
}
}
private void addResourceURLs(List<String> prop_values, StringBuffer sb) throws RuleServiceException
{
for (String prop_value : prop_values)
{
URI uri;
try
{
uri = new URI(prop_value.trim());
}
catch (URISyntaxException use)
{
throw new RuleServiceException(use);
}
if (uri.isAbsolute())
{
URL url;
try
{
url = uri.toURL();
}
catch (MalformedURLException mue)
{
throw new RuleServiceException(mue);
}
String location = url.toString();
ResourceType type = getResourceType(location);
if (type == null)
{
type = ResourceType.PKG;
}
addResource(location, type, sb, true);
}
}
}
private void addResource(String source, ResourceType type, StringBuffer sb)
{
addResource(source, type, sb, false);
}
private void addResource(String source, ResourceType type, StringBuffer sb, boolean isResourceURL)
{
String source_tlc = source.trim().toLowerCase();
sb.append("<add><resource source=\"");
sb.append(source);
sb.append("\" type=\"");
sb.append(type.getName());
if (isResourceURL)
{
if (source_tlc.startsWith("http://") || source_tlc.startsWith("https://"))
{
if ((basicAuthentication != null) && basicAuthentication.booleanValue())
{
sb.append("\" basicAuthentication=\"enabled");
if (username != null)
{
sb.append("\" username=\"");
sb.append(username);
}
if (password != null)
{
sb.append("\" password=\"");
sb.append(password);
}
}
}
}
boolean closeResourceElement = true;
if (ResourceType.DTABLE.equals(type))
{
if (source_tlc.endsWith(".xls"))
{
sb.append("\"><decisiontable-conf input-type=\"XLS\"/></resource>");
closeResourceElement = false;
}
else if (source_tlc.endsWith(".csv"))
{
sb.append("\"><decisiontable-conf input-type=\"CSV\"/></resource>");
closeResourceElement = false;
}
}
if (closeResourceElement)
{
sb.append("\"/>");
}
sb.append("</add>");
}
// copied (and modified for generics) from package-protected RuleAgent.list(String):List
private List<String> list(String property) {
if ( property == null ) return Collections.<String>emptyList();
char[] cs = property.toCharArray();
boolean inquotes = false;
List<String> items = new ArrayList<String>();
String current = "";
for ( int i = 0; i < cs.length; i++ ) {
char c = cs[i];
switch ( c ) {
case '\"' :
if ( inquotes ) {
items.add( current );
current = "";
}
inquotes = !inquotes;
break;
default :
if ( !inquotes && (c == ' ' || c == '\n' || c == '\r' || c == '\t') ) {
if ( !"".equals( current.trim() ) ) {
items.add( current );
current = "";
}
} else {
current = current + c;
}
break;
}
}
if ( !"".equals( current.trim() ) ) {
items.add( current );
}
return items;
}
public Properties getProperties()
{
return properties;
}
public ClassLoader getClassLoader()
{
return classLoader;
}
public String getName()
{
return name;
}
public ChangeSet getChangeSet()
{
return changeSet;
}
}