Advanced — New in 1.2.0

XML Rule Definitions

rulii-spring 1.2.0 introduces an XML namespace that lets you declare rules, rulesets, and validation rules entirely in XML files. This is useful when business logic needs to live outside of compiled code — in configuration files that non-developers can edit and deploy without recompiling.

Namespace Declaration

Add the rulii namespace to your Spring XML context file:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:r="http://www.rulii.org/schema/rulii"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           https://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.rulii.org/schema/rulii
           classpath:org/rulii/spring/xml/rulii-spring.xsd">

    <!-- rules go here -->

</beans>

Setting the Default Language

All expression elements — given, then, otherwise, pre-condition, initializer, stop-condition, finalizer — are evaluated by a script engine. The default language is "el" (SpEL) when none is configured. Use <r:scripting> to change the file-wide default:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:r="http://www.rulii.org/schema/rulii"
       ...>

    <!-- Use SpEL (the default) -- expressions reference bindings via #ctx -->
    <r:scripting defaultLanguage="el"/>

    <r:rule name="PremiumOrderRule">
        <given>#ctx.orderTotal >= 500</given>
        <then>#ctx.discountTier = 'PREMIUM'</then>
    </r:rule>

</beans>

Place <r:scripting> once near the top of the file, before any <r:rule> or <r:ruleset> elements — the setting applies to every expression element that follows it. Any individual element can still override the language with a language attribute:

<!-- File default is SpEL, but this one condition uses GraalJS -->
<r:scripting defaultLanguage="el"/>

<r:rule name="RegexRule">
    <given language="js">/^[A-Z]{3}-\d{4}$/.test(ctx.productCode)</given>
    <then>#ctx.validated = true</then>
</r:rule>

Declaring a Rule

Use <r:rule> to define a rule with a condition (pre-condition, given), actions (then, otherwise), all expressed as SpEL:

<r:rule name="AgeVerificationRule">
    <given>#ctx.age >= 18</given>
    <then>#ctx.status = 'approved'</then>
    <otherwise>#ctx.status = 'denied'</otherwise>
</r:rule>

Declaring a RuleSet

<r:ruleset> supports typed parameters, lifecycle hooks, an inline rule collection, and both built-in and custom validation rules:

<r:ruleset name="RegistrationValidation">

    <r:param name="name" type="java.lang.String"/>
    <r:param name="email" type="java.lang.String"/>
    <r:param name="age" type="java.lang.Integer">
        <r:default-value>0</r:default-value>
    </r:param>

    <r:rules>
        <r:notNull   binding="name"  errorCode="NAME_REQUIRED"/>
        <r:notBlank  binding="name"  errorCode="NAME_BLANK"/>
        <r:size      binding="name"  min="2" max="50"/>
        <r:email     binding="email" errorCode="EMAIL_INVALID"/>
        <r:min       binding="age"   min="18"  errorCode="UNDERAGE"/>
    </r:rules>

</r:ruleset>

Lifecycle Hooks

RuleSets support four lifecycle hooks, each taking a SpEL expression:

<r:ruleset name="PricingRules">
    <pre-condition>#ctx.enabled == true</pre-condition>
    <initializer>#ctx.discount = 0</initializer>
    <stop-condition>#ctx.discount >= #ctx.maxDiscount</stop-condition>
    <finalizer>#ctx.totalPrice = #ctx.basePrice - #ctx.discount</finalizer>

    <r:rules>
        <r:rule name="LoyaltyDiscount">
            <given>#ctx.loyaltyYears >= 5</given>
            <then>#ctx.discount = #ctx.discount + 10</then>
        </r:rule>
    </r:rules>
</r:ruleset>

Referencing Existing Beans or Classes

You can include existing Spring beans or annotated rule classes inside a ruleset's rule collection:

<r:rules>
    <!-- Reference an existing Spring bean -->
    <r:bean-ref name="myEligibilityRule"/>

    <!-- Reference an annotated @Rule class -->
    <r:class-ref class="com.example.rules.AgeVerificationRule"/>
</r:rules>

Predefined Validation Rule Elements

All 38 built-in validators are available as first-class XML elements. Every element accepts binding (required), errorCode, severity, and errorMessage. Error messages support Spring property placeholder substitution:

<r:notNull   binding="name"  errorMessage="${validation.name.required}"/>
<r:notBlank  binding="name"/>
<r:size      binding="name"  min="2" max="100"/>
<r:email     binding="email"/>
<r:pattern   binding="phone"  regexp="^\+?[0-9]{7,15}$"/>
<r:min       binding="age"   min="18"/>
<r:max       binding="quantity" max="999"/>
<r:future    binding="appointmentDate"/>
<r:positive  binding="price"  severity="FATAL"/>
<r:in        binding="status" values="PENDING,ACTIVE,CLOSED"/>
<r:startsWith binding="code"  values="PRD-,SKU-"/>

Custom Scripted Validation Rules

When the predefined validators don't cover a business case, <r:validationRule> lets you write the condition as a script expression. The rule registers a RuleViolation when the <given> condition returns false:

<r:validationRule name="ValidShippingDate"
                  errorCode="SHIPPING_DATE_INVALID"
                  severity="ERROR"
                  errorMessage="Shipping date must be today or in the future">
    <given>#ctx.shippingDate != null and !#ctx.shippingDate.isBefore(T(java.time.LocalDate).now())</given>
</r:validationRule>

Supported attributes:

Attribute Required Description
name Yes Unique rule identifier, also used as the Spring bean name
errorCode Yes Code placed in the RuleViolation when the condition fails
severity No ERROR (default), FATAL, WARNING, INFO
errorMessage No Message template shown in the violation (supports ${...} placeholders)
defaultMessage No Fallback message when the template cannot be resolved via MessageResolver
description No Human-readable documentation for the rule

The <given> child element supports the same language attribute as any other expression element, letting you mix languages within the same file. Custom validation rules can also be placed directly inside a <r:ruleset>:

<r:ruleset name="OrderValidation" validating="true">

    <r:param name="productCode" type="java.lang.String"/>
    <r:param name="quantity"    type="java.lang.Integer"/>
    <r:param name="shipDate"    type="java.time.LocalDate"/>

    <r:rules>
        <!-- Predefined rules -->
        <r:notBlank binding="productCode" errorCode="ORD-001"/>
        <r:min     binding="quantity"    errorCode="ORD-002" min="1"/>

        <!-- Custom scripted validation rule -->
        <r:validationRule name="FutureShipDate"
                          errorCode="ORD-003"
                          errorMessage="Shipping date must not be in the past">
            <given>#ctx.shipDate != null and !#ctx.shipDate.isBefore(T(java.time.LocalDate).now())</given>
        </r:validationRule>
    </r:rules>

</r:ruleset>

Loading XML Files via @RuleScan

Use the xmlLocations attribute on @RuleScan to tell rulii-spring where to find your XML rule files. All *.xml files found inside each folder are loaded automatically. See Rule Scanning & Discovery for the full details.

@SpringBootApplication
@RuleScan(
    scanBasePackages = "com.example.rules",
    xmlLocations = {
        "classpath:rules/pricing/",
        "classpath:rules/validation/"
    }
)
public class MyApp { }

Registration order

Class-based rules from scanBasePackages are always registered first. XML rules are loaded afterwards, in the order their folders are listed in xmlLocations.