Chapter 5. Validation
5.1. Rules in Smooks
In Smooks, rules are a general concept, not something specific to a particular cartridge.
You can configure and reference a RuleProvider from other components.
Note
The only cartridge that uses rules functionality is the validation cartridge.
Rules are centrally defined through ruleBases. A single Smooks configuration can refer to many ruleBase definitions. A rulesBase configuration has a name, a rule
src
and a rule provider.
The format of the rule source is entirely dependent on the provider implementation. The only requirement is that the individual rules be uniquely named (within the context of a single source) so they can be referenced.
5.2. Configuring Rules in Smooks
Here is an example ruleBase configuration:
<?xml version="1.0"?> <smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd" xmlns:rules="http://www.milyn.org/xsd/smooks/rules-1.0.xsd"> <rules:ruleBases> <rules:ruleBase name="regexAddressing" src="/org/milyn/validation/address.properties" provider="org.milyn.rules.regex.RegexProvider" /> <rules:ruleBase name="order" src="/org/milyn/validation/order/rules/order-rules.csv" provider="org.milyn.rules.mvel.MVELProvider"/> </rules:ruleBases> </smooks-resource-list>
5.3. Mandatory Configurations for the rules:ruleBase Configuration Element
- name: this is used by other components to refer to this rule.
- src: this can be a file or anything else that is meaningful to the RuleProvider.
- provider: This is the actual provider implementation. In the configuration above, there is one RuleProvider that uses regular expressions but you can specify multiple ruleBase elements and have as many RuleProviders as you need. .
5.4. Rule Providers
Rule providers implement the
org.milyn.rules.RuleProvider
interface.
Smooks comes pre-configured to support two RuleProvider implementations:
- RegexProvider
- MVELProvider
You can also create your own RuleProvider implementations.
5.5. The RegexProvider
The RegexProvider utilises regular expressions. It allows you to define low-level rules specific to the format of selected data fields in the message being filtered. For example, it may be applied to a particular field to validate the syntax to make sure the right e-mail address is being used.
5.6. Configuring a Regex ruleBase
- Use this example code to configure a Regex ruleBase:
<?xml version="1.0"?> <smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd" xmlns:rules="http://www.milyn.org/xsd/smooks/rules-1.0.xsd"> <rules:ruleBases> <rules:ruleBase name="customer" src="/org/milyn/validation/order/rules/customer.properties" provider="org.milyn.rules.regex.RegexProvider"/> </rules:ruleBases> </smooks-resource-list>
- Define the Regex expressions in a standard
.properties
file format. The followingcustomer.properties
Regex rule definition file example shows you how:# Customer data rules... customerId=[A-Z][0-9]{5} customerName=[A-Z][a-z]*, [A-Z][a-z]
5.7. The MVEL Provider
The MVEL provider allows you to define rules as MVEL expressions. These expressions are executed over the contents of the Smooks Javabean context. You should to bind data from the message being filtered into Java objects in the Smooks bean context.
MVEL allows you to define more complex rules on message fragments. (Such as "Is the product in the targeted order item fragment within the age eligibility constraints of the customer specified in the order header details?")
5.8. Configuring an MVEL ruleBase
- To configure an MVEL ruleBase, see the code below:
<?xml version="1.0"?> <smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd" xmlns:rules="http://www.milyn.org/xsd/smooks/rules-1.0.xsd"> <rules:ruleBases> <rules:ruleBase name="order" src="/org/milyn/validation/order/rules/order-rules.csv" provider="org.milyn.rules.mvel.MVELProvider"/> </rules:ruleBases> </smooks-resource-list>
- You must store your MVEL rules in CSV files. The easiest way to edit these files is through a spreadsheet application such as LibreOffice Calc or Gnumeric. Each rule record contains a rule name and an MVEL expression.
- If you wish to create comment and header rows, prefix the first field with a hash (#) character.
5.9. The Smooks Validation Cartridge
The Smooks validation cartridge works with the rules cartridge to provide rules-based fragment validation.
This allows you to perform detailed validation on message fragments. As with everything in Smooks, the validation functionality is available across all supported data formats. This means you can perform strong validation on not just XML data, but also on EDI, JSON, CSV and so on.
Validation configurations are defined by the http://www.milyn.org/xsd/smooks/validation-1.0.xsd configuration namespace.
Smooks supports a number of different rule provider types and these can all be used by the Validation Cartridge. Each of these provide a different level of validation but they are all configured in exactly the same way. The Smooks Validation Cartridge sees a rule provider as an abstract resource that it can aim at message fragments in order to validate them.
5.10. Configuring Validation Rules
To configure a validation rule you need to specify the following:
- executeOn: this is the fragment on which the rule is to be executed.
- excecuteOnNS: this is the fragment namespace to which the
executeOn
belongs. - name: this is the name of the rule to be applied. This is a composite rule name that refers to a ruleBase and ruleName combination in a dot delimited format (in other words
ruleBaseName.ruleName
). - onFail: this determines the severity of a failed match.
Here is a sample validation rule configuration:
<validation:rule executeOn="order/header/email" name="regexAddressing.email" onFail="ERROR" />
5.11. Configuring Validation Exceptions
- You can set a maximum number of validation failures per Smooks filter operation. (An exception will be thrown if this maximum is exceeded.) Validations configured with OnFail.FATAL will always throw an exception and stop processing.To configure the maximum validation failures, add this code to your Smooks configuration:
<params> <param name="validation.maxFails">5</param> </params>
- The onFail attribute in the validation configuration specifies what action is to be taken. This determines how validation failures are to be reported. To utilize it, modify the following options to suit your needs:
- OK: Use this to save the validation as "okay". By calling
ValidationResults.getOks
all validation warnings will be returned. This option is useful for content-based routing. - WARN: Use this to save the validation as a warning. By calling
ValidationResults.getWarnings
all validation warnings will be returned. - ERROR: Use this to save the validation as an error. By calling
ValidationResults.getErrors
you will return all validation errors. - FATAL: Use this to throw a ValidationException as soon as a validation failure occurs. If you call
ValidationResults.getFatal
you will see the fatal validation failure.
5.12. Rule Bases
- Use a composite rule name in the following format for a rule base:
<ruleProviderName>.<ruleName>
- ruleProviderName identifies the rule provider and maps to the
name
attribute in theruleBase
element. - ruleName identifies a specific rule the rule provider knows about. This could be a rule defined in the
src
file.
5.13. Smooks.filterSource
The Smooks.filterSource captures the validation results. When the filterSource method returns, the ValidationResult instance will contain all validation data.
This code shows how to make Smooks perform message fragment validation:
ValidationResult validationResult = new ValidationResult(); smooks.filterSource(new StreamSource(messageInStream), new StreamResult(messageOutStream), validationResult); List<OnFailResult> errors = validationResult.getErrors(); List<OnFailResult> warnings = validationResult.getWarnings();
As you can see, individual warning and error validation results are made available from the
ValidationResult
object in the form of OnFailResult
instances, each of which provide details about an individual failure.
The Validation Cartridge also allows you to specify localized messages relating to validation failures. You can define these messages in standard Java ResourceBundle files (which use the
.properties
format).
Note
The base name of the validation message bundle is derived from the rule source ("src") by dropping the rule source file extension and adding an extra folder named
i18n
. For example, if you have an MVEL ruleBase source of /org/milyn/validation/order/rules/order-rules.csv
, the corresponding validation message bundle base name will be /org/milyn/validation/order/rules/i18n/order-rules
.
5.14. The Validation Cartridge and Messages
The validation cartridge lets you apply FreeMarker templates to the localized messages, allowing messages to contain contextual data from the bean context, as well as data about the actual rule failure. You must prefix FreeMarker-based messages with
ftl:
and reference the contextual data using standard FreeMarker notation. The beans from the bean context can be referenced directly, while you can refer to the RuleEvalResult and rule failure path through the ruleResult
and path
beans.
Here is an example that uses RegexProvider rules which shows how Smooks can be used to perform validation of message fragment data:
customerId=ftl:Invalid customer number '${ruleResult.text}' at '${path}'. Customer number must match pattern '${ruleResult.pattern}'.
5.15. Types of Validation
Smooks performs two types of validation using two different kinds of validation rules:
- message field value/format validation using regular expressions defined in a
.properties
file RuleBase. This, for example, can be to validate a field as being a valid e-mail address. - business rules validation using MVEL expressions defined in a
.csv
file RuleBase. This can, for example, be validating that the total price of an order item on an order (price * quantity) does not breach some predefined business rule.
5.16. Running Validation Rules
- To run validaton rules, go to the example root folder and execute:
mvn clean install
mvn exec:java
5.17. RuleBase Example
In this example, there is an XML message containing a collection of order items. (This functionality works similarly for all other data formats supported by Smooks.):
<Order> <header> <orderId>A188127</orderId> <username>user1</username> <name> <firstname>Bob</firstname> <lastname>Bobington</lastname> </name> <email>bb@awesomemail.com</email> <state>Queensland</state> </header> <order-item> <quantity>1</quantity> <productId>364b</productId> <title>A Great Movie</title> <price>29.95</price> </order-item> <order-item> <quantity>2</quantity> <productId>299</productId> <title>A Terrible Movie</title> <price>29.95</price> </order-item> </Order>
5.18. Message Data Validation
- When processing an order message, you should perform a number of validations. First, check that the supplied username follows a format of an upper case character, followed by five digits (for example,
S12345
orG54321
). To perform this validation, you should use regular expression. - Next, check that the supplied e-mail address is in a valid format. Use a regular expression to check it.
- Confirm that each order item's productId field follows a format of exactly three digits (such as
123
). Use a regular expression to do this. - Finally, you need to confirm that the total for each order item does not exceed 50.00 (price * quantity is not greater than 50.00). Perform this validation using an MVEL expression.
5.19. Using an MVEL Expression
- To use an MVEL expression on a rule set, divide the Regex rules and place them in two separate
.properties
files. - Drop these rules into the example
rules
directory. - Put the MVEL expression in a
.csv
file, also in therules
directory.The customer-related Regex rules that go in thecustomer.properties
file look like this:# Customer data rules... customerId=[A-Z][0-9]{5} # Email address... email=^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$
- Insert the product-related Regex rule in the
product.properties
file:# Product data rules... productId=[0-9]{3}
- Insert the MVEL expression for performing the order item total check into the
order-rules.csv
file.Note
The easiest way to edit a .csv file is through using a spreadsheet application like LibeOffice Calc or Gnumeric. - Create resource bundle
.properties
files for each of the rule source files.Note
The names of these files are derived from the names of their corresponding rule files.The message bundle for the rules defined inrules/customer.properties
is located in therules/i18n/customer.properties
file:customerId=ftl:Invalid customer number '${ruleResult.text}' at '${path}'. <!-- Customer number must begin with an uppercase character, followed by 5 digits. --> email=ftl:Invalid email address '${ruleResult.text}' at '${path}'. <!-- Email addresses match pattern '${ruleResult.pattern}'. -->
The message bundle for the rule defined inrules/product.properties
is located in therules/i18n/product.properties
file:# Product data rule messages... productId=ftl:Invalid product ID '${ruleResult.text}' at '${path}'. <!-- Product ID must match pattern '${ruleResult.pattern}'. -->
The message bundle for the rule defined inrules/order-rules.csv
is located in therules/i18n/order-rules.properties
file:# <!-- Order item rule messages. The "orderDetails" and "orderItem" beans are populated by Smooks bindings - see config in following section. --> order_item_total=ftl:Order ${orderDetails.orderId} <!-- contains an order item for product ${orderItem.productId} with a quantity of ${orderItem.quantity} and a unit price of ${orderItem.price}. This exceeds the permitted per order item total. -->
- Apply this validation to the rules:
<?xml version="1.0"?> <smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd" xmlns:rules="http://www.milyn.org/xsd/smooks/rules-1.0.xsd" xmlns:validation="http://www.milyn.org/xsd/smooks/validation-1.0.xsd" xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.2.xsd"> <params> <!-- Generate a ValidationException if we get more than 5 validation failures... --> <param name="validation.maxFails">5</param> </params> <!-- Define the ruleBases that are used by the validation rules... --> <rules:ruleBases> <!-- Field value rules using regex... --> <rules:ruleBase name="customer" src="rules/customer.properties" provider="org.milyn.rules.regex.RegexProvider"/> <rules:ruleBase name="product" src="rules/product.properties" provider="org.milyn.rules.regex.RegexProvider"/> <!-- Order business rules using MVEL expressions... --> <rules:ruleBase name="order" src="rules/order-rules.csv" provider="org.milyn.rules.mvel.MVELProvider"/> </rules:ruleBases> <!-- Capture some data into the bean context - required by the business rule validations... --> <jb:bean beanId="orderDetails" class="java.util.HashMap" createOnElement="header"> <jb:value data="header/*"/> </jb:bean> <jb:bean beanId="orderItem" class="java.util.HashMap" createOnElement="order-item"> <jb:value data="order-item/*"/> </jb:bean> <!-- Target validation rules... --> <validation:rule executeOn="header/username" name="customer.customerId" onFail="ERROR"/> <validation:rule executeOn="email" name="customer.email" onFail="WARN"/> <validation:rule executeOn="order-item/productId" name="product.productId" onFail="ERROR"/> <validation:rule executeOn="order-item" name="order.order_item_total" onFail="ERROR"/> </smooks-resource-list>
- Execute from the example's
Main
class using this code:protected static ValidationResult runSmooks(final String messageIn) throws IOException, SAXException, SmooksException { // Instantiate Smooks with the config... final Smooks smooks = new Smooks("smooks-config.xml"); try { // Create an exec context - no profiles.... final ExecutionContext executionContext = smooks.createExecutionContext(); final ValidationResult validationResult = new ValidationResult(); // Configure the execution context to generate a report... executionContext.setEventListener(new HtmlReportGenerator("target/report/report.html")); // Filter the input message... smooks.filterSource(executionContext, new StringSource(messageIn), validationResult); return validationResult; } finally { smooks.close(); } }