CORBA, Part I: Java and the Web using the Play Framework

CORBA, Part I: Java and the Web using the Play Framework

by Charles Calkins, Principal Software Engineer 

January 2013

Introduction

Available since the 1990s, CORBA [1] is a mature technology for developing distributed applications. While some have forecasted its demise [2], it is still alive and well in embedded and realtime systems [3], as well as used in a wide range of industries, including banking and finance, healthcare, telecommunications, entertainment and more [4]. Technologies that have been developed to be simpler than CORBA, as they have matured, are no longer simple, are implementing similar infrastructure, and, in some cases, remain less functional [4][5].

For basic client-server communication, CORBA does not need to be complex, and provides a straightforward means for developing systems that span multiple languages and platforms. This article is the first in a series of two articles that shows a simple use of CORBA to connect Linux and Windows, Java and .NET, and a web application, into a unified system. The Add example as used in previous Middleware News Briefs [6][7] will be revisited, though rather than passing an object reference via a file, the Naming Service will be used.

Linux and Java

We'll begin development on ako, a machine running 64-bit Ubuntu 12.10, with Java 1.7.0_09. As in the previous articles, we define the server interface in IDL.

// IDL/Math.idl
module MathModule {
    interface Math 
    {
        long Add(in long x, in long y);   
    };
};

SIDEBAR

The build system uses OCI's MPC [8] to generate the project files, with the Java compilation style based on that as used by OCI's OpenDDS [9]. The various MPC build files, including IDL/IDL.mpc, are included in the code archive that accompanies this article. The environment variable NETCORBAWEB_ROOT must be set to point to the top level of the extracted source, and, while in that same directory, MPC is invoked as:

$ $MPC_ROOT/mwc.pl -type make NetCorbaWeb.mwc

This generates a series of Makefiles, and executing make in this directory will build all accompanying projects.

The build files as generated by MPC invoke idlj from the Java JDK on Math.idl, producing several Java files which comprise the client stub and server skeletons, and these are packaged into the file MathIDL.jar.


We create a library, JavaAddLib, so code can be shared among projects. Our first item in the library is an implementation of the Math interface, as was defined in the IDL.

  1. // JavaAddLib/MathImpl.java
  2. package com.ociweb.JavaAddLib;
  3.  
  4. import MathModule.MathPOA;
  5.  
  6. public class MathImpl extends MathPOA {
  7.  
  8. @Override
  9. public int Add(int x, int y) {
  10. return x + y;
  11. }
  12. }

Our second item in the library is a base class to centralize common client and server behavior. Java began an implementation of CORBA in version 1.2, and included full support, with features such as the portable object adapter, portable interceptors, dynamic ANYs, and an implementation of the naming service, in version 1.4 [10]. We will use some of this functionality, found in the org.omg.CORBA and related packages, in this article.

We provide two means by which the ORB is initialized. The first is via an array of strings.

  1. // JavaAddLib/CorbaBase.java
  2. package com.ociweb.JavaAddLib;
  3.  
  4. import java.util.logging.ConsoleHandler;
  5. import java.util.logging.Handler;
  6. import java.util.logging.Logger;
  7.  
  8. public class CorbaBase {
  9. protected org.omg.CORBA.ORB _orb = null;
  10.  
  11. public void init(String[] args) {
  12. SetConsoleLogLevel(java.util.logging.Level.OFF);
  13. _orb = org.omg.CORBA.ORB.init(args, null);
  14. }

The second is by supplying the port, and optionally the host, of the running Naming Service [11]. The Naming Service is a CORBA service which provides a directory tree that associates names with object references. The ORBInitialPort and ORBInitialHost properties identify the location of the Naming Service instance.

    public void init(int nsPort) { init(nsPort, null); }
    public void init(int nsPort, String nsHost) {
        SetConsoleLogLevel(java.util.logging.Level.OFF);
        java.util.Properties props = new java.util.Properties();
        props.put("org.omg.CORBA.ORBInitialPort", 
            String.valueOf(nsPort));
        if (nsHost != null)
            props.put("org.omg.CORBA.ORBInitialHost", nsHost);
        _orb = org.omg.CORBA.ORB.init((String[])null, props);	
    }

The destroy() method will shut down the ORB and clean up its resources.

    public void destroy() {
        if (_orb != null)
            _orb.destroy();
    }

The CORBA functionality within the Java runtime uses java.util.logging.Logger for the display of messages. Suppressing these messages via a manner described in [12], and handling errors and other conditions explicitly, allows console output to be less cluttered.

    void SetConsoleLogLevel(java.util.logging.Level level) {
        // get the top logger
        Logger topLogger = java.util.logging.Logger.getLogger("");
 
        Handler consoleHandler = null;
 
        // see if there is already a console handler
        for (Handler handler : topLogger.getHandlers()) {
            if (handler instanceof ConsoleHandler) {
                consoleHandler = handler;
                break;
            }
        }
 
        if (consoleHandler == null) {
            // no console handler found, so create a new one
            consoleHandler = new ConsoleHandler();
            topLogger.addHandler(consoleHandler);
        }
 
        // set the console handler log level:
        consoleHandler.setLevel(level);
    }
}

The next items for the library encapsulate the interaction with the Naming Service. A small helper class is needed to associate human-readable names with object references.

// JavaAddLib/Ref.java
package com.ociweb.JavaAddLib;
 
public class Ref {
    public final String Name;
    public final org.omg.CORBA.Object ObjRef;
 
    public Ref(String name, org.omg.CORBA.Object objRef) {
        Name = name;
        ObjRef = objRef;
    }	
}

Class Naming provides methods to bind, unbind, and enumerate object references in the Naming Service. We shall first implement the bind operation. The method resolve_intitial_references() is called on the ORB to obtain a reference to the Naming Service, as located by the host and port provided earlier. This reference is then narrowed to a NamingContext, which represents a collection of name-reference pairs and/or other contexts.

  1. // JavaAddLib/Naming.java
  2. package com.ociweb.JavaAddLib;
  3.  
  4. import java.util.*;
  5.  
  6. import org.omg.CORBA.UserException;
  7. import org.omg.CosNaming.*;
  8. import org.omg.CosNaming.NamingContextPackage.*;
  9.  
  10. public class Naming {
  11.  
  12. static final NameComponent[] _adders =
  13. new NameComponent[] { new NameComponent("Adders", "") };
  14.  
  15. public static void bind(org.omg.CORBA.ORB orb, Ref ref)
  16. throws UserException {
  17.  
  18. NamingContext nsCtx =
  19. NamingContextHelper.narrow(
  20. orb.resolve_initial_references("NameService"));

We wish to provide object references to multiple objects implementing the Math interface, so we create a new context named "Adders" to hold them. The method bind_new_context() is used to create the new context, but it will throw exceptions if errors occur, or if the context already exists. If the context already exists, we wish to use the existing one, but fail if any other errors occur.

        NamingContext addersCtx = null;
        try {
            addersCtx = nsCtx.bind_new_context(_adders);
        } catch (AlreadyBound e) {
            // ok - context already exists
            addersCtx = NamingContextHelper.narrow(
                nsCtx.resolve(_adders));
        }

One we have the Adders context, rebind() is used to bind the name-object reference association, overwriting any existing bindings that have the same name.

        NameComponent[] name = {new NameComponent(ref.Name, "")}; 
        addersCtx.rebind(name, ref.ObjRef);
    }

To unbind a name-object reference association, we perform the same steps in reverse, after obtaining the initial context from the Naming Service.

    public static void unbind(org.omg.CORBA.ORB orb, Ref ref) 
      throws UserException {
 
        NamingContext nsCtx = 
            NamingContextHelper.narrow(
                orb.resolve_initial_references("NameService"));

We obtain the Adders context, and unbind the reference.

        NamingContext addersCtx = 
            NamingContextHelper.narrow(nsCtx.resolve(_adders));
 
        NameComponent[] name = { new NameComponent(ref.Name, "") };
        addersCtx.unbind(name);

If the Adders context is now empty of references, we destroy it, and remove it, too, from the Naming Service.

        if (list(orb).isEmpty()) {
            addersCtx.destroy();
            nsCtx.unbind( _adders);
        }
    }

We also need to obtain a list of all of the object references that are registered. Once again, we obtain the initial context from the Naming Service, and the Adders context from it. If the Adders context is not found, then there are no Adder references to return.

    public static Vector<Ref> list(org.omg.CORBA.ORB orb) 
      throws UserException {
 
        Vector<Ref> refList = new Vector<Ref>();
 
        NamingContext nsCtx = 
            NamingContextHelper.narrow(
                orb.resolve_initial_references("NameService"));
 
        NamingContext addersCtx;
        try {
            addersCtx = NamingContextHelper.narrow(
                nsCtx.resolve(_adders));
        } catch (NotFound e) {
            return refList;  // no context, so no adders
        }

If the Adders context does exist, we ask for 100 bindings at a time from it, via the list() method. If more than 100 bindings are available, a BindingIterator is also returned which is used to fetch the remainder. For each binding found, addBindings() is called.

        BindingListHolder blh = new BindingListHolder();
        BindingIteratorHolder bIth = new BindingIteratorHolder();
        int chunkSize=100;
        addersCtx.list(chunkSize, blh, bIth);
 
        addBindings(blh, addersCtx, refList);
        if (bIth != null) {
            BindingIterator bi = bIth.value;
            while (bi.next_n(chunkSize, blh))
                addBindings(blh, addersCtx, refList);
            bi.destroy();
        }
 
        return refList;
    }

The addBindings() method extracts the name of the binding, as well as the object reference, and adds them to the list to return to the caller.

    static void addBindings(BindingListHolder blh, 
        NamingContext addersCtx, Vector<Ref> refList) 
      throws UserException {
 
        Binding bindings[] = blh.value;
        for (int i=0; i<bindings.length; i++) {
            org.omg.CORBA.Object objRef = 
                addersCtx.resolve(bindings[i].binding_name);
            String name = bindings[i].binding_name[
                bindings[i].binding_name.length -1].id;        	
            refList.add(new Ref(name, objRef));
        }
    }
}

We now add a class which implements the server functionality to the library. The run()method activates the root POA, creates the Math servant, binds it in the Naming Service, and then runs the ORB to wait for incoming client requests. All CORBA functionality is encapsulated, as even exceptions are caught before propagation to the caller and converted into a RuntimeException [13].

// JavaAddLib/JavaServer.java
package com.ociweb.JavaAddLib;
 
import org.omg.CORBA.UserException;
 
public class AddServer extends CorbaBase {
    Ref ref = null;
 
    public void run(String name) {
        try {
            // obtain a reference to the RootPOA
            org.omg.PortableServer.POA poa =
                org.omg.PortableServer.POAHelper.narrow(
                    _orb.resolve_initial_references("RootPOA"));
 
            // activate the POAManager
            poa.the_POAManager().activate();
 
            // create the Math servant
            org.omg.CORBA.Object objRef = 
                poa.servant_to_reference(new MathImpl());
 
            // set the object reference in the name server
            ref = new Ref(name, objRef);
            Naming.bind(_orb, ref);
 
            // run the ORB
            _orb.run();
        } catch (UserException e) {
            throw new RuntimeException(e);
        }
    }

The shutdown() method unbinds the Adder object reference from the Naming Service, shuts down the ORB, and cleans up any remaining resources.

    public void shutdown() {
        try {
            if (_orb != null) {
                Naming.unbind(_orb, ref);
                _orb.shutdown(true);
            }	
        } catch (UserException e) {
            throw new RuntimeException(e);
        }
        finally {
            destroy();
        }
    }
}

Finally, we provide an encapsulation of the client. The add() method narrows the provided object reference of an Adder object, and calls its Add() method.

  1. // JavaAddLib/JavaClient.java
  2. package com.ociweb.JavaAddLib;
  3.  
  4. import java.util.Vector;
  5. import java.util.List;
  6. import java.util.logging.*;
  7.  
  8. import org.omg.CORBA.UserException;
  9.  
  10. public class AddClient extends CorbaBase {
  11.  
  12. public int add(Ref ref, int x, int y) {
  13. try {
  14. MathModule.Math math =
  15. MathModule.MathHelper.narrow(ref.ObjRef);
  16. return math.Add(x, y);
  17. } catch (Exception e) {
  18. throw new RuntimeException(e);
  19. }
  20. }

The list() method obtains the list of bindings from the Naming Service.

    public Vector<Ref> list() {
        try {
            return Naming.list(_orb);
        } catch (UserException e) {
            throw new RuntimeException(e);
        }
    }
}

With this, the JavaAddLib library is complete. We will first use it in a server application. As Naming Service host and port arguments must be supplied, we'll provide them on the command line. The Apache Commons CLI library [14] will be used to parse them.

  1. // JavaServer/JavaServer.java
  2. package com.ociweb.JavaServer;
  3.  
  4. import java.lang.management.ManagementFactory;
  5. import org.apache.commons.cli.*;
  6.  
  7. import com.ociweb.JavaAddLib.*;
  8.  
  9. public class JavaServer {
  10. public static CommandLine ParseArgs(String[] args) {
  11. try {
  12. CommandLineParser parser = new PosixParser();
  13. Options options = new Options();
  14. options.addOption("?", "help", false,
  15. "print this message");
  16. options.addOption(null, "ORBInitialPort", true,
  17. "nameserver port (required)");
  18. options.addOption(null, "ORBInitialHost", true,
  19. "nameserver host");
  20. CommandLine line = parser.parse( options, args );
  21.  
  22. if (line.hasOption("help") ||
  23. !line.hasOption("ORBInitialPort")) {
  24. HelpFormatter formatter = new HelpFormatter();
  25. formatter.printHelp("Server", options);
  26. return null;
  27. }
  28.  
  29. return line;
  30. } catch (ParseException e) {
  31. System.out.println(e.getMessage());
  32. return null;
  33. }
  34. }

The server process parses the command-line arguments, sets a hook so shutdown() is called when CTRL-C is pressed, and runs an instance of the AddServer class that was defined in JavaAddLib. The name of the running instance is displayed so the particular server can be easily identified. The method that is used to obtain the instance name should be valid in most circumstances [15].

  1. public static void main(String[] args) {
  2. try {
  3. CommandLine line = ParseArgs(args);
  4. if (line == null)
  5. return;
  6.  
  7. String instanceName = "Java_" +
  8. ManagementFactory.getRuntimeMXBean().getName();
  9. final AddServer s = new AddServer();
  10.  
  11. Runtime.getRuntime().addShutdownHook(new Thread() {
  12. public void run() {
  13. System.out.println("Shutting down...");
  14. s.shutdown();
  15. }
  16. });
  17.  
  18. s.init(args);
  19. System.out.println("Running " + instanceName);
  20. s.run(instanceName);
  21. } catch (Exception e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. }

We also create a client application using the JavaClient class from JavaAddLib. It, too, obtains command-line arguments (not shown here), and then invokes the add() method of each Adder object located by the Naming Service. If add() was called successfully, the sum is displayed. If an error was generated, it is shown instead. For instance, if a server dies without unbinding its name from the Naming Service, the Naming Service will contain a stale reference. Use of the stale reference will raise a CORBA exception.

...
    public static CommandLine ParseArgs(String[] args) {
        ...
    }
 
    public static void main(String[] args) {
        CommandLine line = ParseArgs(args);
        if (line == null)
            return;
 
        String[] opt = line.getOptionValues("add");
        int x = Integer.parseInt(opt[0]);
        int y = Integer.parseInt(opt[1]);
 
        AddClient c = new AddClient();
        c.init(args);
 
        Vector<Ref> refs = c.list();
        if (refs.size() == 0) 
            System.out.println("No servers found");
        else {
            Iterator<Ref> it = refs.iterator();
            while(it.hasNext()) {
                Ref r = it.next();
                System.out.print(r.Name + ": ");
                try {
                    System.out.println(c.add(r, x, y));
                } catch (Exception e) {
                    System.out.println(e.getMessage());
                }
            }
        }
 
        c.destroy();
    }
...

To run the client and server, first start an instance of orbd, the Object Request Broker Daemon, that is included with Java. This daemon provides Naming Server functionality, and a port must be provided for the Naming Server to listen on. We use 2809 here, the standard port as assigned by the IANA. When an incoming request is received, orbd can also be used to automatically start servers that are registered with it if they not already running, but we will not make use of that capability.

$ orbd -ORBInitialPort 2809 &

In one window, start the server. The same port that was provided to orbd must be given here as well.

$ java -jar JavaServer.jar -ORBInitialPort 2809
Running Java_31313@ako

In another window, run the client, providing the Naming Service port, and numbers to add. The result will be displayed, along with the name of the server that returned the answer.

$ java -jar JavaClient.jar -ORBInitialPort 2809 -add 4 7
Java_31313@ako: 11

The configuration of the above processes is shown in the following diagram, where clients are represented as diamonds, servers as ovals, and the Naming Service as a triangle. Here, and in the successive diagram, newly-started processes are highlighted in gray.

CORBA processes

Linux and the Web

We can also use JavaAddLib in a web application. The Play Framework [16], distributed under the Apache License version 2.0, allows web applications to be created rapidly, which are even deployable to cloud services such as Heroku [17]. After the Play Framework is installed (version 2.0.4 is used here) and added to the PATH, the framework of a new application named JavaWeb is created with the command

$ play new JavaWeb

A series of questions are asked, resulting in a new Java application created in a directory with the same name. To make use of JavaAddLib, create a lib directory under the JavaWeb directory, and copy JavaAddLib.jar into it. Any dependencies in this directory will be automatically included by Play.

After changing directory into the JavaWeb directory, executing this command will create an Eclipse project, which can then be imported into an Eclipse workspace for easy editing.

$ play eclipsify

Play uses the Model-View-Controller framework. The default application includes an Application class (extending the Controller class) implemented in app/controllers/Application.java, and a view for the index (default) page in app/views/index.scala.html. Play uses Scala as a template language, allowing HTML pages to be compiled, in addition to the rest of the application. index.scala.html invokes a method in main.scala.html as a template to centralize page script includes and other common features.

For our application, we first update the Application class, as follows. Several code imports are performed, including for JavaAddLib, and the AddParams class is defined. It will store values that will be entered by the user into a web form.

  1. // app/controllers/Application.java
  2. package controllers;
  3.  
  4. import java.util.*;
  5.  
  6. import play.*;
  7. import play.mvc.*;
  8. import play.data.Form;
  9. import play.data.validation.Constraints.*;
  10. import play.libs.F.*;
  11.  
  12. import views.html.*;
  13. import com.ociweb.JavaAddLib.*;
  14.  
  15. public class Application extends Controller {
  16.  
  17. public static class AddParams {
  18. @Required public Integer x;
  19. @Required public Integer y;
  20. }

The core functionality of the application is implemented in the AddValues() method. In Play 2.0.4, asynchronous operations are performed by the use of the Akka actor-based framework which is included with Play. We create an Akka future that wraps virtually the same code as is contained in JavaClient.java, where the parameters x and y are the two integers to add. By executing potentially long-running CORBA calls asynchronously, the web application's execution is not blocked. The main difference between this code and that of JavaClient.java is that here, instead of displaying the output to the console, the output is provided as a list of strings that is returned to the caller.

    static Result AddValues(final int x, final int y) {
        play.libs.F.Promise<List<String>> promise = 
          play.libs.Akka.future(
            new java.util.concurrent.Callable<List<String>>() {
                public List<String> call() {
                    String namingserviceHost = Play.application().
                        configuration().getString("namingservice.host");
                    Integer namingservicePort = Play.application().
                        configuration().getInt("namingservice.port");
                    List<String> response = new ArrayList<String>();
 
                    try {
                        AddClient c = new AddClient();
                        c.init(namingservicePort, namingserviceHost);
 
                        Vector<Ref> refs = c.list();
                        if (refs.size() == 0) 
                            response.add("No servers found");
                        else {
                            Iterator<Ref> it = refs.iterator();
                            while(it.hasNext()) {
                                Ref r = it.next();
                                String msg = r.Name + ": ";
                                try {
                                    msg += c.add(r, x, y);
                                } catch (Exception e) {
                                    msg += e.getMessage();
                                }
                                response.add(msg);
                            }
                        }
 
                        c.destroy();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
 
                    return response;
                }
            }
        );

The creation of the future returns a promise object. When the promise is fulfilled, the values of xy and the list of strings containing the addition results are sent to the add.scala.html view via its implicit render() method. The built-in function ok() returns the result as successful (HTTP status code 200 - OK) to the browser.

        return async(
            promise.map(
                new Function<List<String>,Result>() {
                    public Result apply(List<String> response) {
                        return ok(add.render(x,y,response));
                    } 
                }
            )
        );
    }

The next two methods will be mapped to URLs. The first, index(), displays the index view, and passes a form object based on the AddParams class. Fields of the form will be associated with variables of the class.

    public static Result index() {
        return ok(
            index.render(form(AddParams.class))
        );
    }

The doAdd() method displays the response of the addition as produced by AddValues(). The values of x and y are set by obtaining the form from the request, and then retrieving its contents, which is of type AddParams.

    public static Result doAdd() {
        Form<AddParams> form = form(AddParams.class).bindFromRequest();
        if (form.hasErrors()) {
            return badRequest(index.render(form));
        } else {
            AddParams data = form.get();
            return AddValues(data.x,data.y);
        }
    }
}

With the controller complete, we now create the views. The app/views/index.scala.html is changed from the default to present a form based on the AddParams class, prompting for xand y. When index.render() is called, this page is generated and is ready to be returned to the browser.

@(addForm: Form[Application.AddParams])
 
@import helper._
 
@main("JavaWeb") {
 
    <p>Enter two numbers to add:</p>
 
    @form(action = routes.Application.doAdd, args = 'id -> "addform") {
 
        @inputText(
            field = addForm("x"),
            args = '_label -> "x?"
        )
 
        @inputText(
            field = addForm("y"),
            args = '_label -> "y?"
        )
 
        <p class="buttons">
            <input type="submit">
        </p>
    }
}

We also need a view to display the result of the addition. The file app/views/add.scala.html is created as follows. It receives xy, and sums, the list of strings as passed from doAdd() as parameters into the page. It displays x and y directly, and iterates over sums via a for loop. This page also provides a link back to the index page so new values to add can be entered.

@(x: Integer, y: Integer, sums: List[String])
 
@main("Result") {
    <p>The sum of @x and @y is:</p>
 
    <ul>
    @for(sum <- sums) {
        <li>@sum</li>
    }
    </ul>
 
    <p>
        <a href="@routes.Application.index">Back to the form</a>
    </p>
}

Mapping from URLs to Application methods is done via the conf/routes file. Matching can be done via regular expressions and wildcards, but we only need a simple name. The route to index() is already configured, but a line needs to be added to associate http://host:port/add with the doAdd() method.

GET     /add    controllers.Application.doAdd()

Finally, the host and port for the Naming Service must be specified in the configuration file conf/application.conf. These lines are added at the end of the existing file.

# CORBA configuration
namingservice.host = "localhost"
namingservice.port = 2809

To run the application, execute this command in the JavaWeb directory:

$ play run

This will start Play running in development mode. Starting a browser and opening the URL http://ako:9000 will display the application. Refreshing the browser will cause Play to recompile code as needed, so Play does not need to be shut down as code is modified. If any compilation errors are generated, they will be shown in the browser window.

Enter values for x and y, and the result will be displayed, as shown in the screenshots below.

Play Framework Screenshot
ako2

This gives:

CORBA processes 2

As the form is submitted via HTTP GET, the result page can be referenced directly, such as with:

http://ako:9000/add?x=19&y=22

Conclusion

In this article, we have seen the development of a simple CORBA-based client-server system in Java under Linux, using both command-line-based and web-based clients. As we have seen, CORBA can make communication between processes on a single machine straightforward. Part II of this article will extend the system with the addition of clients and servers running under Microsoft Windows.

References

The Middleware News Brief is a periodic newsletter. The purpose and intent of this publication is to advance and inform on features, news, and technical information about Open Source, middleware technologies (e.g., TAO, OpenDDS, JacORB), including case studies and other technical content.

© Copyright Object Computing, Inc. 1993, 2016. All rights reserved

Subscribe

secret