Enterprise JavaBeans (EJBs) are important for developing mission-critical business applications in Java. However, business applications don't exist in isolation; today, companies require integrated applications. In addition, most companies already have existing applications written in programming languages other than Java. Therefore, integrating EJB-based solutions with existing applications is becoming increasingly important.

In this article, I show how to access EJBs from applications written in programming languages other than Java. More specifically, I discuss access to session and entity beans (which use the synchronous IIOP, or Internet Inter-ORB Protocol, communication) from a CORBA C++ client. I won't address message-driven beans, although you could access them too from other programming languages using MOM (message-oriented middleware)-compliant products.

RMI-IIOP

Session and entity beans use synchronous communication with remote method invocation (RMI). Java 2 Platform, Enterprise Edition (J2EE) 1.3 requires that Java clients use RMI-IIOP. RMI-IIOP uses CORBA's IIOP protocol, which makes RMI-IIOP CORBA compatible. In other words, clients not based on Java can use CORBA to communicate with EJBs.

To do this, you must use a J2EE 1.3-compliant application server. Previous EJB specifications did not require you to use RMI-IIOP. Instead, the application server used RMI-JRMP (Java Remote Method Protocol) or some proprietary protocol. You must also use an ORB (Object Request Broker) compliant with CORBA 2.3.1 or higher. Older CORBA versions did not implement the necessary specifications for RMI-IIOP interoperability, particularly the Objects by Value specification later integrated into the CORBA specification (see the Object Management Group's (OMG) CORBA/IIOP Specification 2.6, under the "Value Type Semantics" chapter) and the Java Language Mapping to OMG IDL specification.

Value-type semantics added the concept of transferring objects by value, introduced by RMI, to CORBA. CORBA initially didn't support this functionality; however, that concept is crucial for achieving Java/CORBA interoperability.

The Java Language Mapping to OMG IDL specification defines how Java interfaces map to the CORBA Interface Definition Language (IDL). This definition lets CORBA distributed objects access EJBs (and also RMI-IIOP distributed objects) that originally didn't have CORBA IDL. Specifically, the specification defines a Java RMI subset, called RMI/IDL, which you can map to IDL using IIOP (or, more generally, General Inter-ORB Protocol) as the underlying protocol for communication.

RMI/IDL

Many RMI/IDL data types conform to certain restrictions; we'll look at the most important ones. For more information, please see the Java Language Mapping to OMG IDL specification.

Table 1 shows the mapping of Java primitive data types to IDL.

Table 1. Java-to-IDL mapping

JavaOMG IDL
voidvoid
booleanboolean
charwchar
byteoctet
shortshort
intlong
longlong long
floatfloat
doubledouble

Java packages map to IDL modules. RMI/IDL remote interfaces map to the IDL interfaces with corresponding names. However, methods that use JavaBeans naming for read-write and read-only properties map to IDL attributes. I'll return to this later.

Java serializable objects map to CORBA value types. Value types provide pass-by-value semantics to CORBA. Value types are local and cannot be called remotely. They aren't registered with the ORB and don't require an identity, as their value is their identity. For more information, please refer to Professional J2EE EAI and the CORBA/IIOP Specification 2.6.

As I mentioned, all Java serializable objects, built-in and user-defined, map to value types. However, there are certain exceptions to the rule -- you'll encounter one when attempting to map java.lang.String, for example. If defined as a constant (final static), the object maps to IDL wstring. In all other cases, including method parameters and return values, the object maps to the CORBA::WStringValue value type. This value type is defined as a part of the CORBA module; the IDL definition is as follows:

valuetype WStringValue wstring;

This is equivalent to the following IDL definition:

valuetype WStringValue {
   public wstring data;
};

Keep in mind, however, that the first definition maps more cleanly to Java. Table 2 shows other special case mappings.

Table 2. Other important special case mappings

JavaOMG IDL
java.lang.Object::java::lang::_Object
java.lang.String::CORBA::WStringValue or wstring
java.lang.Class::javax::rmi::CORBA::ClassDesc
java.io.Serializable::java::io::Serializable
java.io.Externalizable::java::io::Externalizable
java.rmi.Remote::java::rmi::Remote
org.omg.CORBA.ObjectObject

Achieve integration

I'll return to value types later for user-defined classes and then for built-in classes, such as Vectors, Collections, and Enumerations. For now, let's look at the basic approach for CORBA and EJB integration. First, we'll need an EJB. In this first example, we use a simple session bean that only uses primitive data types as method parameters and return values. That way, we aren't forced to use value types. (Note: Accessing entity beans from CORBA clients mirrors the process of accessing session beans.)

This approach is the simplest; however, you cannot use it for more complex interfaces. Its benefit: you can use CORBA ORBs, which don't support value types. This is the case for some CORBA products, particularly for those based on languages other than C++.

For the examples, I'll use Inprise/Borland's VisiBroker for C++ 4.5 as the CORBA ORB and Microsoft's Visual C++ 5.0 (or higher) C++ compiler to compile the C++ client code. To deploy the example EJBs, I'll use BEA's WebLogic 6.1. You can download free trial versions of VisiBroker for C++ and WebLogic 6.1 from Resources.

You can use any CORBA 2.3.1 (or higher)-compliant ORB product that provides C++ mapping, a corresponding C++ compiler, and a J2EE 1.3-compliant application server. Theoretically, no code modifications are needed; however, minor modifications might be necessary if you use other products.

EJB session bean

Let's look briefly at a simple session bean, called CorbaEai. For our first example, the simple remote component interface contains a single method that sums two integer numbers:

package eai;
import java.rmi.RemoteException;
import javax.ejb.EJBObject;
public interface CorbaEai extends EJBObject {
  public int sum(int num1, int num2) throws RemoteException;
}

After you become familiar with the integration process, I'll show you how to extend the interface to include methods with user-defined and built-in objects.

To run these examples, you must deploy the session bean to an application server. This requires that you define the home interface, implement the bean implementation class, define the deployment descriptor, build the jar file, and finally deploy the EJB. I won't show these steps; however, you can download the file containing the full code example for WebLogic 6.1, jw-0329-corba.zip, from Resources below.

Develop the CORBA client

To develop the CORBA client, we'll perform the following steps:

  • Generate the IDL from session bean home and component remote interfaces
  • Simplify the generated IDL
  • Compile the IDL interfaces to the corresponding programming language -- C++ in our case -- to generate stubs and other necessary mappings
  • Find out how to use JNDI (Java Naming and Directory Interface) as a CORBA naming service
  • Develop the C++ client and the support for value types
  • Build the client

Generate the IDL

To generate the IDL from Java interfaces, you can use any tool that supports the Java Language Mapping to OMG IDL specification. Examples include:

  • rmic compiler, using the -idl option, bundled with Java SDK 1.3 and higher
  • java2idl, bundled with VisiBroker
  • rmic and ejbc (using -idl) compilers, bundled with BEA WebLogic

Other application servers provide similar tools. Be aware that generating the IDL interfaces from EJB interfaces is more complicated than generating them from RMI-IIOP interfaces. First, the EJB interfaces inherit from the EJBObject and EJBHome interfaces that define some base methods. Because the IDL interfaces also have to inherit from those interfaces, the tool must generate the IDL interfaces for the EJB interfaces as well. Second, the methods in EJB home interfaces throw at least CreateException, so the tool must map this exception, as well as any other user-defined exceptions, to the IDL.

You can use WebLogic's ejbc compiler, which provides all necessary IDL definitions for the enterprise bean. Suppose we built our CorbaEai session bean and generated the eai.jar file. To use ejbc to generate IDL files, we must use an additional -idl switch, which we use to tell the ejbc to generate the IDL interfaces. We also use the -idlDirectory switch to specify the directory in which to store the generated IDL files. First, we have to set WebLogic's environment variables. Then, we can use the following command:

java weblogic.ejbc eai.jar eaiC.jar -idl -idlDirectory ./idl

The ejbc compiler creates IDL files in the ./idl directory, generating 17 IDL files.

Simplify the generated IDL

Compiling and implementing all the generated interfaces and value types in C++ is time consuming. However, we won't need all the generated interfaces because we won't use certain methods. Therefore, we can save a lot of work by manually simplifying the generated IDLs.

For this first example, suppose we don't invoke methods on the EJBObject and EJBHome interfaces, and we don't need the EJBMetaData. We won't need a specialized RemoveException, so we can leave out the RemoteException and RemoveEx IDL interfaces. We can also eliminate all the IDL interfaces: java.io, java.lang, and java.rmi.

To simplify the IDL even further, we can merge the IDL interfaces into one file. Therefore, after simplifying the IDL, we get the following file, named CorbaEai.idl:

#include "orb.idl"
module javax {
  module ejb {
    valuetype CreateException {
       #pragma ID CreateException "RMI:javax.ejb.CreateException:FD98A9711F66DF7F:575FB6C03D49AD6A"
    }; 
  };
};
module javax {
  module ejb {
    exception CreateEx {
       CreateException value;
    };
  };
};
module eai {
  interface CorbaEai {
    long sum( in long arg0,  in long arg1);
    #pragma ID CorbaEai "RMI:eai.CorbaEai:0000000000000000"
   };
};
module eai {
  interface CorbaEaiHome {
    ::eai::CorbaEai create() raises (::javax::ejb::CreateEx);
    #pragma ID CorbaEaiHome "RMI:eai.CorbaEaiHome:0000000000000000"
   };
};

Compile the IDL interfaces

Next, we map the IDL interfaces to the client's programming language. We use C++ for the client, so let's use the idl2cpp compiler included with VisiBroker for C++ 4.5. (You can use any IDL-to-C++ compiler, as long as it complies with CORBA 2.3.1 or higher.)

If you look closer at the IDL definition, you'll see that it includes orb.idl. In our example, we will use the orb.idl file that comes with VisiBroker. Note that, in this case, VisiBroker is installed in the /prg/vbroker folder; therefore, we must specify the /prg/vbroker/idl directory in which the orb.idl file resides in our command. We use the -I switch for the idl2cpp compiler.

We force the IDL compiler to use strict code generation with the -strict switch. Such code corresponds to the CORBA specification; otherwise, VisiBroker generates additional methods. We don't need the server side, so we suppress its generation (-no_servant switch). Additionally, we direct that the idl2cpp compiler should implement IDL modules as C++ namespaces (-namespace switch). Finally, we define that the file suffix should be cpp instead of cc, which is VisiBroker's default (-src_suffix switch).

The command line looks like this:

idl2cpp -strict -no_servant -namespace -I/prg/vbroker/idl -src_suffix cpp CorbaEai.idl

Use JNDI as a CORBA naming service

Before we develop the C++ client, think about how it obtains the initial reference to the session bean home interface. CorbaEaiHome is registered with the application server's (WebLogic in our case) JNDI, so the C++ client must access the JNDI naming service.

Some application servers, like BEA WebLogic, provide an interface compliant with the CORBA naming service to access JNDI. To test this, we use the WebLogic host2ior utility, which outputs the IORs (Interoperable Object References) for the naming service used with IIOP and IIOP/SSL (Secure Socket Layer). For our example's purposes, we are only interested in the nonsecure IIOP.

The CORBA naming service is realized as a CORBA distributed object with a standardized interface, technically the same as other CORBA objects. Therefore, we could use the IOR directly to connect to the JNDI.

A better way, however, gets a reference by resolving the initial references with a keyword NameService. In this case, we specify at the start of the client the service provider that client should use. We do this with a command-line switch that I'll show later.

Develop the C++ client

We develop a simple C++ client that invokes the sum() method on the stateless session bean CorbaEai. I assume you are familiar with CORBA. You can find details of developing CORBA C++ clients in Professional J2EE EAI.

First, we declare the necessary includes, followed by the main() method:

#include "CosNaming_c.hh"
#include "CorbaEai_c.hh"
USE_STD_NS
int main(int argc, char* const* argv)
{
Page 2 of 3

Then we initialize the ORB, connect to the naming service, and obtain the initial context:

  try {
    CORBA::ORB_var orb = CORBA::ORB_init(argc, argv);
    cout << "Accessing the naming service" << endl;
    CORBA::Object_ptr o = orb->resolve_initial_references("NameService");
    cout << "Getting the naming context" << endl;
    CosNaming::NamingContext_var context = CosNaming::NamingContext::_narrow(o);

Next, we create a name in an appropriate format for the CORBA naming service, under which our EJB home interface is stored in the JNDI. Note that we use the JNDI name corba-eai:

    CosNaming::Name name;
    name.length(1);
    name[0].id = CORBA::string_dup("corba-eai");
    name[0].kind = CORBA::string_dup("");

Next, we resolve the name and narrow the object reference to our session bean's CorbaEaiHome home interface:

    cout << "Resolving the home interface" << endl;
    CORBA::Object_var object = context->resolve(name);
    eai::CorbaEaiHome_var ceaihome = eai::CorbaEaiHome::_narrow(object);

We now have a reference to the home interface, so we can create a new instance of the session bean and invoke a method on it:

    cout << "Creating a new instance" << endl;
    eai::CorbaEai_ptr ceai = ceaihome->create();
    CORBA::Long r = ceai->sum((CORBA::Long)15,(CORBA::Long)20);
    
    cout << "Result: " << r << endl;
  }
  catch(const CORBA::Exception& e) {
    cout << "Exception: " << e << endl;
  }
  return 0;
}

Build and run the client

To build the C++ client, we use Microsoft Visual C++ 5.0 (or higher) command-line tools. Before we start, be sure you have set the environment variables for Visual C++. The easiest way is to use the vcvars32.bat batch file, which you can find in the Visual C++ installation bin subdirectory. Below is the relevant command line to compile and link the C++ client. Again, VisiBroker should be installed in the /prg/vbroker folder:

CL /MD /DTHREAD -DWIN32 /GX /DSTRICT /DALIGNED /DVISIBROKER 
/DMSVCUSING_BUG /DMSVCNESTEDUSING_BUG -I/prg/vbroker/include -
I/prg/vbroker/include/stubs CorbaEai_c.cpp Client.cpp /link 
/out:Client.exe /LIBPATH:/prg/vbroker/lib

To run the example, do the following:

  • Start the application server (WebLogic, in our case)
  • Deploy the session bean CorbaEai to the application server
  • Start the C++ EJB client, using the command line shown below:
    Client -ORBInitRef
    NameService=iioploc://localhost:7001/NameService
    

Notice that in this example the EJB application server resides on the same computer as the client. To enable remote communication, specify an actual name or IP address instead of localhost.

Value types

This example is far from perfect. It doesn't implement the value types for Java serializable objects, which means that we cannot reliably catch remote exceptions (because they are mapped as value types, as the IDL shows). In addition, I limited the first example to methods that use primitive data types only.

To understand how CORBA handles Java serializable classes, you must look at CORBA value types. Java serializables namely map to value types. Every Java serializable object passed as a parameter, return value, or exception to/from a CORBA client has to be re-implemented in the client's programming language, C++ in our case. This requires significant effort. Unfortunately, I am not aware of any CORBA implementations that provide full implementations for Java built-in types, such as Remote, Throwable, EJBObject, and EJBHome. As far as I know, only the IBM WebSphere Application Server provides a value type library that contains C++ value type implementations for some commonly used Java types, such as Integer, Float, Vector, Exception, and OutputStream. According to the WebSphere Application Server 4.0 documentation, the value type library supports the WebSphere C++ ORB only.

For each value type, the IDL to C++ (idl2cpp) compiler generates a pointer type definition _ptr and a _var class for each type. The _var class automatically manages the memory for the dynamically allocated object reference. A casting operator also lets you assign a _var to a _ptr. Also note that the original Java constructors do not map to IDL. However, when IDL maps to C++, constructors become the init methods on the factory classes.

To implement a value type (in C++, for example), you:

  • Implement a value type class, which should inherit from the value type base class (generated by the IDL compiler) and the default value reference count base class. For value types, you must implement reference counting manually.
  • Implement the factory class with factory methods.
  • Register the factory with the ORB.

Let's look at the example for the CreateException value type. First, we declare the implementation class CreateExceptionImpl:

class CreateExceptionImpl : public virtual
::javax::ejb::OBV_CreateException, 
                            public virtual 
CORBA::DefaultValueRefCountBase 
{

We must provide at least the implementation for the constructor and the _copy_value() method, which simply returns a new value type class instance:

  public:
  CreateExceptionImpl () : OBV_CreateException () {}
  virtual ~CreateExceptionImpl () {}
  CORBA::ValueBase* _copy_value(){
    return new CreateExceptionImpl();
  }

We can also provide the implementation for other methods, such as message(), localizedMessage(), and toString():

  CORBA::WStringValue* message() {
    return new 
CORBA::WStringValue(CORBA::wstring_dup(L"javax::ejb::CreateException"))
;
  }
  CORBA::WStringValue* localizedMessage() {
    return new 
CORBA::WStringValue(CORBA::wstring_dup(L"javax::ejb::CreateException"))
;
  }
  CORBA::WStringValue* toString() {
    return new 
CORBA::WStringValue(CORBA::wstring_dup(L"javax::ejb::CreateException"))
;
  }
};

Next, we define the factory class CreateExceptionFactory. We should provide at least the implementation for create_for_unmarshal() methods; however, we should also implement the create__() method. Both methods simply return a new implementation class instance:

class CreateExceptionFactory : public 
::javax::ejb::CreateException_init 
{
public:
  CreateExceptionFactory() {}
  virtual ~CreateExceptionFactory() {}
  javax::ejb::CreateException* create__() {
    return new CreateExceptionImpl();
  }
  CORBA::ValueBase* create_for_unmarshal() {
    return new CreateExceptionImpl();
  }
};

Finally, we register the factory with the ORB. For this, the ORB interface provides a method register_value_factory(), which accepts the repository ID and the factory instance. We can get the repository ID from the IDL (it is defined using the #pragma directive). The example below shows how to register the CreateException:

    orb->register_value_factory("RMI:javax.ejb.CreateException:
FD98A9711F66DF7F:575FB6C03D49AD6A", (CORBA::ValueFactory)new CreateExceptionFactory);

Similarly, we implement all value types, which we use in the CORBA client. Although this looks complicated, you'll see that the procedure is not difficult and you can define template classes to automate it.

Develop a more advanced CORBA client

Now that you are familiar with CORBA value types, you can develop a CORBA client for EJBs with more complicated interfaces.

In this example, we extend our CorbaEai session bean's interface with several methods that use user-defined and built-in Java objects. First, we add a method sumObj() to sum up two Integer objects:

  public Integer sumObj(Integer num1, Integer num2) throws RemoteException;

Then we add a getter/setter pair for a user-defined serializable class, called MyType:

  public MyType getMyType() throws RemoteException;
  public void setMyType(MyType mt) throws RemoteException;

The MyType class is simple; it contains two attributes (for simplicity, I have not shown the corresponding getter/setter methods):

package eai;
import java.io.Serializable;
public final class MyType implements Serializable {
   public int a;
   public double b;
   
}

Finally, we add a method that returns a Java vector (getVector()) and a method that returns a Java collection (getCollection()):

  public Vector getVector() throws RemoteException;
  public Collection getCollection() throws RemoteException;

These methods represent typical methods found in most EJB-based applications. The complete example can be found in jw-0329-corba.zip.

The process of developing the CORBA client resembles that of the first example. First, we generate the IDL interfaces. We can use the same command as before; however, we won't simplify the IDL this time.

Please notice that the generated IDL interface for the CorbaEai session-bean remote component interface follows the RMI/IDL rule for mapping property accessor methods. Methods that use the JavaBeans naming for read-write and read-only properties do not map to IDL operations, but rather to IDL attributes. In our example, we have the getMyType() and setMyType() read-write property methods and the getVector() and getCollection() read-only methods. These methods map to the following IDL attributes:

    attribute ::eai::MyType myType;
    readonly attribute ::java::util::Vector vector;
    readonly attribute ::java::util::Collection collection;

To access these attributes from C++ (or some other language), you must know how they map from IDL to C++ (or whatever language you choose). For C++, each attribute maps to two overloaded C++ functions, one to set and one to get the attribute value, with the same name as the attribute. A read-only attribute maps to the get function only. For more information on IDL-to-C++ mapping, please refer to OMG's C++ Language Mapping.

Other methods, such as sum() and sumObj(), map on a one-to-one basis:

    long sum( in long arg0,  in long arg1);
    ::java::lang::Integer sumObj( in ::java::lang::Integer arg0,  in 
::java::lang::Integer arg1);

Next, we compile the IDL interfaces to C++ using the VisiBroker idl2cpp compiler. You can compile all the generated IDL files one by one, or you can gather all generated IDL files into one file and then compile that file.

Now we can code the C++ client. First, we provide the value type implementations (value type classes and factory classes), as I explained before, for all relevant value types. In our example, we define the implementations for the following value types:

  • MyType
  • Integer
  • Vector
  • CreateException
  • RemoveException

We also register those value types with the ORB.

You might wonder why we didn't define a value type for the Java Collection. As you know, Collection is an interface, implemented by the AbstractCollection class, which provides a skeletal implementation. The Java SDK provides implementations for more specific interfaces, such as List. To understand the class and interface hierarchy, please examine the following UML diagram.

Collection interface and implementation classes hierarchy

You can see that Vector is a possible implementation for the Collection interface. Therefore, we use it to access the Collection.

The actual C++ code to invoke the CorbaEai session bean methods is relatively simple. To invoke the sumObj() method, we first create two Integer value types and insert the values. Then we invoke the method and output the result:

    ::java::lang::Integer_var javaInt1 = new IntegerImpl();
    ::java::lang::Integer_var javaInt2 = new IntegerImpl();
    javaInt1->value(15);
    javaInt2->value(20);
    ::java::lang::Integer_var javaIntR = ceai->sumObj(javaInt1, 
javaInt2);
    cout << "sumObj(): " << javaIntR->value() << 
endl;

To invoke the getMyType() and setMyType() methods, recall that we mapped them to the IDL attribute. Therefore, we use the C++ overloaded method myType(). The following code invokes the getMyType() method on our session bean:

    ::eai::MyType_var mt = ceai->myType();
    cout << "getMyType(): " << mt << endl;

To invoke the setMyType() method, we first create a MyType instance and fill in the necessary values; only then do we invoke it:

    cout << "setMyType()... " << endl;
    ::eai::MyType_var mt2 = new MyTypeImpl();
    mt2->a((CORBA::Long)9);
    mt2->b((CORBA::Double)9.9);
    ceai->myType(mt2);

Notice that the method names have changed because we used JavaBeans naming (get/set methods). Otherwise, the methods would map without changes.

The same is true for the getVector() method, which we mapped to a read-only IDL attribute. Therefore, we access it from C++ as vector():

    ::java::util::Vector_ptr vec = ceai->vector();    
    cout << "getVector(): " << vec << endl;

Perhaps the most interesting part of this whole process is how we access the Collection. After invoking the getCollection() session bean method (which in C++ is called collection()), we explicitly downcast it to a Vector:

    ::java::util::Collection_ptr coll = ceai->collection();    
    ::java::util::Vector_ptr cvec = 
::java::util::Vector::_downcast(coll);
    cout << "Collection: " << cvec << endl;

Finally, notice that we can use the remote component interface methods, inherited from EJBObject. The most important such method is remove(). Therefore, we can conclude our example with the following C++ line:

    ceai->remove();
Page 3 of 3

Interoperability issues

To actually run the described example with VisiBroker for C++ 4.5 and BEA WebLogic 6.1, you unfortunately need a small workaround. Due to incompatibilities between VisiBroker and WebLogic, which both use GIOP/IIOP 1.2 by default, you must force them to use GIOP/IIOP 1.1. You can achieve this by adding the DefaultGIOPMinorVersion="1" attribute to the <Server> tag in the WebLogic config.xml file.

Unfortunately, this example once again shows that the interoperability between CORBA products from different vendors in practice still needs improvement.

Ideal integration

In this article, I've shown that interoperability between CORBA and EJBs is possible. However, developing an EJB client in a programming language other than Java is not as easy as you might hope, particularly if you use methods that accept or return objects and user-defined types. This is the case for most real-world EJB components, so you must use CORBA value types. The major problem is that you must implement Java built-in types as well. I hope CORBA vendors provide value-type implementations soon, as IBM WebSphere Application Server does. I also hope interoperability issues between different COBRA products resolve soon.

At any rate, the interoperability between EJBs and CORBA is important and useful in most cases where you need to integrate existing non-Java-based applications with newly developed J2EE-based solutions.

Matjaz B. Juric holds a PhD in computer and information science and currently works at the University of Maribor in Slovenia. He has been involved in several large-scale object technology projects. In cooperation with the IBM Java Technology Centre, he worked on performance analysis and optimization in RMI-IIOP development, an integral part of the Java 2 Platform. Matjaz has coauthored Professional J2EE EAI and Professional EJB, both from Wrox Press, and has published a chapter in More Java Gems, from Cambridge University Press. He has published in journals and magazines, such as Java Developer's Journal and Java Report, and presented at conferences such as OOPSLA (Object-Oriented Programming, Systems, Languages, and Applications Conference), SIGS Java Development, Wrox Conferences, and others.

Learn more about this topic