/*
* JBoss, Home of Professional Open Source
* Copyright 2006, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.soa.esb.actions;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.jboss.soa.esb.ConfigurationException;
import org.jboss.soa.esb.Service;
import org.jboss.soa.esb.actions.cbr.SxcXPathRouter;
import org.jboss.soa.esb.actions.cbr.XPathRouter;
import org.jboss.soa.esb.actions.cbr.RegexRouter;
import org.jboss.soa.esb.helpers.ConfigTree;
import org.jboss.soa.esb.listeners.ListenerTagNames;
import org.jboss.soa.esb.client.ServiceInvoker;
import org.jboss.soa.esb.client.MessageMulticaster;
import org.jboss.soa.esb.listeners.message.MessageDeliverException;
import org.jboss.soa.esb.message.Message;
import org.jboss.soa.esb.message.mapping.ObjectMapper;
import org.jboss.soa.esb.message.mapping.ObjectMappingException;
import org.jboss.soa.esb.services.registry.Registry;
import org.jboss.soa.esb.services.registry.RegistryException;
import org.jboss.soa.esb.services.registry.RegistryFactory;
import org.jboss.soa.esb.services.routing.MessageRouterException;
import org.jboss.soa.esb.services.routing.cbr.ContentBasedRouterFactory;
/**
* ContentBasedWirtap implements the WireTap pattern.
* The WireTap is an Enterprise Integration Pattern (EIP) where a copy of the message is
* sent to a control channel.
* <br>
* The CBWT is identical in functionality to the ContentBasedRouter,
* however it does not terminate the pipeline which makes it suitable to be used as a WireTap.
* <p/>
*
* Configuration Example:
*<pre>{@code
*
*<action class="org.jboss.soa.esb.actions.ContentBasedRouter" name="MyAction">
* <property name="ruleSet" value="OrderDiscountOnMultipleOrders.drl" />
* <property name="ruleReload" value="false" />
* <property name="ruleFireMethod" value="FIRE_ALL_RULES" />
* <property name="ruleAuditType" value="THREADED_FILE" />
* <property name="ruleAuditFile" value="/tmp/event" />
* <property name="ruleAuditInterval" value="1000" />
* <property name="ruleClockType" value="REALTIME" />
* <property name="ruleEventProcessingType" value="STREAM" />
* <property name="stateful" value="true" />
* <property name="object-paths">
* <object-path esb="body.TheOrderHeader" />
* <object-path esb="body.TheCustomer" />
* <object-path esb="body.TheOrderStatus" entry-point="OrderStatusEntry" />
* <object-path esb="body.TheOrderInfo" entry-point="OrderInfoEntry" />
* </property>
* <property name="channels">
* <!-- chan1 and chan2 are equivalent (but timeout only applies if async == false) -->
* <send-to channel-name="chan1" service-category="cat1" service-name="svc1" />
* <send-to channel-name="chan2" channel-class="org.jboss.soa.esb.services.rules.ServiceChannel" service-category="cat1" service-name="svc1" async="true" timeout="30000" set-payload-location="org.jboss.soa.esb.message.defaultEntry" />
* <!-- a custom channel -->
* <send-to channel-name="chan3" channel-class="com.example.MyChannel" />
* </property>
* <property name="destinations">
* <route-to destination-name="blue" service-category="BlueTeam" service-name="GoBlue" />
* <route-to destination-name="red" service-category="RedTeam" service-name="GoRed" />
* <route-to destination-name="green" service-category="GreenTeam" service-name="GoGreen" />
* </property>
*</action>
*}</pre>
*
* Property description:
* <ul>
* <li> <i>class</i> action class, one of : org.jboss.soa.esb.actions.ContentBasedRouter, org.jboss.soa.esb.actions.ContentBasedWireTap,<br>
* or org.jboss.soa.esb.actions.MessageFilter
* <li> <i>ruleSet</i> Name of the filename containing the Drools ruleSet.
* <li> <i>ruleLanguage</i> Optional reference to a file containing the definition of a Domain Specific Language to be used for evaluating
* the rule set.
* <li> <i>ruleReload</i> Optional property which can be to true to enable 'hot' redeployment of rule sets.
* <li> <i>ruleFireMethod</i> Optional property that defines, for StatefulKnowledgeSessions, if fireAllRules() or fireUntilHalt() should be called. Possible values are FIRE_ALL_RULES (the default) or FIRE_UNTIL_HALT.
* <li> <i>ruleAuditType</i> Optional property that defines the type of KnowledgeRuntimeLogger used (CONSOLE, FILE, THREADED_FILE). If not defined and neither is ruleAuditFile, no auditing is done. If not defined but ruleAuditFile is, the assumption is THREADED_FILE.
* <li> <i>ruleAuditFile</i> Optional property that defines the file path for a ruleAuditType of FILE or THREADED_FILE ("event" is the default, and ".log" is always appended).
* <li> <i>ruleAuditInterval</i> Optional property that defines the interval (in milliseconds) for a ruleAuditType of THREADED_FILE (1000 is the default).
* <li> <i>ruleclockType</i> Optional property that defines the type of clock used (REALTIME, PSEUDO). Default is up to drools.
* <li> <i>ruleEventProcessingType</i> Optional property that defines the type of event processing used (STREAM, CLOUD). Default is up to drools.
* <li> <i>stateful</i> Optional property which tells the RuleService to use a stateful session where facts will be
* remembered between invokations.
* <li> <i>object-paths</i> Optional property to pass Message objects into Rule Services WorkingMemory. If an entry-point is specified, that will be placed in the StatefulSession's WorkingMemoryEntryPoint with the same name.
* <li> <i>channels</i> A set of send-to properties each containing the logical name
* of the destination along with the Service category and name
* as referenced in the registry (if the default channel-class is used),
* OR your own custom-class, which MUST implement org.drools.runtime.Channel,
* , but MAY provide a setConfig(ConfigTree):void method.
* <br/> The logical name is the name which should be used in the rule set.
* <li> <i>destinations</i> A set of route-to properties each containing the logical name
* of the destination along with the Service category and name
* as referenced in the registry.<br/> The logical name is the name
* which should be used in the rule set.
* </ul>
* </br>
*
*
* @author <a href="mailto:schifest@heuristica.com.ar">schifest@heuristica.com.ar</a>
* @author kstam@jboss.com
* @author kevin.conner@jboss.com
* @author <a href="mailto:dbevenius@redhat.com">Daniel Bevenius</a>
*/
public class ContentBasedWiretap extends AbstractActionPipelineProcessor {
public static final String ROUTE_TO_TAG = "route-to";
public static final String OBJECT_PATH_TAG = "object-path";
public static final String OBJECT_PATH = "esb";
public static final String ENTRY_POINT = "entry-point";
public static final String DEFAULT_CBR_CLASS = "org.jboss.internal.soa.esb.services.routing.cbr.JBossRulesRouter";
ServiceInvoker dlQueueInvoker;
public ContentBasedWiretap(ConfigTree config)
throws ConfigurationException, RegistryException,
MessageRouterException {
_config = config;
checkMyParms();
_registry = RegistryFactory.getRegistry();
_cbr = ContentBasedRouterFactory.getRouter(_cbrClass);
_cbr.setConfigTree(config);
_mapper = new ObjectMapper(config);
try {
dlQueueInvoker = new ServiceInvoker(ServiceInvoker.INTERNAL_SERVICE_CATEGORY, ServiceInvoker.DEAD_LETTER_SERVICE_NAME);
} catch (MessageDeliverException e) {
throw new MessageRouterException(e);
}
messageMulticaster.setAggregatorOnProperties(Aggregator.aggregatorOnProperties(config));
}
public void initialise() { }
/**
* Router the message to one or more destinations, using the
* ContentBasedRouter to figure out to which destinations it is going to
* be routed too.
*
* @param message
* @return Message
* @throws ActionProcessingException
*/
public Message process(Message message) throws ActionProcessingException
{
try {
List<Service> outgoingDestinations = executeRules(message);
if (outgoingDestinations.size()==0) {
String error = "No rule destination(s) "+ _destinations.keySet() + " were matched, "
+ ". Please fix your configuration and/or routing rules.";
_logger.error(error);
try {
_logger.debug("Sending message to the DeadLetterService");
dlQueueInvoker.deliverAsync(message);
throw new ActionProcessingException(error);
} catch (MessageDeliverException e) {
throw new MessageRouterException("Failed to deliver message to Dead Letter Channel.", e);
}
} else {
routeMessage(message, outgoingDestinations);
}
} catch (MessageRouterException e) {
throw new ActionProcessingException(e);
}
return message;
}
protected List<Service> executeRules(Message message)
throws MessageRouterException
{
List<Service> outgoingDestinations = new ArrayList<Service>();
try {
List<Object> objectList = _mapper.createObjectList(message,
_messagePathList);
List<String> destinations = _cbr.route(_ruleSet, _ruleLanguage,
_ruleReload, message, objectList);
for (String destination : destinations) {
if (_destinations.containsKey(destination)) {
outgoingDestinations.add(_destinations.get(destination));
} else {
throw new MessageRouterException("Destination " + destination + " does not exist your configuration");
}
}
} catch (ObjectMappingException ome) {
throw new MessageRouterException(ome);
}
return outgoingDestinations;
}
protected final void routeMessage(Message message, List<Service> outgoingDestinations)
throws MessageRouterException
{
try {
messageMulticaster.sendToSubset(message, outgoingDestinations);
} catch (RegistryException e) {
throw new MessageRouterException(e);
} catch (MessageDeliverException e) {
throw new MessageRouterException(e);
}
}
/**
* Reading the piece of configTree specific to the CBR, and setting the
* configuration.
*
* @throws ConfigurationException
*/
protected void checkMyParms() throws ConfigurationException {
_ruleSet = _config.getAttribute(ListenerTagNames.RULE_SET_TAG);
_ruleLanguage = _config.getAttribute(ListenerTagNames.RULE_LANGUAGE_TAG);
String ruleReload = _config.getAttribute(ListenerTagNames.RULE_RELOAD_TAG);
if (ruleReload != null && "true".equals(ruleReload)) {
_ruleReload = true;
}
String cbrAlias = _config.getAttribute(ListenerTagNames.CBR_ALIAS);
_cbrClass = _config.getAttribute(ListenerTagNames.CBR_CLASS);
if(cbrAlias != null && _cbrClass != null) {
throw new ConfigurationException("Invalid " + getClass().getSimpleName() + " configuration. Cannot configure both '" + ListenerTagNames.CBR_ALIAS + "' and '" + ListenerTagNames.CBR_CLASS + "' properties on the same action configuration.");
}
if(_cbrClass == null) {
if(cbrAlias == null) {
_cbrClass = DEFAULT_CBR_CLASS;
} else if(cbrAlias.equalsIgnoreCase("JBRules") || cbrAlias.equalsIgnoreCase("Drools")) {
_cbrClass = DEFAULT_CBR_CLASS;
} else if(cbrAlias.equalsIgnoreCase("XPath")) {
_cbrClass = XPathRouter.class.getName();
} else if(cbrAlias.equalsIgnoreCase("SXC")) {
_cbrClass = SxcXPathRouter.class.getName();
} else if(cbrAlias.equalsIgnoreCase("Regex") || cbrAlias.equalsIgnoreCase("Regexp")) {
_cbrClass = RegexRouter.class.getName();
} else {
throw new ConfigurationException("Invalid " + getClass().getSimpleName() + " configuration. Unsupported '" + ListenerTagNames.CBR_ALIAS + "' property value '" + cbrAlias + "'.");
}
}
_destinations = new HashMap<String, Service>();
ConfigTree[] destList = _config.getChildren(ROUTE_TO_TAG);
if (destList != null) {
for (ConfigTree curr : destList) {
try {
String key = buildDestinationKey(curr);
String category = curr.getAttribute(
ListenerTagNames.SERVICE_CATEGORY_NAME_TAG, "");
String name = curr
.getRequiredAttribute(ListenerTagNames.SERVICE_NAME_TAG);
Service service = new Service(category, name);
_destinations.put(key, service);
messageMulticaster.addRecipient(service);
}
catch (Exception e) {
throw new ConfigurationException(
"Problems with destination list", e);
}
}
}
_messagePathList = new ArrayList<String>();
entryPointMap = new HashMap<String, List<String>>();
ConfigTree[] objectList = _config.getChildren(OBJECT_PATH_TAG);
if (objectList != null) {
for (ConfigTree curr : objectList) {
try {
final String objectPath = curr.getRequiredAttribute(OBJECT_PATH);
final String entryPoint = curr.getAttribute(ENTRY_POINT);
if (entryPoint != null)
{
List<String> list = entryPointMap.get(entryPoint);
if (list == null)
{
list = new ArrayList<String>();
// Add the list to the entrypoint map indexed by the entry-point name.
entryPointMap.put(entryPoint, list);
}
// Add the object path to the entry-point list.
list.add(objectPath);
}
else
{
_messagePathList.add(objectPath);
}
}
catch (Exception e) {
throw new ConfigurationException( "Problems with object path list", e);
}
}
}
}
public static String buildDestinationKey(ConfigTree destinationConfig) throws ConfigurationException {
String key = destinationConfig.getAttribute(ListenerTagNames.DESTINATION_NAME_TAG);
if(key != null) {
return key;
}
// Otherwise, build a key from the service category and name...
String category = destinationConfig.getAttribute(ListenerTagNames.SERVICE_CATEGORY_NAME_TAG, "");
String name = destinationConfig.getRequiredAttribute(ListenerTagNames.SERVICE_NAME_TAG);
return category + "-" + name;
}
protected ConfigTree _config;
protected Map<String, Service> _destinations;
protected MessageMulticaster messageMulticaster = new MessageMulticaster();
protected String _cbrClass;
protected String _ruleSet;
protected String _ruleLanguage;
protected boolean _ruleReload;
protected List<String> _messagePathList;
protected Map<String, List<String>> entryPointMap;
protected ObjectMapper _mapper;
protected Registry _registry;
protected org.jboss.soa.esb.services.routing.cbr.ContentBasedRouter _cbr;
protected static final Logger _logger = Logger.getLogger(ContentBasedWiretap.class);
}