CXF and Digital Signatures

Some web service interactions may require the use of different technologies that are under the umbrella of WS-Security. One of those requirements is driven by the need for specific parts of the message to contain metadata to ensure that the intended sender actually created that part of the message. This can resolved by applying a Digital Signature. I can create a unique Digital Signature (like our own personal signature) and then I can sign parts of the message (as if you were initialing different parts of a contract). Thomas Erl’s site (What is SOA) defines it as this:

XML-Digital Signatures establishes a standardized format for representing digital signature data. Digital signatures establish credibility within a message, as they assure the recipient that the message was in fact transmitted by the expected partner service. It also provides a means of communicating that the message contents were not altered in transit, as well as support for standard non-repudiation. As with the XML-Encryption standard, XML-Digital Signature also supports binary and textual data.

So I am going to be utilizing functionality provided by CXF to implement Digital Signatures. The details of the properties and features of Digital Signatures sometimes seem hard to find, but luckily CXF (like most Web Service frameworks such as Axis or Spring Web Services Framework) utilizes WSS4J to do much of the actual signature part of the message (as well as Username Token, SAML, etc.). In order to get Digital Signatures to work in CXF, I am going to take advantage of CXF’s Interceptors, as well as Spring’s util namespace for representing properties, and finally some modifications to our startup scripts for WebLogic Server because of issues with jar files that are used.

In this example, I want to go one step further and also provide a Timestamp that is signed, because my service provider only allows Digital Signature requests within a certain timeframe to prevent highjacking of the message (for instance if the message is signed at 11:00 AM and the request isn’t received at the service provider until 11:05 AM, the service provider can read the Timestamp and see that it has been signed…to verify that it wasn’t altered and reject the message because it is outside of the required one minute timeframe for a request). Once I have the Signature piece complete, adding a Timestamp requirement is absolutely simple.

So just as I mentioned in my other post about CXF and 2-way SSL, I will need to create a Java Keystore that is separate from our other keystore since Digital Signatures will allow us to provide the alias for our cert, while 2-way SSL configuration does not. So I will create our keystore and provide a password for it. Unlike the 2-way SSL that requires a Certificate Authority keystore, which I created a custom one and put it as a startup parameter for WebLogic Server, I will not be doing that for this keystore because I have multiple keystores for different security requirements and services.

Much of the source example here is leveraged and taken from David Valeri – CXF with WS* Example.

So first thing I need to do is define a bean for SAAJ and then register that with our JaxWsProxyFactoryBean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<bean id="saajOutInterceptor" class="org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor"/>

<bean id="secureWebServiceProxyFactory" class="org.apache.cxf.jaxws.JaxWsProxyFactoryBean" lazy-init="true">
    <property name="serviceClass" value="com.foo.SecureWebService"/>
    <property name="address" value="${secure.service.url}"/>
    <property name="inInterceptors" ref="logInbound"/>
    <property name="outInterceptors">
        <list>
            <ref bean="saajOutInterceptor"/>
            <ref bean="secureWebServiceWss4jOutInterceptor"/>
            <ref bean="logOutbound"/>
        </list>
    </property>
</bean>

The next part is deciding which actions I am going to do and what type of information I need for those actions. So I will be doing “Timestamp Signature” to signify that I want to Timestamp our outbound request as well as apply a Digital Signature. The Digital Signature needs information such as the certificate alias, how to attach the key (which can be the most complicated piece for me in order to get the Signature correctly received by the provider), which parts of the message I want to sign, I can select the properties file which will provide the necessary information for WSS4J, the signature digest algorithm, and a password callback handler instance. The reason the example I referenced above is so beneficial, is because I really don’t want an external properties file. So as the example shows, I can utilize Spring’s util namespace to represent the properties that are required for WSS4J and I can reference this properties bean via the SignaturePropRefId.

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<bean id="secureWebServiceWss4jOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
  <constructor-arg>
        <map>
           <entry key="action" value="Timestamp Signature"/>
           
           <!-- This is the fallback user for sig/enc if the sig/enc specific
            properties are not set.  We shouldn't need this, but the WSS4JOutInterceptor
            (CXF 2.2.2.2) looks for it and fails if it is not set. -->
           <entry key="user" value="${secureWebService.keystore.alias}"/>
           
           <!-- The certificate alias in the signature crypto config to sign
            the message with.  The password is retrieved from the callback handler. -->
           <entry key="signatureUser" value="${secureWebService.keystore.alias}"/>
           
           <!-- Signature key attachment method.  We want to put the token
            directly in the header and not use a reference. -->
           <entry key="signatureKeyIdentifier" value="DirectReference"/>
           <!--entry key="signatureKeyIdentifier" value="X509KeyIdentifier"/-->
           
           <!-- The parts of the response to sign.  Include: Body,
            token, timestamp, and addressing headers. -->
           <entry key="signatureParts" value="{http://schemas.xmlsoap.org/soap/envelope/}Body;{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp;{http://www.w3.org/2005/08/addressing}MessageID"/>
           
           <!--entry key="signaturePropFile" value="${secureWebService-wss4j.properties}"/-->
           <!-- Defines the property name that contains a Properties object with the desired
             settings in it.  Better than loading a static file from the classpath when using
            Spring. -->
           <entry key="SignaturePropRefId" value="cryptoProperties" />
           
           <!-- The entry that actually contains the Properties object for
            the signature crypto configuration.  See SignaturePropRefId. -->
           <entry key="cryptoProperties" value-ref="secureWebServiceCryptoProperties" />

           <!-- The algorithm used to create the actual signature data. -->
           <!--entry key="signatureAlgorithm" value="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/-->

           <!-- The algorithm used to create digests of message content. -->
           <!-- <entry key="signatureDigestAlgorithm" value="http://www.w3.org/2001/04/xmlenc#sha256"/> -->
           <!--entry key="signatureDigestAlgorithm" value="http://www.w3.org/2000/09/xmldsig#sha1"/-->

           <!-- The reference to the callback handler for retrieving passwords
            for private keys in the signature and encryption crypto configurations. -->
           <entry key="passwordCallbackRef">
                <bean class="com.foo.PasswordCallbackHandler">
                    <property name="password" value="${secureWebService.keystore.password}"/>
                </bean>
            </entry>
        </map>
     </constructor-arg>
  </bean>

<!-- Define a Properties object with the properties required by the
       org.apache.ws.security.components.crypto.Merlin WSS4j Crypto implementation.
       This crypto config is used for signature creation and validation and decryption. -->
  <util:properties id="secureWebServiceCryptoProperties">
    <!-- Defines the implementation class. -->
    <prop key="org.apache.ws.security.crypto.provider">org.apache.ws.security.components.crypto.Merlin</prop>
    <!-- Defines the location, on the classpath, of the keystore file.  Also
         takes URL or file path. Not applicable when using PKCS 11. -->
    <prop key="org.apache.ws.security.crypto.merlin.file">${secureWebService.keystore.file}</prop>
    <!-- The type of the keystore pointed to by org.apache.ws.security.crypto.merlin.file. -->
    <prop key="org.apache.ws.security.crypto.merlin.keystore.type">jks</prop>
    <!-- The crypto provider that can load the keystore. -->
    <!--prop key="org.apache.ws.security.crypto.merlin.keystore.provider"></prop-->
    <!-- The password for the keystore file. -->
    <prop key="org.apache.ws.security.crypto.merlin.keystore.password">${secureWebService.keystore.password}</prop>
    <!-- The alias for the default private key to use.  Not required.
    <prop key="org.apache.ws.security.crypto.merlin.cert.provider"></prop>-->
    <prop key="org.apache.ws.security.crypto.merlin.keystore.alias">${secureWebService.keystore.alias}</prop>
    <!-- If the JVM cacerts file contents should be loaded into the trust chain.
    <prop key="org.apache.ws.security.crypto.merlin.load.cacerts">false</prop> -->
    <!-- If the JVM cacerts file is used, the password for the file.
    <prop key="org.apache.ws.security.crypto.merlin.cacerts.password"></prop> -->
  </util:properties>

So you can see by the signatureParts that I am signing the Body, the Timestamp, and part of the Address. The reason I am signing part of the Address is just like our reasons for the Timestamp, in this case the service provider requires WS-Addressing, and by signing it, I am providing the necessary integrity that the intended client that made the original request is providing the Addressing information without being altered. All of the values in this configuration are provided through a security.properties file that is external to our war (I will talk about this later in utilize WebLogic Optional Packages to create a jar containing our enterprise properties so that the war does not have hard-coded properties per environment).

1
<context:property-placeholder location="classpath*:/enterprise.properties" />

So I wish that this was all that was required, but it is unfortunately not because of the fact that I am running on WebLogic Server. If you have run the latest Hibernate/JPA with WebLogic, you have probably had to make the same types of modification to the startup scripts. Here is some parameters that I had to include, along with adding the saaj jar onto our preclasspath:

1
2
SAAJ_JAVA_OPTIONS="-Djavax.xml.soap.MessageFactory=com.sun.xml.messaging.saaj.soap.ver1_1.SOAPMessageFactory1_1Impl -Djavax.xml.soap.SOAPConnectionFactory=weblogic.wsee.saaj.SOAPConnectionFactoryImpl"
SAAJ_PRECLASSPATH="$/saaj-impl-1.3.2.jar"

The last piece that really caused me grief was around the version of the JDK I was using and whether or not I needed references to Bouncycastle APIs. And as the following post on CXF’s forum suggests, I no longer need this API because of the features in JDK 6 and therefore I had to remove any references/dependencies on Bouncycastle (as it caused our Digital Signatures) to fail.

http://cxf.547215.n5.nabble.com/java-lang-NoClassDefFoundError-org-bouncycastle-asn1-pkcs-PrivateKeyInfo-td4710843.html

And as I mentioned in the CXF and 2-way SSL thread, I required the additional cryptography libraries in order to digitally sign the message: Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files

Resources related to this subject:

Apache CXF – WS-Security Add Interceptors
Adding X.509 security headers to Apache CXF SOAP calls (WS-SecurityPolicy method)
Adding X.509 security headers to Apache CXF SOAP calls
WSS4J Javadoc – Constants
Java Cryptography Architecture Standard Algorithm Name Documentation
Apache CXF Tutorial – WS-Security with Spring
David Valeri – CXF with WS* Example
JBoss – Turn on WS-Security
Securing your Web services with Spring-WS
Sending WS-Security Signature and Encryption Profiles to Axis2/Rampart Web service
An Overview of the WS-Security Framework

Forum posts related to this subject:

http://cxf.547215.n5.nabble.com/CXF-with-Digital-Signatures-td3408690.html
http://cxf.547215.n5.nabble.com/KeyName-within-a-Digital-Signature-Configurable-td4656619.html
http://cxf.547215.n5.nabble.com/java-lang-NoClassDefFoundError-org-bouncycastle-asn1-pkcs-PrivateKeyInfo-td4710843.html
http://cxf.547215.n5.nabble.com/JaxWsProxyFactoryBean-amp-WS-Addressing-in-Configuration-td4683953.html
http://cxf.547215.n5.nabble.com/Send-X509Certificate-with-request-td564722.html

limo service austin tx

Share and Enjoy

CXF and 2-way SSL

When interacting with some clients, there is a need to add an extra level of security by applying 2-way SSL between the client and the service endpoint. This interaction is going to require using two keystores (one being the truststore and one being a basic keystore), along with CXF configuration. If you have never done work with a keystore before, no worries, the commands are pretty simple and there are some easy to use tools. My favorite site for commands is below:

The Most Common Java Keytool Keystore Commands

And if I feel lazy and want to interact with keystores via a GUI instead of the command line, I use Keystore Explorer.

The JDK defaults to using cacerts as the start location to put trust certs. The default password for the cacerts is “changeit” incase you want to practice the commands or utilizing a tool to view default certs. For simplifying upgrades to Application Servers, sometimes it is better to create a new jks that is used as the truststore. Just be aware that there might be default certificate authorities in cacerts that you might need to export/import into your new truststore. Then a parameter is added to the startup scripts of WebLogic Server to inform it that the application is using a different truststore from the default.

-Djavax.net.ssl.trustStore=ssl/CA-Trust.jks -Djavax.net.ssl.trustStorePassword=Password

There is other one important item related to Java Keystores. If you are doing more WS* capabilities or interactions with other clients and therefore need to have multiple keys for doing 2-way SSL and Digital Signatures, etc…I have to refer back to documentation about the fact that you cannot have more than one private key in a keystore:

From IBM Documentation: If you are using the default properties to configure SSL (javax.net.ssl.*), the SSL keystore should contain exactly one private key, because there is no way to specify which key will be used.

This becomes more apparent when you realize that the configuration for http:conduit does not let you supply an alias, even though configurations like Digital Signatures do. So if you have two private keys within the same keystore, you may run into an issue where your Digital Signature works but your 2-way SSL has handshake failures because it is scanning the keystore for the most immediate private key that it uses (which was meant for the Digital Signature). So the solution is just to create a separate keystore for each private key.

Once I have the keystores in place and startup scripts for registering our new truststore, then our next task is to figure out what is the necessary configuration for CXF. The best documentation for how to do security with CXF is actually from a vendor that uses CXF as a core product and publishes some documentation on how to use it…Fusesource. There document Fuse Services Framework – Security Guide is hands down the best resource I have used on the subject.

I need to configure an http:conduit in CXF. The one thing to realize as you read about the http:conduit is that if you use a wildcarding feature for http:conduit, it will apply to every service call out that occurs with that war application. So with regards to the previous link to CXF, the following is the example:

1
2
3
4
5
<http-conf:conduit name="*.http-conduit">
  <!-- you can also using the wild card to specify
       the http-conduit that you want to configure -->
    ...
</http-conf:conduit>

The problem with the “*.http-conduit” is that it is a wildcard for any service. So if you have more than one service callout and only one of those service callouts requires 2-way SSL, unfortunately 2-way SSL will be attempted on all the service callouts. The way around this is to use a different name pattern matching to configure a specific service for 2-way SSL (remember this configuration is an low level transport configuration so it is not contained within a bean id configuration for a JaxwsProxyFactoryBean service invocation). So I use other strategies such as:

1
2
3
4
5
6
7
8
9
10
11
<http-conf:conduit name="http://localhost:8080/.*">
  <!-- you can also using the reg-ex URL matching for
       the http-conduit that you want to configure -->
    ...
</http-conf:conduit>

or

<http-conf:conduit name="{http://widgets/widgetvendor.net}widgetSOAPPort.http-conduit">
    ...
</http-conf:conduit>

The reg-ex for URLs is very easy to use and can provide the necessary simplicity especially if the url differs only by an environmentally named DNS. The next step is to add the keyManagers configuration, the trustManagers configuration, and the cipherSuitesFilter configuration. The keyManagers needs the jks file path, the jks password, and the certificate password. The trustManagers just needs the jks file path and the jks password. The last piece is the cipherSuitesFilter configuration which just lists the types of ciphers that are available for inclusion or exclusion. Based on the type of ciphers that are going to be used (possibly for Digital Signatures), you may need to install Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files.

The configuration will look like the following:

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
<http:conduit name="https://.*\.secure.foo.com/service">
    <http:tlsClientParameters disableCNCheck="true" secureSocketProtocol="SSL">
        <sec:keyManagers keyPassword="${secureWebService.keystore.cert.password}">
            <sec:keyStore type="JKS" password="${secureWebService.keystore.password}" file="${secureWebService.keystore.file}" />
        </sec:keyManagers>
        <sec:trustManagers>
            <sec:keyStore type="JKS" password="${truststore.password}"
                file="${truststore.file}" />
        </sec:trustManagers>
        <sec:cipherSuitesFilter>
            <!--
                these filters ensure that a ciphersuite with export-suitable or
                null encryption is used, but exclude anonymous Diffie-Hellman key
                change as this is vulnerable to man-in-the-middle attacks
        -->
            <sec:include>.*_EXPORT_.*</sec:include>
            <sec:include>.*_EXPORT1024_.*</sec:include>
            <sec:include>.*_WITH_DES_.*</sec:include>
            <sec:include>.*_WITH_NULL_.*</sec:include>
            <sec:include>.*_RSA_.*</sec:include>
            <sec:include>.*_SHA.*</sec:include>
            <sec:include>.*_MD5.*</sec:include>
            <sec:exclude>.*_DH_anon_.*</sec:exclude>
        </sec:cipherSuitesFilter>
    </http:tlsClientParameters>
    <http:client AutoRedirect="true" Connection="Keep-Alive" />
</http:conduit>

Resources related to this subject:

Client HTTP Transport (including SSL support)

Forum posts related to this subject:

What is a Pem file and how does it differ from other OpenSSL Generated Key File Formats?
http://cxf.547215.n5.nabble.com/CXF-Support-2-way-SSL-td3414301.html
http://cxf.547215.n5.nabble.com/CXF-2-4-Migration-removal-of-cxf-xml-2way-SSL-errors-td5551465.html

Share and Enjoy

Custom Maven Plugin for Drools Rules

When I start developing business rules with Drools, I can utilize Guvnor as a repository for storing those assets. This application will allow us to add and create rules or functions within the product, or to upload those to the server. I can then use Guvnor to create packages that I can deploy with our applications that will use these rules. The packages are usually compiled into a pkg file that is then included into a war, for instance, a war that utilizes a Spring context file with a bean definition to register a KnowledgeSession that imports the pkg file. There are multiple ways to configure the KnowledgeSeesion with a pkg file, either by pulling a pkg file from the classpath or by pulling a pkg file from a URL.

One of the complexities that occurs when dealing with rules and the concept of the Guvnor, is treating Guvnor as not just a repository but as the main build system for rules. This hinders the potential for rapid development as it complicates build/run/test by introducing an Application Server which will be used as the main source for compilation of the rules for verification that they were designed correctly. If developers are not familiar with JBoss Application Server or in the case I have been familiar with (don’t have access to the server), when rules fail to compile, they have no way to search the log files for why rules failed to compile. Also, I ran into situations with the product poorly performing because it was installed as a simplistic development server and then doesn’t match the required performance capabilities.

To move past this, I decided to utilize the capabilities of Maven to create a custom plugin that could load rule files and compile those into a pkg file, by doing this locally and not having to rely on Guvnor for doing developmental builds. The plugin would need to scan a maven resources directory, compile a list of all the files, and then process those files through a KnowledgeSession, and then compile them into the binary package. The next step is to utilize a better deployment unit than a pkg. Since I am using Nexus (like Artifactory), I want to be able to deploy the pkg without having to create a customized dependency type. I can do this by jaring the pkg file, since as long as the jar file is on the classpath of the war I am deploying (which uses our rules), the configured KnowledgeSession will be able to resolve the pkg file within the jar. So once our pkg is complete, I will jar the pkg file with the same name as the pkg.

In this case, I am going to write a Maven Mojo. In order to do this, I really need to understand the Maven properties I have available to us. Maven runs on Plexus and as IOC, it has the great ability to inject property values into class level fields that are configured via Java Annotations (those that match Maven Properties or configurations within the pom file). I find some of these properties through the following resources:

Sonatype Maven Reference – Properties
Maven Properties Guide

In order to begin doing the Mojo development and use some of the more advanced capabilities that Plexus gives us, I will need to add to our Maven POM file for the plugin:

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
    <groupId>org.apache.maven</groupId>
    <artifactId>maven-plugin-api</artifactId>
</dependency>
<dependency>
        <groupId>org.apache.maven</groupId>
        <artifactId>maven-project</artifactId>
</dependency>
<dependency>
        <groupId>org.codehaus.plexus</groupId>
        <artifactId>plexus-utils</artifactId>
</dependency>

Sample Maven Properties

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
    /**
     * This will inject the MavenProject object that exists in the current
     * POM in which the plugin is running.  We can access all the information
     * we need from this single object.
     *
     * @parameter default-value="$"
     * */

    private MavenProject mavenProject;

    /**
     * This is where the .pkg files are written.  This is the project outputDirectory
     * and we technically don't need this field since we have the mavenProject field.
     *
     * @parameter expression="${project.build.outputDirectory}"
     */

    private File targetDirectory;

    /**
     * This is the project resource directory (not test).  This way we don't have to
     * hard-code a path in our plugin to access the resources, and we will have the
     * unique path even if a resources/resource configuration is used to modify the
     * default location of resources.  We technically don't need this field since
     * we have the mavenProject field.
     *
     * @parameter expression="${project.resources}"
     * @required
     */

    private List resources;

    /**
     * This gives us the artifactId for the project so that we can use this to name
     * we will give the pkg as well as the jar file.  This can also be retrieved from
     * the mavenProject.
     *
     * @parameter expression="${project.artifactId}"
     * @required
     */

    private String artifactId;

    /**
     * This parameter will automatically map the excludes/exclude listings from the
     * configuration segment of the plugin.
     *
     * @parameter
     */

    private List<String> excludes;

    /**
     * This parameter will automatically map the includes/include listings from the
     * configuration segment of the plugin.
     *
     * @parameter
     */

    private List<String> includes;

I can then access the Resources by calling the mavenProject.getResources(). I also need to get the list of possible includes/excludes, but these must first be converted from a List to a comma separated list of values (in order for us to take advantage of Maven’s path wildcarding). One of the features I also add is to set a default include list incase one is not set through the plugin:

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
private static final String DEFAULT_INCLUDES_TYPES = "**/*.drl,**/*.xls,**/*.rf";

   private String getExcludesList() {
        if (CollectionUtils.isNotEmpty(excludes)) {
            return convertListToCsv(excludes);
        } else {
            return null;
        }
    }

    private String getIncludesList() {
        if (CollectionUtils.isNotEmpty(includes)) {
            return convertListToCsv(includes);

        } else {
            return DEFAULT_INCLUDES_TYPES;
        }
    }

    private String convertListToCsv(List<String> includeList) {
        StringBuilder nameBuilder = new StringBuilder();
        for (String includeItem : includeList) {
            nameBuilder.append(includeItem).append(",");
        }

        nameBuilder.deleteCharAt(nameBuilder.length() - 1);
        return nameBuilder.toString();
    }

I will use the class from the Plexus API.

When then utilize one of the helper methods to build the list of files that I will loop through and add the rule file to the KnowledgeBuilder:

1
List<File> listOfFiles = org.codehaus.plexus.util.FileUtils.getFiles(new File(resource.getDirectory()), getIncludesList(), getExcludesList(), true);

I will then loop get the KnowledgePackages and then stream those packages out to the pkg file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
String targetPath = mavenProject.getBuild().getDirectory() + "/classes/";
Collection<KnowledgePackage> kpkgs = kbuilder.getKnowledgePackages();
for (KnowledgePackage kpkg : kpkgs) {
    Collection<Rule> rules = kpkg.getRules();
    FileOutputStream out = null;

    try {
        File targetFile = new File(targetPath);
        if (!targetFile.exists()) {
            FileUtils.forceMkdir(new File(targetPath));
        }
        File outFilePath = new File(targetPath + mavenProject.getArtifactId() + ".pkg");
        out = new FileOutputStream(outFilePath);
        DroolsStreamUtils.streamOut(out, ((KnowledgePackageImp) kpkg).pkg);
    } catch (IOException ex) {
        throw new RuntimeException(ex);
    } finally {
        IOUtils.closeQuietly(out);
    }

}

Once I complete this Mojo, I can then install it and configure the plugin through a master POM. When can then create Maven Projects and store our rules assets (like xls, drl, or rf) into the src/main/resources directory.

I will configure the master pom of our rules projects to reconfigure the jar plugin so that it will ignore all our rules files and just compile the built pkg file into the jar:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-jar-plugin</artifactId>
                    <configuration>
                        <includes>
                            <include>**/${project.artifactId}.pkg</include>
                        </includes>
                    </configuration>
                    <executions>
                        <execution>
                            <goals>
                                <goal>jar</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

I can then configure our plugin and the types of files I want to exclude (for example) as well as the dependencies I need to include in order to compile the rules.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        <plugins>
            <plugin>
                <groupId>com.foo.tools</groupId>
                <artifactId>foo-rules-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>**/sample.xls</exclude>
                        <exclude>**/*.drl</exclude>
                    </excludes>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>com.foo.services</groupId>
                        <artifactId>core-foo-service</artifactId>
                        <version>1.0.0-SNAPSHOT</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>

When I am all said and done…the completed Mojo looks like this:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;

import org.codehaus.plexus.util.FileUtils;

import org.drools.builder.KnowledgeBuilder;
import org.drools.builder.KnowledgeBuilderConfiguration;
import org.drools.builder.KnowledgeBuilderFactory;
import org.drools.builder.ResourceType;
import org.drools.compiler.DroolsParserException;
import org.drools.core.util.DroolsStreamUtils;
import org.drools.decisiontable.InputType;
import org.drools.decisiontable.SpreadsheetCompiler;
import org.drools.definition.KnowledgePackage;
import org.drools.definition.rule.Rule;
import org.drools.definitions.impl.KnowledgePackageImp;
import org.drools.io.ResourceFactory;

public class DroolsRulesMojo extends AbstractMojo {

    private static final String DEFAULT_INCLUDES = "**/*.drl,**/*.xls,**/*.rf";

    /**
     * Maven Project
     *
     * @parameter default-value="$"
     * */

    private MavenProject mavenProject;

    /**
     * @parameter
     */

    private List<String> excludes;

    /**
     * @parameter
     */

    private List<String> includes;

    /**
     * Setup the options to pass to the code generator.
     *
     * @return the configured options.
     */

    public void execute() throws MojoExecutionException, MojoFailureException {
        try {
            compileRules(mavenProject);
        } catch (Exception e) {
            // Normally don't log when an exception is thrown.
            // This just helps debugging more quickly for rules errors.
            getLog().error(e);
            throw new RuntimeException(e);
        }

    }

    public void compileRules(MavenProject mavenProject) throws DroolsParserException, IOException {
        ClassLoader classLoader = null;
        KnowledgeBuilderConfiguration kbuilderConf = KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration(null,
                classLoader);
        KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(kbuilderConf);

        // This is the target directory for where the rules will be placed during
        // the build.
        String targetPath = mavenProject.getBuild().getDirectory() + "/classes/";

        // Depending on how the project is checked out or the maven pom
        // sometimes the getDirectory() will not have the name of the project/pom
        // but unnamed since it cannot determine the correct name.
        if (StringUtils.contains(targetPath, "Unnamed")) {
            throw new RuntimeException("The project was incorrectly checked out so that maven cannot determine the"
                    + " project name, therefore it creates a directory with Unnamed.  Fix the project or add the name"
                    + " property into the Maven pom.xml");
        }

        // Ask the mavenProject for all it's resource locations.
        List resources = mavenProject.getResources();
        getLog().debug("Target directory to write output file - " + targetPath);

        // Loop through the collection, even though we really could lock this down
        // to just be the /src/main/resources. We could also change the plugin
        // to differentiate between running against test rules and production
        // rules.
        for (Iterator i = resources.iterator(); i.hasNext();) {
            Resource resource = (Resource) i.next();
            getLog().debug("Resource directory to pull rules from - " + resource.getDirectory());

            // The following method call allows us to use the capabilities of
            // wildcarding like we see in most maven plugins. Where we can do
            // things such as **/*.xls. This call will also resolve any conflicting
            // includes and excludes.
            List<File> listOfFiles = org.codehaus.plexus.util.FileUtils.getFiles(new File(resource.getDirectory()),
                    getIncludesList(), getExcludesList(), true);

            if (CollectionUtils.isEmpty(listOfFiles)) {
                getLog().warn("No files were found to compile into package");
            } else {
                for (File file : listOfFiles) {

                    if (file.isFile()) {
                        String fileName = file.getName();

                        // Store the suffix so we can determine the rules type
                        String fileSuffix = StringUtils.substringAfterLast(fileName, ".");

                        if (StringUtils.endsWith(fileSuffix, "drl")) {
                            getLog().info("- Adding DRL: " + fileName + " to package");
                            kbuilder.add(ResourceFactory.newFileResource(file.getAbsolutePath()), ResourceType.DRL);
                        } else if (StringUtils.endsWith(fileSuffix, "xls")) {
                            // Decision tables have an extra step, so we provide additional
                            // output information.
                            SpreadsheetCompiler sc = new SpreadsheetCompiler();
                            String drlstr = sc.compile(ResourceFactory.newFileResource(file.getAbsolutePath())
                                    .getInputStream(), InputType.XLS);
                            getLog().debug("--- XLS compile details = " + drlstr);
                            getLog().info("- Adding Decision Table: " + fileName + " to package");
                            kbuilder.add(ResourceFactory.newFileResource(file.getAbsolutePath()), ResourceType.DTABLE);
                        } else if (StringUtils.endsWith(fileSuffix, "rf")) {
                            getLog().info("- Adding Rule Flow: " + fileName + " to package");
                            kbuilder.add(ResourceFactory.newFileResource(file.getAbsolutePath()), ResourceType.DRF);
                        }

                    }
                }
            }
        }

        // If any errors have occurred in the KnowledgeBuilder, then throw
        // the errors and stop the plugin process.
        if (kbuilder.hasErrors()) {
            getLog().error("Errors " + kbuilder.getErrors().toString());
            throw new RuntimeException(kbuilder.getErrors().toString());
        }

        int pkgCount = 0;
        Collection<KnowledgePackage> kpkgs = kbuilder.getKnowledgePackages();
        for (KnowledgePackage kpkg : kpkgs) {
            Collection<Rule> rules = kpkg.getRules();
            for (Object element : rules) {
                org.drools.definitions.rule.impl.RuleImpl rule = (org.drools.definitions.rule.impl.RuleImpl) element;
               
                //Providing additional debug information for rules within the KnowledgeBuilder
                getLog().debug(
                        "<RuleName> : <Activation-Group (null=non-existent)> : <Agenda-group>:   " + rule.getName()
                                + " : " + rule.getRule().getActivationGroup() + " : "
                                + rule.getRule().getRuleFlowGroup());
            }

            if (++pkgCount > 1) {
                getLog().warn(
                        "Your rules are being built into multiple packages, this is most likely an issue.");
            }

            //Create the pkg on the file system.
            FileOutputStream out = null;

            try {
                File targetFile = new File(targetPath);
                if (!targetFile.exists()) {
                    FileUtils.forceMkdir(new File(targetPath));
                }
                File outFilePath = new File(targetPath + mavenProject.getArtifactId() + ".pkg");
                out = new FileOutputStream(outFilePath);
                DroolsStreamUtils.streamOut(out, ((KnowledgePackageImp) kpkg).pkg);
                long filesize = outFilePath.length();
                long filesizeInKB = filesize / 1024;

                //Log information about the create package.
                getLog().info(
                        "- Package " + mavenProject.getArtifactId() + " built, putting pkg: " + targetPath
                                + mavenProject.getArtifactId() + ".pkg");

                getLog().info("- Size of " + mavenProject.getArtifactId() + ".pkg is: " + filesizeInKB + " KB");
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            } finally {
                IOUtils.closeQuietly(out);
            }

        }
    }

    /**
     * Create the exclude list. If the maven plugin was configured with excludes, then convert the list into a Csv since
     * the Plexus utility needs a comma separated list, else return a null String.
     *
     * @return String containing the list of excludes.
     */

    private String getExcludesList() {
        if (CollectionUtils.isNotEmpty(excludes)) {
            return convertListToCsv(excludes);
        } else {
            return null;
        }
    }

    /**
     * Create the include list. If the maven plugin was configured with includes, then convert the list into a Csv since
     * the Plexus utility needs a comma separated list, else return the list of default rules types that will be
     * compiled.
     *
     * @return String containing the list of excludes.
     */

    private String getIncludesList() {
        if (CollectionUtils.isNotEmpty(includes)) {
            return convertListToCsv(includes);

        } else {
            return DEFAULT_INCLUDES;
        }
    }

    /**
     * Looks through a list of Strings and creates a comma separated String.
     *
     * @param includeList
     *            List of String values for include/exclude.
     * @return String that is a Csv of values.
     */

    private String convertListToCsv(List<String> includeList) {
        StringBuilder nameBuilder = new StringBuilder();
        for (String includeItem : includeList) {
            nameBuilder.append(includeItem).append(",");
        }

        nameBuilder.deleteCharAt(nameBuilder.length() - 1);
        return nameBuilder.toString();
    }
}

viagra uden recept

Share and Enjoy