Share This Post

Using Custom Utility Methods in Migration Workbench

Introduction

A core component of the Migration Workbench product (aka, Workbench) is its embedded rules engine, which provides a language for expressing transformations on migration objects. To achieve the transformations often required in migration efforts, a user of Workbench will author migration rules that are intepreted and executed by the embedded rules engine. These rules are authored in an expressive rule language which users can use to read and write properties of source and target objects involved in the migration. The Workbench also permits users to introduce custom utility methods written in Java that can be called from the context of rules. Custom utility methods are helpful for several reasons, which include:

  1. Common transformation code can be encapsulated in utility methods and used across rules files without having to duplicate transformation logic. For example, a common transformation rule may be to convert a comma-delimited first name (e.g, Smith, John) to its typical colloquial version (e.g., John Smith) — this would be perfect behavior to encapsulate in a Workbench custom utility method.
  2. In some cases the MWB rules language is not expressive enough to achieve all of a migration’s transformation requirements. Custom utility methods can be used in these special cases.

Prerequisites

This document assumes that the reader has the MWB product, including the IDE (Integrated Development Environment) plugins, installed. It is also assumed that the reader can create a valid MWB migration project and perform a batch execution using that project.

A Simple Example

To demonstrate how to introduce and employ a utility method in a migration project, we will write an extremely simple utility method. The method will print a Hello message to standard out, which will appear in the Workbench console during execution. The Hello message will be followed by a string representation of a target document that it receives as a parameter:

  1. In the Workbench IDE, make sure that you are in the Java perspective (See Referenced Images 1.)
  2. Create a new source folder. Give it any name you like. For example, “src/custom” (See Referenced Images 2.)
  3. Create a new Java package to contain your class. You can give it any name you like but for this example we will use “utility.sample.hello” (See Referenced Images 3.)
  4. Create a new Java class in this package called “HelloUtil” (See Referenced Images 4.)
  5. Copy and paste the following code as the implementation for this class:
                        
    package utility.sample.hello;
    
    import com.ArgonDigitalgroup.mwb.object.IMObject;
    
    public final class HelloUtil {
    
    	public static void printHello(IMObject object) {
    		System.out.println("Hello " + object);
    	}
    }
    
    

    We now have an implementation for our utility method. Note that the printHello() method is a public static method. This allows us to access the utility method in a static manner from the rules file. We’ll demonstrate the invocation of this method later. Note that the utility method accepts an IMObject instance as a parameter. The source and target attributes on our document classes implement this interface; we’ll see how to invoke the method with a valid argument later in this example.
    But first let’s make sure that this class is compiled into the proper location in the MAR/project so that the rules engine can access its implementation at runtime.

  6. Ensure that the target output folder for compiled classes is the META-INF/classes directory of the MAR/project. Right click the project folder and select Properties from the pop-up menu. Select “Java Build Path” from the navigation tree and then select the “Source” tab. You should see the following dialog; ensure that the Default output folder is the project name followed by “/META-INF/classes”:

    mwb-util-output-folder
    Figure 1:
    The default output folder for a Workbench project.

    At this point our utility method should be available when writing our rules. Open any rules file in the project and add the following import statement to the project:

                        
    import utility.sample.hello.HelloUtil
    
    

    Note that for any custom utility class that we wish to use in a rules file, a corresponding import statement needs to appear at the top of that file. Now we can write a rule in the rules file that invokes the utility method:

                        
    rule "Print hello message for every document"
    when
    	document : Document( )
    then
    	HelloUtil.printHello( document.target );
    end
    
    

    This rule matches every Document available in the rules engine during execution, then invokes our custom printHello() method to print a message for the target side of each Document. Recall that the printHello() method takes an IMObject instance as a parameter. For Document instances in the rules engine the source and target attributes are valid values to pass in as IMObjects.
    When you are finished you should have a rules file that looks roughly like:

                        
    package com.ArgonDigitalgroup.mwb.external.vocabulary.acme
    import com.ArgonDigitalgroup.mwb.external.vocabulary.trial.mfacade.Document
    import com.ArgonDigitalgroup.mwb.external.vocabulary.trial.mfacade.Document.Source
    import com.ArgonDigitalgroup.mwb.external.vocabulary.trial.mfacade.Document.Target
    import com.ArgonDigitalgroup.mwb.external.vocabulary.trial.mfacade.Document.Custom
    import utility.sample.hello.HelloUtil
    rule "Print hello message for every document"
    when
    	document : Document()
    then
    	HelloUtil.printHello(document.target);
    end
    
    

    We are now finished and can execute a batch that references this rules file. The console should display our debug message for each migration Document. The output we will see should look something like:

                        
    Hello MObject(091c963880020a0c,[FIFTH_DOCUMENT.txt])
    Hello MObject(091c963880020a0f,[FIRST_DOCUMENT.txt])
    Hello MObject(091c963880020a12,[FOURTH_DOCUMENT.txt])
    Hello MObject(091c963880020a06,[FIFTH_DOCUMENT.txt])
    ...
    
    

Using Third-Party Libraries In Rules

The previous example demonstrated a simple but perhaps not very useful example of adding custom behavior to rules transformations via custom utility methods. In more realistic use cases we may need to employ more complex Java functionality than just message printing. It is likely that a reasonably complex transformation would need to invoke a third-party or otherwise external Java library, for example. In this section we will show how to invoke Java behavior that is contained inside a JAR file from the context of rules:

  1. Add your third-party JAR to the “lib” folder in your MAR/project. For this example, we will use the Apache commons-lang library. After adding the JAR to the lib folder, make sure to add the JAR to your project’s build path. This step will allow us to use auto-completion and rule validation features when using methods in commons-lang:

    mwb-util-add-to-build-path
    Figure 2:
    Adding a third-party library to a Workbench project’s build path.

  2. Now we can use commons-lang utility methods in a rules file. The IDE will even provide us with auto-completion:

    mwb-util-third-party-auto-complete
    Figure 3:
    Example of auto-completion for a utility method in a rules file.

At runtime the MWB loads classes from JARs in the “lib” directory and makes them available to the rules engine so we should see our rules that use 3rd-party libraries executed successfully.

Invoking Third-Party Libraries From Custom Utility Methods

In the previous example we invoked a 3rd-party library’s utility method directly from a rules file. Of course we could have also written a custom utility method that invoked behavior in one or more 3rd-party libraries — in which case we would still need to provide the 3rd-party library JARs in the project’s “lib” directory to ensure its classes were available at runtime.

Performance Concerns

One concern in developing and invoking custom utility methods from rules is the potential negative impact on rules engine performance. A method invoked in the right-hand side of a rule will be potentially invoked several times within a rules engine session. If care is not taken to make custom utility methods fast, the execution time of the MWB rules engine could grow with the introduction of a utility method.

Conclusion

Custom utility methods offer a powerful way to develop and encapsulate behavior that can be used from within Migration Workbench transformation rules. These utility methods can employ third-party libraries to achieve transformation or other logic (logging, for example) not easily achieved in the rules language. The usage of custom utility methods can have a negative impact on rules engine performance, but when care is taken they offer a powerful means of introducing extra behavior in MWB rules.

Referenced Images

  1. Use the Window menu to switch to the Java perspective.

    mwb-util-java-perspective
    Figure 4

  2. While in the Java perspective, right-click the MWB project and choose New -> Source Folder. At the prompt, type the name of the source folder. For example, “src/custom”.

    mwb-util-new-source-folder
    Figure 5

    mwb-util-new-source-folder-modal
    Figure 6

  3. Use Right+Click, New -> Package to create a new package. Then fill in the dialog that will pop-up:

    mwb-util-new-package
    Figure 7

    mwb-util-new-package-modal
    Figure 8

  4. Right+click the Java package and select New -> Class. Fill in the class details and select OK. The new class should appear in the selected Java package.

Using Custom Utility Methods in Migration Workbench

More To Explore

AI to Write Requirements

How We Use AI to Write Requirements

At ArgonDigital, we’ve been writing requirements for 22 years. I’ve watched our teams waste hours translating notes into requirements. Now, we’ve cut the nonsense with AI. Our teams can spend

ArgonDigital | Making Technology a Strategic Advantage