Tuesday Jun 07, 2011

Creating Custom XPath Functions using Oracle JDeveloper

JDeveloper supports the addition of custom XPath functions to extend the functions available in both
the XSLT Mapper and the Expression Builder.

Developers can implement new functions by writing Java methods which can then be easily integrated
into the JDeveloper graphical tooling.

Once the functions are registered with JDeveloper they appear
in the Component Palette in the User Defined section.

For example, this image shows the two custom functions in this posting displayed in the JDeveloper Component Palette.

component_palette


The SOA Suite Developer's Guide does a nice job of explaining the configuration steps
for user-defined functions at the following link

http://download.oracle.com/docs/cd/E21764_01/integration.1111/e10224/bp_appx_functs.htm#BEIIAHJH

This posting provides examples of two different custom functions: using SOA Suite 11.1.1.5
  • A simple function for use with the XSLT Mapper
  • A complex example that can be used with the expression builder in a BPEL, Mediator or Worklist component.


A Simple XSLT Mapper Function

The smiple function is named "checkDept", and its intended use is to provide a default department number of 999
if an employee is not assigned to a department

Here is the code for the function

package com.example.reusable.asset;
/**
 * XSLT Specific Mapper function.
* Function is registered in a file with the naming convention ext-mapper-xpath-functions-config.xml
 */
public class CheckDept {
   public static String checkDept(String input){
          if (input == null || input.length() == 0)
                  return "999";
          else return input;
    }
}

Mapper functions are defined in a configuration file named ext-mapper-xpath-functions-config.xml
The following lines define the function for use with JDeveloper and the SOA runtime Server


<soa-xpath-functions version="11.1.1" 
    xmlns=http://xmlns.oracle.com/soa/config/xpath
    xmlns:geo="http://www.oracle.com/XSL/Transform/java/com.example.reusable.asset.CheckDept">
   <function name="geo:checkDept">
  <className>com.example.reusable.asset.CheckDept</className>
 <return type="string"/>
<params>
<param name="value" type="string"/>
</params>
<desc/>
<detail>
<![CDATA[This function returns a department of 999 if a department is not set.]]>
</detail>
</function>
</soa-xpath-functions>

When complete the function can be used as a custom function displayed in the XLST editor.

Mapper

A Complex Expression Builder Function Example

The sortEmployees is a more complex example that sorts a list of employees in ascending order based on their department code.

package com.example.reusable.asset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import javax.xml.xpath.XPathFunctionException;
import oracle.fabric.common.xml.xpath.IXPathContext;
import oracle.fabric.common.xml.xpath.IXPathFunction;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/** Mapper function that can be used in Express Builder of any SOA Suite component.
  *
 **/

    public class SortEmployees implements IXPathFunction {

         public Object call(IXPathContext context,  List args) {

                org.w3c.dom.Node employees = (Node) args.get(0);

                try {
                           sortNodes(employees, false);
                }
                catch (XPathFunctionException ex)
                {
                    System.err.println("Error: SortEmployees XPath function exception, skipped processing: " + ex.getMessage());
                    employees = (Node) args.get(0);
                }   

                return employees;
            }
        
         /**
          * Sorts the nodes in a node list
          * 
          * @param nodeList - list whose nodes will be sorted
          * @param descending - true for sorting in descending order
          */
         public static Node sortNodes(Node inputNode, boolean descending) throws XPathFunctionException {
                   
                   List nodes = new ArrayList();
                   if(inputNode==null) {
                        throw new XPathFunctionException("Input Node is null");
                   }
                   else {    
                       if(inputNode == null)
                           throw new XPathFunctionException("Expected input Node of type http://www.example.org : Employees " +
                                                             "received null document");
                       if(inputNode.getNamespaceURI() == null ||
                           !(inputNode.getNamespaceURI().equals("http://www.example.org")) ||
                           !inputNode.getLocalName().equals("Employees") )
                           throw new XPathFunctionException("Expected input Node of type http://www.example.org : Employees " +
                                                            "received " + inputNode.getNamespaceURI() + " : " + inputNode.getLocalName());
      
                       NodeList employees = inputNode.getChildNodes();                  
                       if (employees.getLength() > 0) {
                          for (int i = 0; i < employees.getLength(); i++) {
                               Node tNode = employees.item(i);
                               nodes.add(tNode);
                          }
                          
                          Comparator comp = new EmpDeptComparator();
                          
                          if (descending)
                          {
                           //if descending is true, get the reverse ordered comparator
                               Collections.sort(nodes, Collections.reverseOrder(comp));
                          } else {
                               Collections.sort(nodes, comp);
                          }

                         for (Iterator iter = nodes.iterator(); iter.hasNext();) {
                               Node element = (Node) iter.next();
                               inputNode.appendChild(element);
                         }
                       }
                   }
               return inputNode;
              
           }
           }

           class EmpDeptComparator implements Comparator {

           public int compare(Object arg0, Object arg1) {
                   return ((Node) arg0).getFirstChild().getTextContent().compareTo(
                                   ((Node) arg1).getLastChild().getTextContent());
           }
        
       }



 

The data types manipulated by the function are defined by the following employee schema.

<?xml version="1.0" encoding="windows-1252" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
            xmlns="http://www.example.org"
            targetNamespace="http://www.example.org"
            elementFormDefault="qualified">
   <xs:element name="Employees" type="EmpCollection"/>
   <xs:complexType name="EmpCollection">
      <xs:sequence>
         <xs:element name="Emp" type="Emp" minOccurs="0" maxOccurs="unbounded"/>
      </xs:sequence>
   </xs:complexType>
   <xs:complexType name="Emp">
      <xs:sequence>
         <xs:element name="number" type="xs:int"/>
         <xs:element name="firstName" nillable="true">
            <xs:simpleType>
               <xs:restriction base="xs:string">
                  <xs:maxLength value="10"/>
               </xs:restriction>
            </xs:simpleType>
         </xs:element>
          <xs:element name="lastName" nillable="true">
            <xs:simpleType>
               <xs:restriction base="xs:string">
                  <xs:maxLength value="10"/>
               </xs:restriction>
            </xs:simpleType>
         </xs:element>
         <xs:element name="job" nillable="true">
            <xs:simpleType>
               <xs:restriction base="xs:string">
                  <xs:maxLength value="12"/>
               </xs:restriction>
            </xs:simpleType>
         </xs:element>
         <xs:element name="hiredate" type="xs:date" nillable="true"/>
         <xs:element name="dept" type="xs:int" nillable="true"/>
      </xs:sequence>
   </xs:complexType>
</xs:schema>

Expression Builder functions are placed in different files to restrict usage.

  • Place definitions in the ext-bpel-xpath-functions-config.xml for use in BPEL Components.
  • Place definitions in the ext-mediator-xpath-functions-config.xml for use in Mediator Components.
  • Place definitions in the ext-wf-xpath-functions-config.xml  for use in Human Workflow Components.
  • Place definitions in the ext-soa-xpath-functions-config.xml for use in any of the above Components.

The following lines define the sortEmployee function for use with JDeveloper and the SOA runtime Server .
They are placed in a file named ext-soa-xpath-functions-config.xml file.


<?xml version="1.0" encoding="UTF-8"?>
<soa-xpath-functions version="11.1.1" 
   xmlns=http://xmlns.oracle.com/soa/config/xpath
   xmlns:mf="http://www.oracle.com/XSL/Transform/java/com.example.reusable.asset">
    <function name="mf:sortEmployees">
    <className>com.example.reusable.asset.SortEmployees</className>
    <return type="node-set"/>
    <params>
      <param name="empList" type="node-set"/>
    </params>
    <desc/>
    <detail>
       <![CDATA[This function sorts a list of employees.]]>
    </detail>
   </function>
</soa-xpath-functions>

When complete the function can be used as a custom function in the expression builder.
The following image shows the sortEmployees function in a BPEL assign node.

Mapper

Customer functions and their configuration files must be packaged in a Jar file and registered with both JDeveloper and the SOA Runtime Server.

The complete list of development and runtime configuration steps is too long for a blog post.

For a complete list of steps to create these functions see the Full Post

Thursday Jun 02, 2011

Creating JAR deployments using JDeveloper

A common J2EE deployment package is a JAR file containing configuration files in a META-INF directory.
Creating this packaging during the build & deploy process is easy using a feature in JDeveloper called Deployment Profiles.

This article details the steps in JDeveloper to assemble a jar file containing project class files along with xml configuration files placed in the META-INF folder.

Read the full article here


Tuesday Feb 22, 2011

SOA Suite 11g Native Format Builder Complex Format Example

This rather long posting details the steps required to process a grouping of fixed length records using Format Builder.   If it’s 10 pm and you’re feeling beat you might want to leave this until tomorrow. 
But if it’s 10 pm and you need to get a Format Builder Complex template done, read on…


The goal is to process individual orders from a file using the 11g File Adapter and Format Builder


Sample Data
===========
001Square Widget            0245.98
102Triagular Widget         1120.00
403Circular Widget           0099.45
ORD8898302/01/2011
301Hexagon Widget         1150.98
ORD6735502/01/2011


The records are fixed length records representing a number of logical Order records.
Each order record consists of a number of item records starting with a 3 digit number,
followed by a single Summary Record which starts with the constant ORD.


How can this file be processed so that the first poll returns the first order?

001Square Widget            0245.98
102Triagular Widget         1120.00
403Circular Widget           0099.45
ORD8898302/01/2011

And the second poll returns the second order?

301Hexagon Widget           1150.98
ORD6735502/01/2011

Note: if you need more than one order per poll, that’s also possible,
see the “Multiple Messages” field in the “File Adapter Step 6 of 9” snapshot further down.

To follow along with this example you will need

- Studio Edition Version 11.1.1.4.0    with the  
- SOA Extension for JDeveloper 11.1.1.4.0 installed

Both can be downloaded from here:  http://www.oracle.com/technetwork/middleware/soasuite/downloads/index.html

You will not need a running WebLogic Server domain to complete the steps and Format Builder tests in this article.

To see the full steps refer to the Full Post


Thursday Jul 01, 2010

Testing Oracle 11g Business Rules directly from Java


In my earlier post Testing Business Rules with JDeveloper Rule Designer I presented an example of testing rules using the JDeveloper Rules Designer.

While that’s nice, testing rules from a simple Java Class is even better.

This article presents an example of how the 11g rule engine can be called locally from a standalone Java application.

With this technique, the rules and decision functions authored using JDeveloper are loaded from the file system and called directly from Java.

Tests can be run from a command line or from within JDeveloper,  No Application Server is required. 

Let me repeat that part,  because I really like it,
Rule Tests can be run without a running WebLogic Server

This provides a simple and efficient way to write rules, rapidly test them and analyze the results without the need to deploy the composite. 

The approach uses two Java Classes,

The RuleTester.java Class is a generic testing harness that will call the Rules Engine, pass input data and return results.
The RuleTester.java class does not need to be modified by the developer.

The second Java Class must be implemented by the developer.
This class creates the required test data and invokes RuleTester.
An example of both classes are provided below.

The following example illustrates the following points;

  • Loading a rule dictionary from the file system.
  • Creating the necessary Java and XML test facts.
  • Calling the decision service and running the rules
  • Output of the decision service results

RuleTester.java

package rulesproject;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import oracle.rules.sdk2.decisionpoint.*;
import oracle.rules.sdk2.dictionary.RuleDictionary;
import oracle.rules.sdk2.exception.SDKWarning;
import oracle.rules.rl.RuleSession;
import oracle.rules.rl.extensions.trace.RuleEngineState;
import oracle.rules.rl.extensions.trace.TraceAnalysis;
import oracle.rules.rl.trace.DecisionTrace;
import oracle.rules.rl.trace.FactTrace;
import oracle.rules.rl.trace.RuleTrace;
import oracle.rules.rl.trace.TraceEntry;

/**
 * A Java Class that demonstrates how to load an Oracle 11g Rules Dictionary
 * from a file system location and execute a decision Function.
 * The class is intended to be used only as Rule development / prototyping tool.
 * The rules and decision service to be tested are expected to located in a .rules
 * file on the file system created using the JDeveloper 11g Rules Editor.
 * The rules should be contained in a JDeveloper SOA Rules Composite Application,
 * for example a "Composite Business Rule" application.
 * Rules are tested in a slave JVM automatically started by JDeveloper.
 * A running WebLogic Application Server is not required.
 */
public class RuleTester {
 public RuleTester() {
 super();
 }
 
 /* Initialize loads the Rule dictionary from the specified file system location and creates a Rule Decision Point.
 * @param dictionaryLocation The full path to the .rules file.
 * @param decisionFunctionName The name of the Decision Function that will be called.
 * The function is defined using the JDevleloper rule editor 
 */
 private static DecisionPointInstance initialize(String dictionaryLocation, String decisionFunctionName) throws Exception {
 
 DecisionPointInstance pointInstance = null;
 if(dictionaryLocation == null || decisionFunctionName == null)
 throw new Exception("RuleProcessor must have all input properties to successfully initialize.");
 
 // Load Decision Point using Dictionary on File System
 DecisionPoint decisionPoint = new DecisionPointBuilder()
 .with(decisionFunctionName)
 .with(loadRuleDictionary(dictionaryLocation)) 
 .build();
 pointInstance = decisionPoint.getInstance(); 
 RuleSession session = pointInstance.ruleSession();
 session.callFunctionWithArgument("setDecisionTraceLevel", RuleSession.DECISION_TRACE_DEVELOPMENT);
 
 System.out.println("RuleTester Initialized");
 return pointInstance;
 }
 
 
 
 /**
 * Loads the rule dictionary from the specified dictionaryPath
 * @param dictionaryLocation The full path to the .rules file.
 * @return A rule dictionary object
 * 
 */
 private static RuleDictionary loadRuleDictionary(String dictionaryLocation) throws Exception{
 RuleDictionary dict = null;
 Reader reader = null;
 Writer writer = null;
 
 try {
 reader = new FileReader(new File(dictionaryLocation));
 dict = RuleDictionary.readDictionary(reader, new DecisionPointDictionaryFinder(null));
 List<SDKWarning> warnings = new ArrayList<SDKWarning>();
 
 dict.update(warnings);
 if (warnings.size() > 0 ) { 
 System.err.println("Validation warnings: " + warnings);
 }
 
 } finally {
 if (reader != null) { try { reader.close(); } 
 catch (IOException ioe) {ioe.printStackTrace();}}
 if (writer != null) { try { writer.close(); } 
 catch (IOException ioe) {ioe.printStackTrace();}}
 }
 
 return dict;
 }
 
 /**
 * Produces a trace of the Rules Session to the Standard Output device
 * @param pointInstance The decision point instance for the test session.
 
 */
 private static void outputTrace(DecisionPointInstance pointInstance) throws Exception
 {
 DecisionTrace trace = pointInstance.decisionTrace();
 TraceAnalysis tAnalysis = new TraceAnalysis(trace);
 
 List<TraceEntry> traceList = trace.getTraceEntries();
 System.out.println("Trace list size is " + traceList.size() );
 
 if(traceList.size() > 0) {
 // Iterator<TraceEntry> it = traceList.iterator();
 for(int t=0; t<traceList.size(); t++)
 {
 TraceEntry entry = traceList.get(t);
 
 RuleEngineState engineState = tAnalysis.getRuleEngineState(entry);
 
 // Fact Trace List for Trace Entry
 List<FactTrace> factTraceList = engineState.getFacts();
 System.out.println("Trace entry " + (t+1) + " Engine has " + factTraceList.size() + " Facts");
 for(int i=0; i< factTraceList.size(); i++)
 { 
 FactTrace fTrace = (FactTrace) factTraceList.get(i);
 System.out.println("Fact " + (i+1) + ": " + fTrace.getFactType());
 }
 
 // Output Fired Rules for Trace Entry
 List<RuleTrace> firedRulesList = engineState.getFiredRules();
 for(int i=0; i< firedRulesList.size(); i++)
 { 
 RuleTrace frTrace = (RuleTrace) firedRulesList.get(i);
 System.out.println("Fired rule " + frTrace.getRuleName());
 }
 }
 }
 }
 
 /**
 * Execute the Rule Decision Function and return the results.
 * @param dictionaryLocation The fully qualified path of the .rules file containing the rules.
 * Created by JDeveloper rule editor.
 * @param decisionFunctionName The name of the Decision Function in the Rules Dictionary that will be called.
 * @param inputs An ArrayList containing the parameters required by the decision service in order or appearance.
 * @param trace A boolean flag indicating whether a trace for the session should be output to the standard out device.
 * @return A modified list of objects returned by the specified rule decision service
 */
 public static List runRules(String dicrtionaryLocation, String decisionFunctionName, ArrayList inputs, boolean trace) throws Exception { 
 
 List<Object> ruleResult = null; 

 DecisionPointInstance pInstance = initialize(dicrtionaryLocation, decisionFunctionName);
 
 if(pInstance == null)
 throw new Exception("RuleTester not intialized.");
 
 System.out.println("Running Rules");
 
 if (inputs != null)
 { 
 pInstance.setInputs(inputs);
 
 // invoke the decision point with our inputs
 ruleResult = pInstance.invoke();
 
 if (ruleResult == null || ruleResult.isEmpty()){
 System.out.println("RuleTester: No results returned by rules"); 
 }
 else System.out.println("RuleTester: " + ruleResult.size() + " result(s) returned.");
 
 if(trace)
 outputTrace(pInstance);
 }
 return ruleResult;
 }
 
}

Download RuleTester.java

MyShippingCostTester.java

package rulesproject;

import example.rules.OrderT;
import example.rules.ObjectFactory;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public class MyShippingCostTester {
 public MyShippingCostTester() {
 super();
 }
 
 /**
 * Creates a list of Shipping Codes Java Facts for input to the rule engine. 
 * @return A populated List of ShipCode objects
 */
 public static List<ShipCode> createShipCodesTestData()
 { 
 ShipCode row1 = new ShipCode();
 row1.setProductId("001");
 row1.setShipCode("C");
 
 ShipCode row2 = new ShipCode();
 row2.setProductId("002");
 row2.setShipCode("A");
 
 List<ShipCode> codes = new ArrayList<ShipCode>();
 codes.add(row1);
 codes.add(row2);
 
 return codes;
 }
 
 /**
 * Creates a sample order for input to the rule engine. Order is an XML type Fact.
 * @return A populated sample OrderT object
 * @throws JAXBException If unmarshalling is unsuccessful
 */
 public static OrderT createOrderTestData() throws JAXBException {
 
 String sampleOrder =
 "<Order xmlns=\"http://www.rules.example\">\n" + 
 " <orderId></orderId>\n" + 
 " <items>\n" + 
 " <item>\n" + 
 " <productId>001</productId>\n" + 
 " <quantity>3</quantity>\n" + 
 " <unitPrice>5.99</unitPrice>\n" + 
 " <extraCharge>false</extraCharge>\n" + 
 " </item>\n" + 
 " <item>\n" + 
 " <productId>123</productId>\n" + 
 " <quantity>1</quantity>\n" + 
 " <unitPrice>34.99</unitPrice>\n" + 
 " <extraCharge>false</extraCharge>\n" + 
 " </item>\n" + 
 " </items>\n" + 
 "</Order>";
 
 OrderT order = null;
 
 // XML Facts are represented by JAXB types. 
 // Use the Generated types to parse an xml test message
 JAXBContext jaxbContext2 = JAXBContext.newInstance("example.rules");
 Unmarshaller unMarsh = jaxbContext2.createUnmarshaller();
 ByteArrayInputStream is = new ByteArrayInputStream(sampleOrder.getBytes());
 Object obj = unMarsh.unmarshal(is);
 JAXBElement jobj = (JAXBElement) obj;
 order = (OrderT) jobj.getValue();
 
 // Write input Order to stdout for later comparison to Order returned by rule engine 
 System.out.println("Input Order Is:");
 Marshaller marshaller2 = jaxbContext2.createMarshaller();
 marshaller2.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
 marshaller2.marshal( obj, System.out );
 
 return order; 
 } 
 
 public static void main(String args[]) throws Exception {

 final String dictionaryLocation = "C:\\projects\\BobsRulesApp\\oracle\\rules\\rulesproject\\OracleRules1.rules";
 final String decisionServiceName = "DetermineShippingCosts";
 
 // Create an array of inputs that match the Rules Decision Function parameters
 ArrayList inputs = new ArrayList();
 inputs.add(createOrderTestData()); // OrderT
 inputs.add(createShipCodesTestData()); // Ship Code List
 
 // Execute the Rules passing in the Test Order and Shipping Codes
 List resultList = RuleTester.runRules(dictionaryLocation, decisionServiceName, inputs, true); // trace on
 
 // Output the results
 if(resultList != null)
 {
 // Only a single Order object returned from rules decision function
 OrderT result = (OrderT) resultList.get(0); 
 
 // Output Modified Order returned from Rules Engine to stdout 
 JAXBContext jaxbContext = JAXBContext.newInstance(OrderT.class);
 Marshaller marshaller = jaxbContext.createMarshaller();
 marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
 ObjectFactory objF = new ObjectFactory();
 marshaller.marshal( objF.createOrder(result), System.out );
 }
 }
 
 

}


Download MyShippingCostTester.java


Running a test
The rules are tested within JDeveloper by right clicking on the MyShippingCostTester.java file and choosing Run.

JDeveloper launches a JVM using the application class path which includes the required rule engine jars.

When run within JDeveloper, the test output is displayed in the Log window as shown below.

In the modified order that is returned, rules have updated the extraCharge element of the first item to indicate additional shipping charges will apply for this item.


A Note about Java and XML Rule Types

If the rules to be tested are based on Java facts then the Java Classes that were provided to the rule engine during fact creation will be on the classpath at runtime.
If the rules to be tested are based on XML, then JDeveloper has generated JAXB content model classes for the input types sent to the rules engine.
The java types for XML facts are generated by JDeveloper during rule creation in the rule editor.
The JAXB types and stored in the  .rulesdesigner folder under the project, which is not visible from within jdeveloper.
These are the types that must be used to create input test data for xml facts.

If XML Facts are being tested from within JDeveloper,
The .rulesdesign/Jaxb_classes folder must be added to project classpath so a run of MyShippingCostTester.java and RuleTester.java can find the types at run time.

Download the complete JDeveloper Project

This provided zip contains a JDeveloper 11.1.1.5 project.

Localizing the Project

The MyShippingCostTester.java file contains a fully qualified path to the .rules file.
The path must be updated in the source to point to the location of the .rules file on the local file system.
To run a test, right click on the MyShippingCostTester.java file and choose Run
The JDeveloper Log window should display the results shown above in the screen snapshot.

The complete example can be downloaded in a zip file from GitHub here

Tuesday Jun 22, 2010

Testing Oracle Business Rules using Rules Designer

The JDeveloper 11G Rules editor allows developers to test business rules directly from within the Rules Designer.

The "User's Guide for Oracle Business Rules" does a nice job of introducing testing fundamentals such as creating the test function and passing a simple fact.

This posting presents a more complex example illustrating;

- How to create test data for input data structures marked as a list or a tree.
- How to create test data for both Java and XML facts.

Our example scenario involves checking line items in an order to determine if additional shipping charges apply.


An Order is represented by an XML document that contains a list of order Items.
An Order and its line items are represented by XML facts using the following XML Schema

Order.xsd

<?xml version= '1.0' encoding= 'UTF-8' ?>
<xs:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.rules.example"
            targetNamespace="http://www.rules.example"  elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="Order" type="OrderT"/>
    <xs:element name="item" type="ItemT"/>
    <xs:complexType name="OrderT">
        <xs:sequence>
            <xs:element name="orderId" type="xs:string"/>
            <xs:element name="items" type="OrderItemCollection"/>
        </xs:sequence>
    </xs:complexType>
     <xs:complexType name="OrderItemCollection">
        <xs:sequence>
            <xs:element name="item" type="ItemT" minOccurs="0" maxOccurs="unbounded"/>
        </xs:sequence>
    </xs:complexType>
     <xs:complexType name="ItemT">
        <xs:sequence>
            <xs:element name="productId" type="xs:string"/>
            <xs:element name="quantity" type="xs:integer"/>
            <xs:element name="unitPrice" type="xs:decimal"/>
            <xs:element name="extraCharge" type="xs:boolean"/>
        </xs:sequence>
    </xs:complexType>
</xs:schema>

To determine Shipping charges we consult a list of Java Facts.

The ShipCode class contains the shipping cost category for a given product.
The ShipCodeList contains a list of all possible ShipCodes.

ShipCode.java

package rulesproject;

public class ShipCode {

public ShipCode() {

super();

}

private String productId;

private String shipCode;

public void setProductId(String productId) {

this.productId = productId;

}

public String getProductId() {

return productId;

}

public void setShipCode(String shipCode) {

this.shipCode = shipCode;

}

public String getShipCode() {

return shipCode;

}

}

ShipCodeList.java

package rulesproject;

import java.util.ArrayList;

import java.util.List;

public class ShipCodeList {

public ShipCodeList() {

super();

shipList = new ArrayList<shipcode>();

}

private List<shipcode> shipList;

public void setShipList(List<shipcode> shipList) {

this.shipList = shipList;

}

public List<shipcode> getShipList() {

if(shipList == null) shipList = new ArrayList<shipcode>();

return shipList;

For our simple example we use a single rule that checks if one of the Items asserted in the order matches a ShipCode for the same product.  If we find a match, we set the extraCharge variable to true for the corresponding order item


The ShippingRules are exposed using a Decision Function.
Our “DetermineShippingCosts” Decision Function takes two inputs.

The first input is an OrderT fact based on our XML Facts. This parameter is marked as a tree structure.
This ensure that the entire XML data structure is asserted into the engine.

The second input is List of ShipCode Java based facts.   We indicate a parameter of  type ShipCode, but since the List checkbox is selected we will provide an input object of type ShipCodeList.

The output for our decision function is a modified order with  line items that require additional shipping charges marked accordingly.

To test this decision function we will create three different functions.

Our first function is the top level test function “testShipRateRules”,  We will execute this function later to perform the test.  By standard the top level function must be defined to return a boolean.  In our case the boolean returned is not relevant. When we run the test later our results will be displayed in a popup dialog.

Within the body of the function we call our Decision Function named DetermineShippingCosts. The decision function we defined takes two parameters.  We create two other functions, createOrderTestData and createShipRateData to create the test data for these parameters.

The next two screen snapshots show the creatOrderTestData() and createShipRateData() functions.

In the body of both these functions we use assign and modify commands to create our test objects. 
We then create an instance of our list object and add the test objects to the list.

The key point to understand is that both XML and Java fact lists are represented under the covers by the java.util.List interface.   The Rules Designer normally assists the user with the creation of Rule statements, but to add the objects to a list you will need to author the line without assistance.  Choose “expression” from the drop down list presented when entering the beginning of each source line.  Invoke the underlying methods to get the list and to add() items to the list.

Finally we are ready to execute our test.
We run the test by selecting the testShipRateRules function and clicking the test link in the upper right corner.   Note that this link will not be enabled if there are any rule validation errors.

When we run the test, our results are output to a dialog window.

We requested this output by including the RL.watch.all()  statement.  

Our test data is designed to find one match for item 001 and no match for item 123.

In the bottom 4 lines of the output we see that the rule has fired and our order line for product 001 has a been modified to set the value of the extraCharge property to true.

The complete example can be downloaded in a zip file from github here

 

Friday May 21, 2010

[Read More]
About

Picture of Bob

I am an Oracle Architect specializing in Service Oriented Architecture and Business Process Management.


Any code presented is for educational purposes only, no warranty or support is implied.

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today