Enterprise Integration Patterns - Content Based Router
Overview
In many business scenarios it is imperative that documents are processed based on their content. Several processing systems are integrated together to provide a unified solution. Messages need to be routed to the right component based on the requirement, which could be discerned only after examining the message content. The Content Based Router addresses this space.
Use Cases
The Content-Based Router examines the message content and routes the message onto a different route based on data contained in the message. The routing can be based on a number of criteria, such as existence of fields, specific field values, and so on.
Routing Orders
Purchase orders need to be routed through different processing steps ( credit check, stock update etc ) before they can be accepted. Based on the type of item being purchased the request can be routed using CBR.
IFL
Syntax (Incompatibly updated in M9. M8 or older IFL needs to be rewritten.)
select [rules-identifier rules-definition]
[ do
[when [ value | condition-identifier inline-rules-definition] to [route-name | service-name] ]*
[else to route-name]
end ]
where:
rules-identifier - provides the semantic information required for rules processing
rules-definition - the rules, syntax depends on the rules-identifier.
rules-identifier = xpath | regex | header | any
condition-identifier = xpath | regex | header
rules-definition = external identifier | inline-rules-definition
inline-rules-definition = ( configuration-string )
Rules come in two flavors: value based or condition based. A value based rule returns a value that is than matched for equality in a value based when clause. A condition based rule is evaluated as a boolean expression as part of a condition based when clause. Value and condition based when clause flavors can't be mixed in the same select statement. By default, the first matching when clause is selected and its route triggered. An (optional) else statement at the end can be used to execute a default route if none of the when conditions are satisfied. A select with only a else statement is legal and can be used as a catch-all when dynamic updates to the rule set is used.
There exists one configuration parameter: match=first|last|unique. Default value is first. In this case the first condition based rule that matches will trigger. If set to last, the last condition based rule that matches will trigger. If set to unique, the condition based rule matching will trigger only if a single rule is found to match. Otherwise, if multiple rules match, or no rules match, the else clause will be selected.
Examples
Value based route selection
email "exception-service"
route do
from "inbound"
select xpath ("/Transaction/Type") do
when "CASH" to "cash-route"
when "CREDIT" to "credit-route"
when "DEBIT" to "debit-route"
else to "exception-service"
end
Value base: values converted to String for comparision
email "other-service"
route do
from "inbound"
select xpath ("/Transaction/Class='Special'") do
when "true" to "special-route"
when "false" to "other-service"
else to "other-transaction"
end
Condition based route selection. When a type other than 'any' used, like xpath in this example, the condition based when clauses must use the same type (xpath is this case.)
route do
from "inbound"
select xpath "my-select" do
when xpath ("/Transaction/Type='CASH'") to "cash-route"
when xpath ("/Transacton/Amount>5000") to "large-amount"
when xpath ("/Transaction/Profile='Special'") to "special-handling"
else to "other-transaction"
end
Condition based: dynamic rules
route do
from "inbound"
select "dynamic-type-selection" do
else to "other-transaction"
end
Condition based: multiple rules types. Any is the default type when not specified in the select statement.
route do
from "inbound"
select do
when xpath ("/Transaction/Type='CASH'") to "cash-route"
when regex ("Text-") to "non-xml-handling"
when header ("nm.prop=special.property") to "special-handling"
else to "other-transaction"
end
Additional functionality
One feature that is more useful in this context is the group support in Regex. If a Regex group is detected, the filter selects the
Group(1) 
value as its result. This allows extracting a value to be used as a when expression test.
Sample IFL:
select header ("nm.attachment=MyAttachment,exp=(\w/\w);.*")
when "text/xml" to "xml-reader"
when "application/octet-stream" to "binary-reader"
else "unsupported-attachment-type"
If the attachment content type is "text/xml;ISO-8859-1", "text/xml" is returned.
Another useful feature is a new property that controls the selection policy. By default, the selection policy is first match. Also, rules can be grouped and ordered within groups to control the evaluation order. The selection policy can be changed to either last match or unique match. When last match is used, the last matching rule triggers, or the else if there is no match. When unique match is used, a rule triggers only if it is the only rule that matches. In the case of multiple matches, the else clause will be selected if it exists. Otherwise, an error is returned.
Example of match property:
select any ("match=unique") do
when xpath "/Value=5" to "service-a"
when xpath "/Time=now" to "service=b"
else to "service-c"
end
If Value==5 and Time==now then the else clause will be triggered.
If, in the above case, match is set to first then "service-a" would be invoked, and if match is set to last then "service-b" would be invoked.
Dynamic update examples
All condition based rules can be dynamically updated. Currently this is only available using a set of Felix CLI commands.
Using the following sample IFL
route do
from "in"
select any "named-select" do
when xpath ("/Trade/Type='CASH'") to "route-cash"
when xpath ("/Trade/Amount>600000") to "route-big"
else to "route-cash"
end
end
Lets walk through the basic CLI commands.
fuji list-eips
EIP Names
---------
[ 0] named-select
- fuji list-eip-configuration
fuji list-eip-configuration 0
-> fuji list-eip-configurations 0
Configurations For EIP named-select
-----------------------------------
defaultRules
Note: The value inside the
(0 in this case) can be used as a short hand.
- fuji show-eip-configuration
fuji show-eip-configuration 0 --config-id defaultRules
fuji show-eip-configuration --config-id defaultRules --eip-id named-select
app-ns: "http://fuji.dev.java.net/application/ifl2"
config:
- !java.util.HashMap
to: route-cash
condition: "/Trade/Type='CASH'"
type: xpath
- !java.util.HashMap
to: route-big
condition: /Trade/Amount>600000
type: xpath
config-id: defaultRules
- fuji add-eip-configuration
Contents of input file:
--------------------------------------------------
config-id:myConfig1
app-ns: http://fuji.dev.java.net/application/ifl1
type: select
name: dynamic-select
enabled: true
priority: 5
config:
- type: xpath
condition: "//Trade/Type=CASH"
to: endpoint-1
- type: xpath
condition: "//Trade/Type=BOND"
to: endpoint-2
--------------------------------------------------
fuji add-eip-configuration 0 --file config.tml
fuji list-eip-configurations 0
Configurations For EIP named-select
-----------------------------------
defaultRules
myConfig1
fuji show-eip-configuration 0 --config-id myConfig1
enabled: true
priority: 5
app-ns: "http://fuji.dev.java.net/application/ifl2"
name: named-select
config:
- !java.util.HashMap
to: endpoint-1
condition: //Trade/Type=CASH
type: xpath
- !java.util.HashMap
to: endpoint-2
condition: //Trade/Type=BOND
type: xpath
config-id: myConfig1
type: select
Note: list-eip-configurations lists the rules sets in priority order from highest to lowest.
- fuji update-eip-configuration
Contents of the update file:
-------------------------------------------------
config-id:myConfig1
app-ns: http://fuji.dev.java.net/application/ifl2
type: select
name: named-select
enabled: false
priority: 200
-------------------------------------------------
fuji update-eip-configuration 0 --file update.yml
fuji list-eip-configurations 0
Configurations For EIP named-select
-----------------------------------
myConfig1
defaultRules
fuji show-eip-configuration 0 --config-id myConfig1
enabled: false
priority: !java.lang.Double 200.0
name: named-select
app-ns: "http://fuji.dev.java.net/application/ifl2"
config:
- !java.util.HashMap
to: endpoint-1
condition: //Trade/Type=CASH
type: xpath
- !java.util.HashMap
to: endpoint-2
condition: //Trade/Type=BOND
type: xpath
config-id: myConfig1
type: select
Note: The enabled and priority values can be updated independently from the rules. In this case we are changing the priority and disabling the rule set leaving the actual rules intact.
- fuji remove-eip-configuration
fuji remove-eip-configuration 0 --config-id myConfig1
fuji list-eips
EIP Names
---------
[ 1] named-select
fuji list-eip-configurations 1
Configurations For EIP named-select
-----------------------------------
defaultRules
Configuration
The rules for Content Based Routing could be inline or defined in an external configuration file. The external configuration will be generated similar to other EIPs such as Split, Aggregate :
- Generation - A default implementation should be provided that results in an empty file with the CBR ruleset name used as the file name.
The directory layout for generated configuration should be:
app-root/
cbr/
{name}/
[configuration]
Where '{name}' is the named ruleset as defined in the IFL. If the named rule set has a default value it will be located in the cbr/{name}/-default-.yml. The default value comes from any condition based when clauses present in the IFL source. Additional rules can be added dynamically. The priority for the default rule group is 100.0.
Content Based Routing uses a similar configuration as filter.