Business Validation in Oracle SOA Suite 11g using Schematron

4

In a previous post I’ve explained the Schematron standard, how it works and how to use it. In the Oracle SOA Suite you can ‘Validate Semantic’ on the input (request) of a routing rule in a Mediator component by selecting a Schematron file. This is the Schemtron xml file in which you define your validation rules. The SOA Suite takes care of applying them on the request by executing the double transformation.
However, to be able to get the Schematron file working you need to declare the namespaces of the input message and rewrite a report rule to an assert rule. In this post I will show you how to do this with the same business rules (so the same Schematron rules and Schematron file) as the last example in a previous blog explaining Schematron.

The business rules to be implemented on a xml data file containing employees of a company with their managers are:

  • All employees should have less salary than any manager (=employee in department “Managers”).
  • An employee may not be the manager of himself.
  • All normal employees (so not a manager) must have a manager (I’ve added this one).
  • There is only one manager without a manager (only one president).
  • The relation manager and employee is a valid one, so the manager of an employee must exist.

For the input xml with the employees data I’ve created two xml schema’s (xsd’s). The target namespace of the two schema’s differ on purpose, so we have to use two different namespaces for the payload in our Schematron xml rules.
Company.xsd:

<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns:com="http://amis.nl/schematrondemo/common"
            xmlns="http://amis.nl/schematrondemo/company"
            targetNamespace="http://amis.nl/schematrondemo/company"
            elementFormDefault="qualified">
  <xsd:import schemaLocation="Common.xsd" namespace="http://amis.nl/schematrondemo/common"/>
  <xsd:element name="Company">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element name="Department" type="tDepartment" maxOccurs="unbounded"/>
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>
  <xsd:complexType name="tDepartment">
    <xsd:sequence>
      <xsd:element name="Employees" type="com:tEmployees"/>
    </xsd:sequence>
    <xsd:attribute name="name" type="xsd:string" use="required"/>
    <xsd:attribute name="abbr" type="xsd:string" use="required"/>
  </xsd:complexType>
</xsd:schema>

Common.xsd:

<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns="http://amis.nl/schematrondemo/common"
            targetNamespace="http://amis.nl/schematrondemo/common"
            elementFormDefault="qualified">
  <xsd:element name="Employees" type="tEmployees"/>
  <xsd:complexType name="tEmployees">
    <xsd:sequence>
      <xsd:element name="Employee" type="tEmployee" minOccurs="0" maxOccurs="unbounded"/>
    </xsd:sequence>
  </xsd:complexType>
  <xsd:complexType name="tEmployee">
    <xsd:sequence>
      <xsd:element name="Name" type="xsd:string"/>
      <xsd:element name="Salary" type="xsd:decimal"/>
    </xsd:sequence>
    <xsd:attribute name="id" type="xsd:long" use="required"/>
    <xsd:attribute name="manager_id" type="xsd:long" use="optional"/>
  </xsd:complexType>
</xsd:schema>

For this demo I created a new SOA project in JDeveloper with initial an empty composite:

Create SOA project with empty composite

Create SOA project with empty composite

Copy the xml schema’s (xsd files) to the xsd folder (on file system using Explorer):

Copy xsd into project

Copy xsd into project

xsd files in project after refresh

xsd files in project after refresh

Drag a Mediator component to the empty composite and provide a decent name. Select Synchronous Interface, enable the checkbox to expose the Mediator and define as input as well as output the Company node from the Company.xsd in our project.

Create Mediator

Create Mediator

For this demo I have no further business logic, but to implement the Schematron validation. So I just return the input as output by selecting the “Echo” static routing rule:

Select static routing rule Echo in Mediator

Select static routing rule Echo in Mediator

Create a new mapper file with the Create Mapper Button button:

Create New Mapping file

Create New Mapping file

In the mapper just plain copy the input to the output (source to target) with the copy-of construction (right click on target node and choose “Add XSL Node” -> “copy-of”):
Copy input to output in mapping

Copy input to output in mapping

Deploy this demo to your SOA runtime environment and test (e.g. with soapUI) if your echo composite successfully returns your input. Be sure to use valid input data, otherwise the mapping doesn’t work.

Because the Schematron rules only work correct if the input xml data is valid according to the xml schema (xsd’s), it’s recommendable to enable the “Validate Syntax (XSD)” checkbox on the routing rule in the Mediator:

Enable Syntax Check in Mediator

Enable Syntax Check in Mediator

Deploy the composite again and inspect the result, the error messages, when you submit invalid xml data (e.g. remove the obligated id attribute from the Employee node).

Finally we come to the part of implementing the Schematron rules.
The namespaces of our input payload (“http://amis.nl/schematrondemo/company” and “http://amis.nl/schematrondemo/common”) have to be declared. This can not be done in the normal way with a “xmlns” attribute, because the declarations have to ‘survive’ the double xsl transformation. The solution is to use the “ns” element with attributes “uri” and “prefix”:

  <ns uri="http://amis.nl/schematrondemo/company" prefix="cmp" />
  <ns uri="http://amis.nl/schematrondemo/common" prefix="com" />

The Schematron standard defines two types of rules: assert and report. The assert rule returns your error message when the test condition fails (returns false), whereas the report rule returns your error message when the test conditions succeeds (returns true). The Schematron implementation in the SOA Suite only supports the assert rule, so you have to rewrite your report rules.
This is not much of a problem, because the assert rule is just the logical complement (inversion) of the report rule, so the logical “not” function solves this issue. In case of just a logical comparison in the test condition, you also can invert it by replacing a < operator with a >= operator, etc.

Implementing the business rules

  • All employees should have less salary than any manager (=employee in department “Managers”).
  • An employee may not be the manager of himself.
  • All normal employees (so not a manager) must have a manager (I’ve added this one).
  • There is only one manager without a manager (only one president).
  • The relation manager and employee is a valid one, so the manager of an employee must exist.

results into the Schematron xml (see previous post for explanation) file Company.sch (the Oracle SOA Suite uses file extension .sch for Schematron files):

<?xml version="1.0" encoding="UTF-8" ?>
<schema xmlns="http://www.ascc.net/xml/schematron">
  <ns uri="http://amis.nl/schematrondemo/company" prefix="cmp" />
  <ns uri="http://amis.nl/schematrondemo/common" prefix="com" />
  <pattern name="Managers earn more than normal employees">
    <rule context="//cmp:Department[@name!='Managers']/cmp:Employees/com:Employee">
      <assert test="com:Salary &lt; min(//cmp:Department[@name='Managers']/cmp:Employees/com:Employee/com:Salary)">Employee earns too much</assert>
    </rule>
  </pattern>
  <pattern name="Employee may not be the manager of himself">
    <rule context="//com:Employee[@manager_id]">
      <assert test="@manager_id != @id">Employee may not manage himself</assert>
    </rule>
  </pattern>
  <pattern name="All normal employees have a manager">
    <rule context="//cmp:Company/cmp:Department[@name!='Managers']/cmp:Employees/com:Employee">
      <assert test="@manager_id">Employee must have a manager</assert>
    </rule>
  </pattern>
  <pattern name="Only one manager without manager, the president">
    <rule context="//cmp:Company/cmp:Department[@name='Managers']/cmp:Employees">
      <assert test="count(com:Employee[not(@manager_id)]) = 1">There must be exactly one president</assert>
    </rule>
  </pattern>
  <pattern name="Manager doens not exist">
    <rule context="//com:Employee[@manager_id]">
      <assert test="//cmp:Company/cmp:Department[@name='Managers']/cmp:Employees/com:Employee[@id=current()/@manager_id]">No valid manager</assert>
    </rule>
  </pattern>
</schema>

Copy or create this Schematron file somewhere in your project (I’ve added a “sch” folder) and use the Schematron button button to apply your Schematron file with you business rules to the incoming request of the routing rule in the Mediator:

Schematron in Mediator

Schematron in Mediator

Deploy and test your business rules. For example this input:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                  xmlns:cmp="http://amis.nl/schematrondemo/company"
                  xmlns:com="http://amis.nl/schematrondemo/common">
   <soapenv:Header/>
   <soapenv:Body>
      <cmp:Company>
         <cmp:Department name="Ground One" abbr="GR1">
            <cmp:Employees>
               <com:Employee id="10" manager_id="19">
                  <com:Name>J. Jansen</com:Name>
                  <com:Salary>1500</com:Salary>
               </com:Employee>
               <com:Employee id="11" manager_id="15">
                  <com:Name>K. Klaassen</com:Name>
                  <com:Salary>1600</com:Salary>
               </com:Employee>
            </cmp:Employees>
         </cmp:Department>
         <cmp:Department name="Ground Two" abbr="GR2">
            <cmp:Employees>
               <com:Employee id="13" manager_id="20">
                  <com:Name>P. Pietersen</com:Name>
                  <com:Salary>1550</com:Salary>
               </com:Employee>
               <com:Employee id="14" manager_id="20">
                  <com:Name>S. Smit</com:Name>
                  <com:Salary>1600</com:Salary>
               </com:Employee>
            </cmp:Employees>
         </cmp:Department>
         <cmp:Department name="Managers" abbr="MAN">
            <cmp:Employees>
               <com:Employee id="15">
                  <com:Name>M.A. Nager</com:Name>
                  <com:Salary>1700</com:Salary>
               </com:Employee>
               <com:Employee id="20" manager_id="25">
                  <com:Name>L.E.A. Der</com:Name>
                  <com:Salary>1600</com:Salary>
               </com:Employee>
               <com:Employee id="25">
                  <com:Name>P.R.E. Sident</com:Name>
                  <com:Salary>2500</com:Salary>
               </com:Employee>
            </cmp:Employees>
         </cmp:Department>
      </cmp:Company>
   </soapenv:Body>
</soapenv:Envelope>

returns this output:

<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
   <env:Header/>
   <env:Body>
      <env:Fault>
         <faultcode>env:Server</faultcode>
         <faultstring><![CDATA[oracle.tip.mediator.infra.exception.MediatorException: ORAMED-01301:[Payload custom validation]Schematron validation fails with error "<ns1:ValidationErrors xmlns:cmp="http://amis.nl/schematrondemo/company" xmlns:com="http://amis.nl/schematrondemo/common" xmlns:ns1="http://xmlns.oracle.com/pcbpel/validationservice">
   <error>Employee earns too much</error>
   <error>Employee earns too much</error>
   <error>There must be exactly one president</error>
   <error>No valid manager</error>
</ns1:ValidationErrors>
".Possible Fix:Fix the payload.]]></faultstring>
         <faultactor/>
         <detail>
            <exception/>
         </detail>
      </env:Fault>
   </env:Body>
</env:Envelope>

Your Schematron error messages are somewhat hidden inside a CDATA section. When you retrieve the error data from the CDATA section and parse it as xml, you get a decent xml structure which can be used to retrieve and process your error messages:

<ns1:ValidationErrors xmlns:cmp="http://amis.nl/schematrondemo/company" 
                      xmlns:com="http://amis.nl/schematrondemo/common" 
                      xmlns:ns1="http://xmlns.oracle.com/pcbpel/validationservice">
   <error>Employee earns too much</error>
   <error>Employee earns too much</error>
   <error>There must be exactly one president</error>
   <error>No valid manager</error>
</ns1:ValidationErrors>

I didn’t succeed in getting context information in my error messages, for example the id of the Employee for which an error is applicable. If somebody knows or figured out how this can be done, please let me know!

I’ve zipped and uploaded this JDeveloper Schematron demo project, so you can download it.

 

Share.

About Author

Emiel is a senior Java & SOA consultant at AMIS, Nieuwegein (The Netherlands).

4 Comments

  1. Markus Zehnder on

    Hi Emiel

    There is a (hack) way to insert context information in the error messages. I hope the following checksum validation example will remain readable:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE schema [
    <!ENTITY length "string-length(.)">
    <!ENTITY diagName '<value-of select="name()"/>'>
    <!ENTITY diagValue '<value-of select="."/>'>
    ]>
    <sch:schema xmlns="http://www.ascc.net/xml/schematron" xmlns:sch="http://www.ascc.net/xml/schematron" queryBinding="xslt2">
    <sch:pattern name="test">
    <sch:rule context="/UpdateRequest/Checksum">
    <sch:assert test="ends-with((
    (substring(.,&length; - 8,1) * 3) +
    (substring(.,&length; - 7,1) * 2) +
    (substring(.,&length; - 6,1) * 7) +
    (substring(.,&length; - 5,1) * 6) +
    (substring(.,&length; - 4,1) * 5) +
    (substring(.,&length; - 3,1) * 4) +
    (substring(.,&length; - 2,1) * 3) +
    (substring(.,&length; - 1,1) * 2)
    ) mod 11, substring(.,&length;,1))">[E|Checksum|INVALID] Invalid value: '&diagValue;' in field '&diagName;'</sch:assert>
    </sch:rule>
    </sch:pattern>
    </sch:schema>

    I’m using Schematron extensively in a large OSB / SOA project and can’t understand why Oracle is using a 10 year old Schematron implementation. The ISO version is way more powerful…

  2. Hi Emiel,

    The reason why you don’t get additional context information on the error messages is because of schematron. The sch file has to be updated with elements and the xpath function that you need to return the context value. I use oXygen to develop and test my XSD’s and SCH files, which is much easier than JDeveloper.

    Extend the sample schematron something like this:

    assert test=”Xpath expression”
    value-of select=”Xpath expression of values that you want to see”

  3. Hi Emiel,

    The post was very useful. Thanks for this. I have  doubt in this.

    “The namespaces of our input payload (“http://amis.nl/schematrondemo/company” and “http://amis.nl/schematrondemo/common”) have to be declared. This can not be done in the normal way with a “xmlns” attribute, because the declarations have to ’survive’ the double xsl transformation”

    What do u mean by ‘survive double xsl transformation’ here.?

Leave a Reply