J. Brisbin

Just another Wordpress.com weblog

Exposing AS/400 Service Programs via RabbitMQ and Java

with 2 comments

One of the things we’ve been doing more and more at my day job (I work at the world’s largest Pizza Hut franchisee, NPC International) is integrating the work we’re doing in Java with the folks over on the RPG and AS/400 side of things (I don’t care what IBM chooses to call it this year; to me it will always be the AS/400, and that’s what I call it). The integration goes both ways: from RPG into Java and from Java back into RPG. In order to reduce complexity and implement a hillbilly version of Don’t Repeat Yourself, we’ve decided to expose the functionality locked inside our service program functions to other code, including Java and C++. To do that, we’re dispatching messages through the RabbitMQ message broker.

I realize there’s no one way to do this. My way may not even be the best (I suspect its not). But I’d like to outline one way to expose RPG service programs to code written in other languages by wiring the two together with asynchronous messaging.

The RPG Side

Unfortunately, DRY can’t be completely eliminated on the RPG side of things. That’s just not the RPG way. 🙂 If you have a service program that exports a function like so (maybe this header is called CALIB/CA9050H):

D get_example_value...
D                 PR            15P 5
D  company                       3P 0 VALUE
D  store_number                  5P 0 VALUE
D  bus_date                      8P 0 VALUE

…you can’t just wrap a Java native method around that service program and go. The problem is in the way the export works inside the service program that implements this function. In CALIB/CA9050SRV, if you wanted to expose this function to other RPG code, you simply provide your export line like normal:

P get_example_value...
P                 B                   export
D get_example_value...
D                 PI            15P 5
D  company                       3P 0 VALUE
D  store_number                  5P 0 VALUE
D  bus_date                      8P 0 VALUE

d sql_cmd         s           1028a

 /FREE

  sql_cmd = 'SELECT exval FROM MYLIB.MYFILE ' +
  ...

…and implement your function. But to call this function from Java (by declaring a “native” method in your Java class) you need a different header definition. I’ve not found a way to export the same function two different ways, so I had to create a parallel service program (CALIB/CA9050JSRV) that makes my function callable from Java (this header file would be CALIB/CA9050JH), and in the process, rename the function to something more Java-centric rather than the C++-influenced lowercase and underscores:

D getExampleValue...
D                 PR             8F   ExtProc(*JAVA:
D                                      'com.npci.cloud.iseries.+
D                                       CALIB.CA9050JSRV':
D                                      'getExampleValue')
D  company                      10I 0 VALUE
D  store_number                 10I 0 VALUE
D  bus_date                     10I 0 VALUE

Now I can implement my service program, EXPORTing it for use by Java by mapping this native ILE function to a Java class (here’s the full member, CALIB/CA9050JSRV):

P getExampleValue...
P                 B                   EXPORT
D getExampleValue...
D                 PI             8F
D  company                      10I 0 VALUE
D  store_number                 10I 0 VALUE
D  bus_date                     10I 0 VALUE
D rate            S             15P 5
 /FREE
   rate = get_example_value(
     company : store_number : bus_date
   );
   return rate;
 /END-FREE
P getExampleValue...
P                 E

All this does is delegate the actual logic to the original CALIB/CA9050SRV function, which can be used by RPG code, while this service program can be used by Java.

The Java Side

Now all that’s left is to implement our Java class, declaring a native method that ties that class to this service program. This is an example which includes some extra features I’ve written to make working with service programs easier:

public class CA9050JSRV extends ServiceProgram {

  @Procedure(srvpgm = "CA9050JSRV", name = "getexample")
  public native float getExampleValue(int cono, int stono, int busdate);

  @Procedure(srvpgm = "", name = "test")
  public float getExampleValue(int cono, int stono, int busdate) {
    return 0.8f;
  }

}

I wrote an abstract base class, ServiceProgram, which is responsible for executing the System.loadLibrary() call (with the value from the annotation). Since the code that does the introspection retains this annotation during runtime, I can interrogate this annotation for the right service program to tie this class to and export that function under a shorter name, which is what is exposed to the incoming messages.

The Messaging Side

Now that we’ve exposed our RPG function through a Java native method, we can work on calling that Java method via introspection, triggered by a RabbitMQ message.

The code to consume messages is fairly straightforward. I simply use a QueueingConsumer (from the RabbitMQ Java API) to dump messages into a java.util.concurrent.BlockingQueue, from which various message handlers pull, based on the message type. I don’t want to muddy the waters too much here–I’m saving some of this for later blog posts. But suffice it to say I have a MessageRouter class that is managed by the Spring Framework, runs on the AS/400 in its own subsytem, and listens for incoming RabbitMQ messages. If it receives a ServiceProgram invocation message, it dispatches it to the appropriate BlockingQueue and the message handler decodes the incoming JSON data. An example might look like this:

{
  "program": "ca9050",
  "procedure": "getexample",
  "params": [ 1, 1134, 20100413 ]
}

In order to get from this JSON data to invoking the above Java method, my MessageHandler has to know what service programs and procedures it’s allowed to invoke. If the method isn’t annotated with a @Procedure annotation, the invoker knows nothing about it. It also doesn’t respond to class names (another security feature) but arbitrary keys set up by the developer.

Maybe it’s best to show the snippet of Spring XML that configures the MessageHandler:

<bean id="srvpgmMessageHandler" class="com.npci.cloud.ServiceProgramMessageHandler" scope="prototype">
  <property name="servicePrograms">
    <map>
      <entry key="ca9050">
        <bean class="com.npci.cloud.iseries.calib.CA9050JSRV"/>
      </entry>
    </map>
  </property>
</bean>

You’ll notice that the JSON data specifies the key referencing the right bean, rather than specifying a generic class name. This is intentional and security-minded. Although our internal network is secure and disconnected from the rest of the world by various routing and firewalling devices, it’s still not a good idea to completely open up a service to invoking of arbitrary objects. PCI and SOX compliance auditors would have a cow, if that were the case! 🙂

The ServiceProgramMessageHandler calls a method on the ServiceProgram base class which is responsible for handing back the right java.lang.reflect.Method which it can invoke, passing the params from the JSON data.

The Return

Our services communicate with one another via JSON, so the output of this native method call is sent back to the requestor’s queue, encoded in JSON format:

{
  "result": "success",
  "total": 1,
  "data": [ .8 ]
}

This allows us to keep from repeating critical pieces of business logic that reside in the bowels of the AS/400 without resorting to over-priced IBM software. Who isn’t for that? 🙂

The MessageRouter infrastructure was written on company time first, so I can’t simply release it as OpenSource. I can, however, give some insight into how we’re solving these problems internally and maybe inspire others to pick up the torch and write truly OpenSource solutions.

In a future post I’ll discuss how we’re batching messages and sending them zip-compressed to save on precious WAN bandwidth.

Advertisements

Written by J. Brisbin

April 13, 2010 at 3:16 pm

Posted in RabbitMQ, The Virtual Cloud

Tagged with , , ,

2 Responses

Subscribe to comments with RSS.

  1. Nice! I’m from the UNIX side of the world, but I’m very interested in the IBM side and happy to see someone writing about bridging IBM with other “worlds”. Thanks for sharing!

    Bryan

    April 15, 2010 at 1:24 pm

  2. […] This post was mentioned on Twitter by alexis richardson, J. Brisbin. J. Brisbin said: Updated the blog with two new HOWTOs: http://bit.ly/bQ3CLp and http://bit.ly/c8CNF6 #vcloud #rabbitmq #systemi […]


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 )

Google+ photo

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

Connecting to %s

%d bloggers like this: