January 15, 2013 - By Charles Calkins, OCI Senior Software Engineer
Middleware News Brief (MNB) features news and technical information about Open Source middleware technologies.
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 Object Computing's MPC[8] to generate the project files, with the Java compilation style based on that as used by Object Computing'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.
- // JavaAddLib/MathImpl.java
- package com.ociweb.JavaAddLib;
-
- import MathModule.MathPOA;
-
- public class MathImpl extends MathPOA {
-
- @Override
- public int Add(int x, int y) {
- return x + y;
- }
- }
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.
- // JavaAddLib/CorbaBase.java
- package com.ociweb.JavaAddLib;
-
- import java.util.logging.ConsoleHandler;
- import java.util.logging.Handler;
- import java.util.logging.Logger;
-
- public class CorbaBase {
- protected org.omg.CORBA.ORB _orb = null;
-
- public void init(String[] args) {
- SetConsoleLogLevel(java.util.logging.Level.OFF);
- _orb = org.omg.CORBA.ORB.init(args, null);
- }
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.
- // JavaAddLib/Naming.java
- package com.ociweb.JavaAddLib;
-
- import java.util.*;
-
- import org.omg.CORBA.UserException;
- import org.omg.CosNaming.*;
- import org.omg.CosNaming.NamingContextPackage.*;
-
- public class Naming {
-
- static final NameComponent[] _adders =
- new NameComponent[] { new NameComponent("Adders", "") };
-
- public static void bind(org.omg.CORBA.ORB orb, Ref ref)
- throws UserException {
-
- NamingContext nsCtx =
- NamingContextHelper.narrow(
- 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.
- // JavaAddLib/JavaClient.java
- package com.ociweb.JavaAddLib;
-
- import java.util.Vector;
- import java.util.List;
- import java.util.logging.*;
-
- import org.omg.CORBA.UserException;
-
- public class AddClient extends CorbaBase {
-
- public int add(Ref ref, int x, int y) {
- try {
- MathModule.Math math =
- MathModule.MathHelper.narrow(ref.ObjRef);
- return math.Add(x, y);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
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.
- // JavaServer/JavaServer.java
- package com.ociweb.JavaServer;
-
- import java.lang.management.ManagementFactory;
- import org.apache.commons.cli.*;
-
- import com.ociweb.JavaAddLib.*;
-
- public class JavaServer {
- public static CommandLine ParseArgs(String[] args) {
- try {
- CommandLineParser parser = new PosixParser();
- Options options = new Options();
- options.addOption("?", "help", false,
- "print this message");
- options.addOption(null, "ORBInitialPort", true,
- "nameserver port (required)");
- options.addOption(null, "ORBInitialHost", true,
- "nameserver host");
- CommandLine line = parser.parse( options, args );
-
- if (line.hasOption("help") ||
- !line.hasOption("ORBInitialPort")) {
- HelpFormatter formatter = new HelpFormatter();
- formatter.printHelp("Server", options);
- return null;
- }
-
- return line;
- } catch (ParseException e) {
- System.out.println(e.getMessage());
- return null;
- }
- }
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].
- public static void main(String[] args) {
- try {
- CommandLine line = ParseArgs(args);
- if (line == null)
- return;
-
- String instanceName = "Java_" +
- ManagementFactory.getRuntimeMXBean().getName();
- final AddServer s = new AddServer();
-
- Runtime.getRuntime().addShutdownHook(new Thread() {
- public void run() {
- System.out.println("Shutting down...");
- s.shutdown();
- }
- });
-
- s.init(args);
- System.out.println("Running " + instanceName);
- s.run(instanceName);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
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.
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.
- // app/controllers/Application.java
- package controllers;
-
- import java.util.*;
-
- import play.*;
- import play.mvc.*;
- import play.data.Form;
- import play.data.validation.Constraints.*;
- import play.libs.F.*;
-
- import views.html.*;
- import com.ociweb.JavaAddLib.*;
-
- public class Application extends Controller {
-
- public static class AddParams {
- @Required public Integer x;
- @Required public Integer y;
- }
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 x
, y
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 x
and 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 x
, y
, 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.
This gives:
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
- [1] History of CORBA
http://www.omg.org/gettingstarted/history_of_corba.htm - [2] The Rise and Fall of CORBA
http://queue.acm.org/detail.cfm?id=1142044 - [3] Is CORBA legacy?
http://stackoverflow.com/questions/1226050/is-corba-legacy - [4] Reinventing the Wheel? CORBA vs. Web Services
http://www2002.org/CDROM/alternate/395/ - [5] What's Wrong with SOAP
http://storm.alert.sk/blog/2010/01/26/Whats-Wrong-with-SOAP - [6] Multi-Language CORBA Development with C++ (TAO), Java (JacORB), Perl (opalORB), and C# (IIOP.NET)
http://mnb.ociweb.com/mnb/MiddlewareNewsBrief-200904.html - [7] Using Erlang with CORBA and DDS
http://mnb.ociweb.com/mnb/MiddlewareNewsBrief-201101.html - [8] MPC (The Makefile, Project, and Workspace Creator)
http://ociweb.com/products/mpc - [9] OpenDDS
http://ociweb.com/products/opendds - [10] CORBA Technology and the JavaTM 2 Platform, Standard Edition
http://docs.oracle.com/javase/1.4.2/docs/guide/corba/index.html - [11] Naming Service
http://docs.oracle.com/javase/1.4.2/docs/guide/idl/jidlNaming.html - [12] java.util.logging.Logger doesn't respect java.util.logging.Level?
http://stackoverflow.com/questions/470430/java-util-logging-logger-doesnt-respect-java-util-logging-level - [13] Best Practices for Exception Handling
http://onjava.com/pub/a/onjava/2003/11/19/exceptions.html - [14] Commons CLI
http://commons.apache.org/cli/ - [15] How can a Java program get it's own process ID?
http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-process-id - [16] Play!
http://www.playframework.org/ - [17] Deploying Play Framework Scala Apps on the Cloud
https://www.youtube.com/watch?v=SHA5aITE7Ak