Share This:

Writing BAO Adapters - Index

 

Introduction

With apologies for the premature release of this article previously, here is the complete version

 

In part 2 of this series, we took a look at the basic configuration of a development environment. In this post, we'll create a skeleton actor adapter -- this will include the adapter class, its configuration class, and a test class. It's going to be necessarily code-heavy this time around (and in most of the subsequent posts in this series).

 

Our Example Actor Adapter

For this series, we're going to create a fairly simply actor adapter that attempts to identify a remote host as Windows or UNIX based on its open ports. This is not going to be anything near as complex as nmap, we'll just look at a couple of ports and assume that:

  • If the SSH port (default TCP 22) is open, the host is UNIX.
  • If the SMB port (default TCP 445) is open, the host is Windows (provided the SSH port isn't also open).

 

We'll allow the user to specify the ports in the adapter configuration. For the purposes of this article, we're only going to create a basic shell -- the minimum parts needed to have our adapter run on the grid.

 

Configuration Parameters

Each named adapter on the grid has an XML configuration document that allows the user to set global parameters for that adapter. You can also allow the user to over-ride this in individual adapter requests.

 

The only required method for the actor adapter configuration (in addition to the constructor) is validate(). This method is called when BAO wishes to check that the configuration is valid; if you find any configuration parameters that are not suitable, you throw an InvalidConfigurationException.

 

 

The Actor Adapter Life-cycle

It's important to understand the basic life-cycle of an actor adapter before we put together the supporting code.

Adapter States.png

When an actor adapter is loaded, it begins its life in the STOPPED state. The Adapter Manager calls the adapter's initalize() method, after which the adapter is marked as RUNNING. At this point it is expected to respond to calls to its performAction() method.

 

If the Adapter Manager wishes to stop an adapter -- as a result of a user request, or during peer shutdown for example -- it will mark the adapter as STOPPING and call the adapter's shutdown() method. Note that the adapter's shutdown() method is expected to set the final adapter state of STOPPED once it has finished any clean-up activities (this is not done automatically by the Adapter Manager).

 

Required Methods

There are only a small number of methods required to create the actor adapter, as touched on in the previous section:

 

public string getAdapterConfigurationClassName()

public void initalize()

public AdapterResponse performAction(AdapterRequest aRequest)

public void shutdown()

 

The getAdapterConfigurationClassName() method must return the class-name of the adapter's configuration class -- this is how BAO knows which configuration class is associated with the adapter class.

 

In the initialize() method, you can set up any persistent objects, create connections to external systems, and perform any other activities needed to prepare the adapter. This method can be empty (bar a call to the same method in the super-class).

 

The performAction() method is called each time your workflows make a call to this adapter. This is where the primary capabilities of the adapter are included.

 

Finally, the shutdown() method allows you to perform clean-up activities when the adapter has been asked to stop.

 

The Configuration Class

So with the above said, here is our template "empty" configuration class:

 

package com.example.bao.adapter.hostid.configuration;


import com.realops.common.configuration.InvalidConfigurationException;
import com.realops.foundation.adapterframework.configuration.BaseAdapterConfiguration;


/**
* Configuration class for the Host Identifier Adapter
*/
public class HostIdentifierActorConfig extends BaseAdapterConfiguration{


    // Constructor - when passed the adapter ID as a string
    public HostIdentifierActorConfig(String adapterId) {
        super(adapterId);
        System.out.println("Adapter config created with adapter ID: " + adapterId);
    }


    // Constructor - when passed a BaseAdapterConfiguration object
    public HostIdentifierActorConfig(BaseAdapterConfiguration newConfiguration) {
        super(newConfiguration.getAdapterId());
        setAdapterName(newConfiguration.getAdapterName());
        System.out.println("Adapter config created with config object.");
    }


    @Override
    public void validate() throws InvalidConfigurationException {
        super.validate();
        System.out.println("Configuration validation requested.");
    }
}
    

 

The Actor Adapter Class

Here we have our template actor adapter class:

 

package com.example.bao.adapter.hostid;


import com.example.bao.adapter.hostid.configuration.HostIdentifierActorConfig;
import com.realops.common.enumeration.StateEnum;
import com.realops.foundation.adapterframework.AbstractActorAdapter;
import com.realops.foundation.adapterframework.AdapterException;
import com.realops.foundation.adapterframework.AdapterRequest;
import com.realops.foundation.adapterframework.AdapterResponse;


/**
* Host Identifier Actor Adapter
*/
public class HostIdentifierActor extends AbstractActorAdapter {


    @Override
    public void initialize() throws AdapterException {
        super.initialize();
        System.out.println("Adapter initialisation complete.");
    }


    @Override
    public AdapterResponse performAction(AdapterRequest adapterRequest) throws AdapterException, InterruptedException {
        System.out.println("Adapter was requested to perform an action.");
        return null;
    }


    @Override
    public void shutdown() throws AdapterException {
        System.out.println("Adapter shutdown request received.");
        super.setState(StateEnum.STOPPED);
    }


    @Override
    public String getAdapterConfigurationClassName() {
        return HostIdentifierActorConfig.class.getName();
    }
}
    

 

 

Test Class

And lastly we have a test class, which so far I have only mentioned in passing. Because BAO adapters are a type of plug-in, you can't execute them directly in your Java IDE. Instead, you must wrap them in a basic test class that simulates some of the behaviours of the Atrium Orchestrator grid. Primarily this means creating an instance of the adapter, creating and passing a configuration object, calling the initialize() method, doing some test performAction() calls, and finally sending the shutdown() message. In other words, we're simulating the life-cycle the adapter would go through on the grid, which allows much more effective debugging than uploading the adapter classes to the grid in order to test them.

 

There are a few ways of doing this. One is to create instances of the actual BAO class loader, adapter manager and other functions. This provides the most similar environment to a real grid, but has the disadvantages of being more complex and requiring more code. So, for simplicity, we're going to just create an instance of our adapter class using standard Java functions.

 

For your own sanity, you should probably place any test classes in a separate module to the adapter classes -- in this way you can easily exclude them from the JAR file creation described below. Just ensure that you specify the dependency of the test module on the main adapter module so that you can import the adapter classes. At this stage we'll just create one simple test class to run through the adapter life-cycle.

 

I'm using JUnit to run the tests. You will need to ensure your IDE is configured to support the JUnit framework -- that is outside of the scope of this document, but Google is your friend! You don't have to use JUnit, of course; it's possible to run a set of tests with just a regular class that kicks off from a main() method. The choice is yours.

 

Important note: Up until now, we've only required three or four of the libraries from an Atrium Orchestrator grid so that we can compile the adapter. In order to perform unit testing in the IDE, you're going to need more. You could add just those that are required, which at this stage would be:

  • jaxen-1.1.2.jar
  • jdom-1.0.jar
  • log4j-1.2.15.jar
  • utilities-7.6.03.jar

 

However, a better long-term approach is to copy all of the libraries from your peer's lib folder into a local folder, then add that folder to your IDE. In IntelliJ IDEA these are conveniently shown as a single entry in the external libraries list:

 

IntelliJ IDEA BAO Libraries.png

 

Within that BAO7.6.03 library folder are the 180+ libraries copied from my CDP. This means that, whatever functions I use in my adapter, I should be able to perform unit tests for them. On to the test code:

 

package com.example.bao.adapter.hostidTest;

import static junit.framework.Assert.*;
import com.example.bao.adapter.hostid.HostIdentifierActor;
import com.realops.common.xml.XML;
import com.realops.foundation.adapterframework.AdapterRequest;
import com.realops.foundation.adapterframework.AdapterResponse;
import org.apache.log4j.xml.DOMConfigurator;

/**
* Junit Tests for Host Identifier Actor
*/
public class HostIdentifierActorTest {

    private static HostIdentifierActor adapter = null;
    private static final String adapterName = "HostIdentifierAdapter";
    private static final String adapterId = "HostIdentifierID";

    public HostIdentifierActorTest() {
        DOMConfigurator.configure("log4j.xml");
    }

    @org.junit.Before
    public void setUp() throws Exception {
        try {
            // Create an instance of the Host Identifier Actor Adapter
            adapter = new HostIdentifierActor();
            adapter.createConfiguration(adapterId);
            adapter.setName(adapterName);
            adapter.initialize();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @org.junit.After
    public void tearDown() throws Exception {
        adapter.shutdown();
        adapter = null;
    }

    @org.junit.Test
    public void testPerformAction() throws Exception {
        XML dummyRequest = new XML("dummy");
        AdapterResponse response = null;
        AdapterRequest request;
        try {
            request = new AdapterRequest(adapterName, null, dummyRequest);
            response = adapter.performAction(request);
            assertNull("Expected a null response from the adapter", response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
    

 

 

Uploading to the Grid

So at this point we have a couple of .class files containing our compiled configuration and adapter classes. They're not doing us much good sat on our local disks, so we need to upload them to a BAO grid so that they can perform their null operations on a real grid. At this stage in the tutorial, we're going to perform this upload in an ugly, slow, error-prone way; later on we'll look at building AROAR files to streamline the deployment process, but for now this will get us up and running.

 

You need to output the classes as a .jar file. In IntelliJ IDEA you can have this generated automatically by configuring a JAR artifact under File -> Project Structure -> Artifacts. Add a jar "from modules with dependencies" and set the output layout to only include the compile output (you don't need to include the BAO libraries). Also, create a manifest file in the default location using the "Create Manifest..." button. The resulting JAR can then be found under the out/artifacts folder.

 

In Eclipse, right-click on your project and choose Export -> Java -> JAR File. Choose the actor adapter and configuration classes, and a suitable output name. You can choose to "save the description of this JAR in the workspace" so that future exports are done by right-clicking the resulting .jardesc file and choosing "Create JAR".

 

Now that we have our classes safely contained in a JAR, we can open up Grid Manager and navigate to Manage / Adapters. Click that oh-so-tempting "Upload Adapter" button and fill out the form:

 

upload_adapter_the_hard_way3.png

Click OK and your adapter should upload. In the left-hand side of the screen you will see "example host identifier 0.0.1 rev 1". Click this tick-box next to this and add it to the grid with an empty configuration like so:

 

add_adapter_to_grid.png

(Note the use of "Core_" as a prefix is just a convention of mine -- when working with segmented and firewalled networks, it is often helpful to tag adapters based on whether they are going to run as part of the core infrastructure or a 'remote' network.)

 

Now you just need to enable the adapter on one or more peers and that's it:

 

Running Adapter.png

What's Next?

Our adapter is now running on the grid and will respond to stop and start commands. Of course, at this stage the adapter doesn't actually do anything useful, but that will change in the next episode!

 

Addendum

If you require a log4j.xml file, the following is a basic example:

 

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender" >
        <layout class="org.apache.log4j.SimpleLayout"/>
    </appender>
    <category name="com.realops" >
      <priority value="all" />
    </category>
    <category name="com.bmc.adapter" >
      <priority value="all" />
    </category>
    <root>
        <priority  value="debug" />
        <!-- <appender-ref  ref="RollingFile" />-->
        <appender-ref  ref="STDOUT" /> 
    </root>
</log4j:configuration>