Saturday, 27 November 2010

Madura Rules Part 3

This has been a while coming. Part 1 covered the types of rules available and part 2 was about how to configure Madura Rules using Spring.

In this entry I want to cover some of the less obvious features in Madura Rules: decision tables and constants. These are closely related to choice lists in Madura Objects.

Decision Tables

Decision Tables are descendants of a construct in COBOL. Oh, yes, COBOL really did have some smart stuff in it. Actually these are somewhat simplified from the COBOL decision tables but they do a good job.

Start with an XML file that looks like this:

<DecisionTable name="business-customerType" scope="Customer" message="nz.co.senanque.newrules.decisiontable.business-customerType">
  <ColumnNames>
      <ColumnName autoAssign="true">business</ColumnName>
     <ColumnName>customerType</ColumnName>
  </ColumnNames>
  <Rows>
      <Row>
        <Column>AG</Column><Column>a</Column>
      </Row>
      <Row>
        <Column>AG</Column><Column>f</Column>
      </Row>
      <Row>
        <Column>FISH</Column><Column>b</Column>
      </Row>
      <Row>
        <Column>FINANCE</Column><Column>c</Column>
      </Row>
      <Row>
        <Column>FINANCE</Column><Column>d</Column>
      </Row>
      <Row>
        <Column>FINANCE</Column><Column>e</Column>
      </Row>
      <Row>
        <Column>FINANCE</Column><Column>f</Column>
      </Row>
  </Rows>
</DecisionTable>

As you can see this is mostly rows and columns with values in each cell. COBOL's equivalent allows expressions in each cell.

The decision table has a name, a relevant object (Customer in this case) which is the equivalent of the scope in the other rules. There is also a message identifier which is delivered as an error if an attempt to set an incorrect value is made.
The column names refer to fields in the Customer object.

Below that are rows and columns. Each row specifies a valid combination. So if we set the business to B then the only valid values for business are AG and FISH. If your application examines the metadata for business, say to create a drop down list, then it will give only those values.

If we set the customerType to A then there is only one valid value for business and, because we set the autoAssign attribute in the columnName, then that value will actually be set.
We can, of course, decide to set the business value first and have it decide what options are available for customerType instead. And we can have more than two columns.

Your decision table data comes from the XML file by default, but you can write a factory in Java to deliver the data.

Constants
You can specify soft constants in your rules like this:

rule: Customer "Determine business from customerType"
{
  if (customerType == ${xyz})
  {
    business = IndustryType.AG;
  }
}

Here xyz is a constant, except that we might want to change that constant sometimes so we can specify it this way. Then we write some XML that looks like this:

<Constants>
  <Constant name="xyz">aaaab</Constant>
</Constants>

Like the decision table the value may come from the XML or it may be overridden by a factory you can supply yourself which delivers the value. This is useful for values that cannot be determined before deployment time, and/or for values that are used in multiple places and you want to ensure they are actually all the same value.

Injecting the XML

All of the decision tables and all of the constants can reside in a single XML document. In fact this document can also contain the choice lists used by Madura Objects. But they can also live in separate documents, injected separately into the rules engine. This allows options such as generating the decision tables by some external program etc. All documents are injected as Spring resources into the engine so you have all the deployment options supported by that (classpath, file, URL etc).

There is still more to cover, such as how to add external functions to the rules and how to handle I18n issues. But that's another post.

Sunday, 7 November 2010

How to write a generic java compile target in ant

The problem

We use ant for our builds and it generally works just fine. But we've been rationalising our many build scripts lately, identifying the common stuff and putting it into a common file. Obvious, really, and that is going well. In the process I came across an awkwardness, a kind of mismatch inside ant. We wanted a generic compile target that handles:
  1. running javac to compile the source files
  2. copying the non-java files (xml, xsd, xsl etc) across to the bin directory
We often have multiple source paths and javac lets you specify them like this:

javac srcdir="java/src:java/test:java/other"

So I have three directories with source files. But when I want to copy them I have to say:

<copy todir="${basedir}/bin" flatten="no" >
  <fileset dir="java/src" includes="**/*.properties,**/*.xml"/>>
  <fileset dir="java/test" includes="**/*.properties,**/*.xml"/>
  <fileset dir="java/other" includes="**/*.properties,**/*.xml"/>>
</copy>

Which would be okay but in different projects the list of source directories varies so I can't have a generic target that will compile anything. Well, actually I can...

The Solution

First I need to have ant-contrib in my classpath, then I define the taskdef for it like this:

<taskdef resource="net/sf/antcontrib/antcontrib.properties"/>

Once that is done I can make my generic target ehich looks like this:

<target name="compile-source">
        <delete dir="${basedir}/bin" failonerror="false"/>
        <mkdir dir="${basedir}/bin"/>
        <javac destdir="${basedir}/bin"  srcdir="${srcpath}">
            <classpath>
                <path refid="libs"/>
            </classpath>
        </javac>
        <foreach delimiter=":"
             list="${srcpath}"
             param="d"
             target="copysource"/>
    </target>
   
    <target name="copysource">
        <copy verbose="true" todir="${basedir}/bin" flatten="no" >
            <fileset dir="${d}" includes="**/*.properties,**/*.xml"/>
        </copy>
    </target>

The key to this is the foreach tag in the compile-source target. That loops through the srcpath, splitting out the colon delimited fields and running the copy.

It took me a while to figure out how to use foreach, so hopefully this is a help.