Initializing Flux in Java Code to Support Runtime Configuration Properties

Introduction

Often times when our customers design workflows, the workflows act as templates and read in different values based on the location of the workflow. For example, let’s say I’ve created a workflow to automate file transfers for a customer, such as “ACME Bank”. The same workflow could likely be used to service all of my customers but with variables whose values are different based on which customer the workflow template is servicing. So, if I run the workflow template for ACME Bank, it may pull files from acmebank.com but if I run the workflow template for Wile Bank, it would put files from wilebank.com. We’ll look at how to setup a Flux environment to support this sort of flexible configuration to reduce overhead in maintaining workflows for many customers. You can easily reduce 200+ workflows down to one workflow using this technique as I’ve witnessed which drastically simplifies your Flux setup.

Initializing Flux

First, let’s create a Java class that will be responsible for initializing the Flux environment.

FluxController.java

import flux.Configuration;
import flux.Engine;
import flux.Factory;
import flux.runtimeconfiguration.RuntimeConfigurationNode;

public class FluxController {
  private static Factory factory = Factory.makeInstance();
  private static Engine engine;

  private String enginePropertiesFilename = "engine.properties";
  private String runtimePropertiesFilename = "runtime.properties";

  public FluxController() throws Exception {
    initializeEngine();
  }

  public Engine initializeEngine() throws Exception {
    System.out.println("Flux version:  " + flux.Factory.getVersion());
    System.out.println("Copyright 2000-2011 Flux Corporation. All rights reserved.\n");

    System.out.println("Creating Flux engine using properties file: " + enginePropertiesFilename + "...");
    Configuration engineConfiguration = factory.makeConfigurationFromProperties(enginePropertiesFilename);
    System.out.println("Flux engine created!");

    engineConfiguration.setRuntimeConfiguration(loadRuntimeConfiguration());
    engine = factory.makeEngine(engineConfiguration);
    engine.start();

    return engine;
  }

  private RuntimeConfigurationNode loadRuntimeConfiguration() throws Exception {
    return new RuntimeConfigurationLoader(runtimePropertiesFilename).getRuntimeConfigurationRootNode();
  }

  public static Engine getEngine() {
    return engine;
  }

  public static void disposeEngine() throws Exception {
    if (engine != null) {
      System.out.println("Disposing Flux engine...");
      engine.dispose();
      System.out.println("Flux engine is disposed!");
    }
  }

  public static void main(String[] args) throws Exception {
    new FluxController();
  }
}

Loading Runtime Configuration Properties

The FluxController class above relies on RuntimeConfigurationLoader to read in the custom runtime configuration properties, or variables, that we’ll use in our workflow templates. Here’s what it looks like.

RuntimeConfigurationLoader

import flux.Factory;
import flux.runtimeconfiguration.RuntimeConfigurationNode;
import flux.xml.XmlFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;

public class RuntimeConfigurationLoader {
  private RuntimeConfigurationNode runtimeConfigurationRootNode = null;

  public RuntimeConfigurationLoader(String filename) throws Exception {
    Properties runtimeProperties = new Properties();
    runtimeProperties.load(new FileReader(filename));

    runtimeConfigurationRootNode = Factory.makeInstance().makeRuntimeConfigurationFactory().makeRuntimeConfiguration();

    Enumeration names = runtimeProperties.propertyNames();
    while (names.hasMoreElements()) {
      String branch = (String) names.nextElement();

      StringTokenizer st = new StringTokenizer(branch, "/");

      int count = st.countTokens();

      RuntimeConfigurationNode currentNode = runtimeConfigurationRootNode;
      while (st.hasMoreElements()) {
        String name = (String) st.nextElement();
        if (count > 1) {
          RuntimeConfigurationNode node = currentNode.getChild(name);
          if (node == null) {
            currentNode = currentNode.makeChild(name);
          } else {
            currentNode = node;
          }
        } else {
          Object obj = null;
          String value = runtimeProperties.getProperty(branch, "");
          if (name.equalsIgnoreCase(RuntimeConfigurationNode.DEFAULT_FLOW_CHART_ERROR_HANDLER)) {
            obj = loadFlowChart(value);
            //} else if (name.equalsIgnoreCase(RuntimeConfigurationNode.CONCURRENCY_THROTTLE)) {
            // Handle standard Flux runtime configuration properties here
          } else {
            obj = value;
          }

          if (obj != null) {
            currentNode.put(name, obj);
          } else {
            System.out.println("Failed to load runtime configuration property: " + name);
          }
        }
        count--;
      }
    }
  }

  public RuntimeConfigurationNode getRuntimeConfigurationRootNode() {
    return runtimeConfigurationRootNode;
  }

  private Object loadFlowChart(String urlPath) throws Exception {
    List flowCharts;
    InputStream fileStream = new FileInputStream(new File(urlPath));
    // Or load resource from class path
    // InputStream fileStream = ClassLoader.getSystemResourceAsStream(resourceName);

    try {
      flowCharts = XmlFactory.makeInstance().makeXmlEngineHelper().makeFlowChartsFromXml(fileStream, false);
    } finally {
      fileStream.close();
    }

    if (flowCharts.size() > 0) {
      return flowCharts.get(0);
    } // if

    return null;
  }
}

Runtime Configuration Properties

Workflows can have variables defined that are only accessible within the workflow itself (flow chart variables). We can also define runtime configuration properties that all workflows can access or isolate the runtime configuration properties so they can be easily accessed within the context of a workflow. In the example mentioned in the introduction, we may want to define some custom properties like: /ACME Bank/username=acmeuser and /Wile Bank/username=wileuser. When we run the same workflow template for these different customers, the correct value will be used. Let’s see what the configuration file format would look like to achieve something like this.

runtime.properties

/businessUnit1/customConfigProperty=testValue1
/businessUnit2/customConfigProperty=testValue2

Test it!

No Java code would be complete without a test that we can automate! Let’s make sure we can initialize Flux, access our custom configuration properties, and shutdown Flux.

import flux.Engine;
import flux.runtimeconfiguration.RuntimeConfigurationNode;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

public class RuntimeConfigurationLoaderTest {
  @Test
  public void testLoad() throws Exception {
    new FluxController();
    Engine engine = FluxController.getEngine();

    assertTrue(engine.isRunning());
    RuntimeConfigurationNode businessUnitConfigNode1 = engine.getRuntimeConfiguration().getChild("businessUnit1");
    assertNotNull(businessUnitConfigNode1);

    Object customConfigProperty1 = businessUnitConfigNode1.getProperty("customConfigProperty");
    assertNotNull(customConfigProperty1);
    assertEquals("testValue1", customConfigProperty1.toString());

    RuntimeConfigurationNode businessUnitConfigNode2 = engine.getRuntimeConfiguration().getChild("businessUnit2");
    assertNotNull(businessUnitConfigNode2);

    Object customConfigProperty2 = businessUnitConfigNode2.getProperty("customConfigProperty");
    assertNotNull(customConfigProperty2);
    assertEquals("testValue2", customConfigProperty2.toString());

    FluxController.disposeEngine();

    assertTrue(engine.isDisposed());
  }
}

Conclusion

Now we can simply edit one of our workflows and change static values to: ${runtime customConfigProperty}. If the workflow runs under “/businessUnit1″, the value “testValue1″ would be used but when the same workflow runs under “/businessUnit2″, the value “testValue2″ would be used. Pretty handy, eh?

About these ads

About fluxeric

I enjoy designing easy to use software that simplifies difficult tasks and spending time with my three kids and wife.

Posted on September 26, 2011, in Uncategorized. Bookmark the permalink. 2 Comments.

  1. Great post.

    Could we continue to use tanuki to expose the flux engine as a windows service? I assume we need only tweak the unsecured-flux-engine-wrapper.conf. Is the example valid for a secure flux engine as well (with a few minor tweaks)

  2. Absolutely! To use the Tanuki wrapper, you’ll just want to replace all instances of flux.Main in the wrapper.conf with the Java class that will be starting your engine. These lines usually look something like:

    wrapper.app.parameter.1=flux.Main

    To use a secured engine rather than unsecured, you’ll want to make sure that you log in to the engine before creating / starting it in the code, like:

    LocalSecurity localSecurity = factory.makeLocalSecurity();
    localSecurity.login(engine, “admin”, “admin”);
    SecurityAdministrator securityAdministrator = engine.getSecurityAdministrator();

    And you’ll also want to enable remote access to the engine by calling:

    RemoteSecurity remoteSecurity = factory.makeRemoteSecurity(engineConfiguration, engine);

    You’ll also want to make sure you log out of the engine after starting it / setting up the runtime configuration:

    localSecurity.logout(engine);

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: