CUBA Introduction


  • What is CUBA
  • The HelloWorld Component
  • HelloWorld Stand-alone
  • HelloWorld as EJB 3.0
  • HelloWorld as EJB 2.1
  • HelloWorld as an interoperable AXIS WebService
  • HelloWorld as a WebService Component
  • Directory Structure
  • More Details

  • "Component-based software engineering is primarily concerned with three functions: [From: Component Based Software Engeneering - Putting the pieces together, Addison Wesley 2001]

    What is CUBA?

    Working with the J2EE and especially Enterprise JavaBeans shure has some light and some shadow. On the one hand the EJB component model provides a broad platform for the construction of scalable client-server architectures. On the other hand, using the naked standard by its own turns out to be very unhandy sometimes, in particular concerning development time and testability of components. An EJB is solely usable within a JEE aplication server which is not only a big disadvantage for automated unit testing but for its reuse in different non-managed environments in general. Now you might say: who will ever like to use the ugly EJB API anywhere else if it is not absolutely necessary? But since version 3 of the specification, the standard became pretty elegant and simple to use and therefore is now attractive for any kind of enterprise application and not only for JEE servers.

    If you would like to develop a component according to the EJB standard but test and run it without a J2EE server, of course someone has to take over the job of the EJB container. And this is exactly where CUBA comes into play. The tiny framework provides a mini container, which allows to use components as EJBs, AXIS webservices or in stand-alone applications without any additional programming work. Missing capabilities in weak containers - e.g. the pure JVM for a stand-alone application - are enriched by CUBA's runtime library without trying to make every system a fully-functional distributed and scalable J2EE environment. As a consequence, their apply some limitations in weak containers. However - and this is the important point! - these limitations are not related to the basic component and programming model but to non-functional aspects only. The coded parts are always the same, no matter in which environment the component is run.
    CUBA components are developed according to the EJB-3 model (with a few limitations) and are encapsulated in suitable technology adapters for the different supported containers by means of a simple code and descriptor generator. This allows to even run CUBA components in an EJB-2 container, providing J2EE developers with a very interesting and smooth migration path to EJB 3, in case they are stuck to EJB-2 technology for the next years.

    One model, generators, different target environments? Doesn't that sound a bit like model-driven architecture? Well, a bit indeed, with the difference that the basis is the code rather than an abstract model. This is why you don't need anything more but a JDK to work with CUBA. The following section explains the most importants aspects with a HelloWorld example which is also part of the CUBA distribution.

    Before getting started with the example, users of the CUBA 1 and 2 framework should know, that CUBA 3 is fully downwards compatible with older versions, i.e. all components developed on CUBA 1 and 2 can be used in CUBA 3 without code modification.  Only a re-generation of adapters is required.

    The HelloWorld Component

    The following sections explain the basic usage of CUBA by means of a Hello-World component as it is usual. The examples below and most other examples being part of the CUBA distribution use XML component descriptors to define a component's meta information. This allows to use the components also in Java 1.4 environments e.g. to generate EJB-2 components. All meta information may as well be defined by code annotations according to the EJB-3 standard if you are working in a Java 5 environment.

    A CUBA component usually consists of three parts to be provided by the developer: Components may have both internal and external interfaces. Internal interfaces can only be used within the component container having a call-by-reference semantic. External interfaces can also be accessed from clients outside the container and have a call-by-value semantic, not regarding if the interface will later be accessed through interprocess communication or from within the same process. The same component model applies everywhere. If you are an EJB expert you probably know what these interfaces are mapped to (remote and local interfaces of course). EJB-2 users may also ask "what's about the home interface?". Well, it's simply gone, because it doesn't make too much sense for SessionBeans - the basic model for CUBA. It is one of the things being generated behind the scenes to run the component in an EJB-2 container. The CUBA model never needed them (even in CUBA 1) and since version 3 they are also gone in the EJB standard.

    The coded parts of a HelloWorld component may look like this:
     
    package hello;
    import java.rmi.RemoteException;
    public interface HelloContractI {
      public String hello(String who) throws RemoteException;
    }

    package hello;
    import cuba.AbstractComponent;
    public class HelloImpl extends AbstractComponent implements HelloContractI {
      public String hello(String who) {
        System.out.println(who + " says Hello");
        return "Hello " + who;
      }
    }

    <?xml version="1.0" encoding="UTF-8"?>
    <component-jar>
      <component>
        <component-name>hello</component-name>
        <external-interface>hello.HelloContractI</external-interface>
        <component-class>hello.HelloImpl</component-class>
      </component>
    </component-jar>

    As you can see, the implementation class properly implements the component interface. Just like EJBs, CUBA components may have lifecycle methods being invoked by the container. In the example above a set of useful lifecycle methods are already implemented by the convenience class AbstractComponent, which is part of the framework. The deployment descriptor is very similar to what you might know from the EJB standard's ejb-jar.xml. It actually has the same purpose of specifying meta information that includes the specification about what is the interface, what is the implementation class and what is the component's name at least. You can provide a lot more information, but there are reasonable defaults applied if you do not. From a first sight you may find it inconvenient to write so many parts by hand and keep them consistent. On the other hand there are some good reasons to keep particular meta information in a separate descriptor - e.g. security restrictions or aspect-oriented instrumentations. However, according to the EJB 3 standard, users of a Java 5 environment may specify meta information in the code rather then in an XML descriptor. The HelloWorld component above could be annoted as follows to substitute the descriptor:

    package hello;
    import java.rmi.RemoteException;
    import cuba.annotation.External;

    @cuba.annotation.External
    public interface HelloContractI {
      public String hello(String who) throws RemoteException;
    }

    package hello;
    import cuba.AbstractComponent;
    import cuba.annotation.Stateless;

    @cuba.annotation.Stateless
    public class HelloImpl extends AbstractComponent implements HelloContractI {
      public String hello(String who) {
        System.out.println(who + " says Hello");
        return "Hello " + who;
      }
    }

    HelloWorld Stand-alone

    The hand-made parts must be compiled and are used as input for CUBA's generator tools. In the first step we are going to run the component in a stand-alone application, e.g. for test purposes. In this case we run the generators with option -w which stands for 'wired' and addresses a minimal embedded container, built for J2SE environments. This container basically does the same job as an EJB container in a JEE environment. But don't panic: As this container is reduced to the absolute minimum, it is nevertheless harmlessly small. The whole CUBA runtime library for all environments has a size of less than 150 kByte.
    The code generator is invoked as follows:

        java cuba.util.codegen.AdapterGenerator –cw META-INF/component-jar.xml

    The result is a file HelloImpl_WiredEx.java providing the adapter code to access the HelloWorld component in a stand-alone application. Of course, this file must be compiled too. In addition, we need a suitable descriptor for the embedded container being generated with a descriptor generator like this:

        java cuba.util.ddgen.DDGenerator –cw META-INF/component-jar.xml

    This will generate the file wired-jar.xml, which is created in sub directory META-INF. This step is also required if you are working with annotations because CUBAs mini container always works with descriptors to make it suitable for both Java 1.4 and Java 5 environments. If you use annotations rather than descriptors, you simply pass option -n and the component class names to the descriptor generator, e.g.

        java cuba.util.ddgen.DDGenerator –cwn hello.HelloImpl

    The output is exactly the same, because code annotations and XML descriptors are just two different but absolutely equivalent notation styles for the same meta information.

    Taking a look at the generated descriptor reveals nothing new, except that the generated adapter is declared here rather than the actual implementation class. But all that doesn't really matter as it is generated code which should be used as a black box. Having finished the steps described above, you should now have (among others) the following files at hand. The blue ones mark generated files:
     
    hello/
      HelloContractI.class
      HelloImpl.class
      HelloImpl_WiredEx.class
    META-INF/
      component-jar.xml
      wired-jar.xml

    The original code and all generated classes and adapters are now packed to a single Jfile by simply archiving the directories hello and META-INF:

        jar cvf hello.jar hello META-INF

    That's all, you now have a complete 'wired' component archive at hand. Application archives like EAR files in the J2EE world are not required for CUBA stand-alone applications (actually they are not even defined). Nevertheless you need an application descriptor which at least names the component archives to load. This descriptor has the predefined name wired-application.xml and must also be available in a directory META-INF. In addition to the EJB standard's application.xml, it also contains component references and the definition of DataSources. Of course all these things also have to be defined in a J2EE environment, but there is no standard for that. The application descriptor for the HelloWorld example looks like this:
     
    <?xml version="1.0" encoding="UTF-8"?>
    <wired-application>
      <modules>
        <wired>hello.jar</wired>
      </modules>
    </wired-application>

    The component archive, the application descriptor and the CUBA runtime library cuba.jar make up the container-side part of the application and all of them must be accessible through the classpath somehow. CUBA also provides its own specialized class loader to simplify path definitions, but that's just an optional extension (see chapter Class Loading). Using ordinary class loader mechanisms here has an important advantage for the development phase: If you specify your class folders before the component archives in the classpath, the classes are of course loaded from there rather than from the archives. I.e. rapid editing, compiling and testing without building archives and especially no deployment cycles as long as the component descriptors don't change.

    Finally, a client application must be implemented which calls the HelloWorld component. The property list in the example client below is not of interest yet for the stand-alone example, but is required when using the same client in EJB mode.
     
    public class HelloClient {
      public HelloClient() throws Exception {
        ClientContextI context = new WiredClientContext();
        HelloContractI hello = (HelloContractI)context.getComponent("hello");
        System.out.println(hello.hello("CUBA"));
      }

      public static void main(String[] args) throws Exception {
        new HelloClient();
      }
    }

    In a stand-alone application you have to instanciate a WiredClientContext and perform a lookup of the required component. The result is a proxy to a component instance.
    Calling the client causes both outputs - the one from the client and the one from the component - to be printed on the caller's console. The application is running locally in a single JVM and thus is very fast to develop and test. But this is more than a test environment. You can also use the components productively that way without any functional limitations, e.g. for batch processing or in a rich client or direct usage in a servlet engine.

    HelloWorld as EJB 3.0

    Now that you have finished the development of the HelloWorld component, you can also run it as an EJB in both EJB 2.1 and EJB 3 containers. For EJB 3 you just have to run the same generators as above with option -e 3.0 to generate EJB-compliant adapters:

        java cuba.util.codegen.AdapterGenerator –c -e 3.0 META-INF/component-jar.xml
        java cuba.util.ddgen.DDGenerator –c -e 3.0 META-INF/component-jar.xml

    Looking at the generator output we now find a class HelloImpl_EJB3 which is a stateless EJB 3 SessionBean delegating all method invocations to the actual implementation class. The descriptor generator actually produced nothing because 3.0 EJBs  are generated completely on a code annotation basis, not regarding if the CUBA meta data is provided by a descriptor or by annotations. This allows subsequent detail configuration by EJB deployment descriptors. After compilation, the directories contain the following files:
     
    hello/
      HelloContractI.class
      HelloImpl.class
      HelloImpl_EJB3.class
    META-INF/
      component-jar.xml

    The files above can be combined with the output from the wired mode into a single JAR file, resulting in a hybrid component archive which allows to run the component in both JEE and J2SE environments. To deploy the component on a JEE application server you should build an EAR file from the component JAR and the library cuba.jar. Alternatively you can add the packages cuba, cuba.util.common, and cuba.ejb from the CUBA library to the component JAR.

    To access the component, you can use the same application as before, you only have to substitute WiredClientContext with EJB3ClientContext. As another difference, the context requires a few properties to address the application server. Using a JBoss 4 server on the local workstation e.g. requires settings like this:

    public class HelloClient {
      public HelloClient() throws Exception {
        Properties props = new Properties();
        props.put(javax.naming.Context.PROVIDER_URL, "jnp://localhost:1099");
        props.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY,
                  "org.jnp.interfaces.NamingContextFactory");

        ClientContextI context = new EJB3ClientContext(props);
        HelloContractI hello = (HelloContractI)context.getComponent("hello");
        System.out.println(hello.hello("CUBA"));
      }

      public static void main(String[] args) throws Exception {
        new HelloClient();
      }
    }

    Invoking the client causes only the client's output to be printed in the caller's console. The component's output - which is now running as an EJB - appears in the console of the application server. The stand-alone component turned to a full-fledged EJB just by a few automated generator steps. Stresslessly developed, locally debugged and run as an EJB only when actually required. The development can be done without an application server.

    HelloWorld as EJB 2.1

    CUBA components can also be run in an EJB 2.1 container without limitations. Features like dependency injection and method interceptors which are available in the EJB 3 and the CUBA component model, are emulated in the generated adapters. For EJB 2.1 adapters, the generators must be run with option -e 2.1:

        java cuba.util.codegen.AdapterGenerator –c -e 2.1 META-INF/component-jar.xml
        java cuba.util.ddgen.DDGenerator –c -e 2.1 META-INF/component-jar.xml

    Having a look at the generator output we now find a standard-compliant EJB 2.1 deployment descriptor META-INF/ejb-jar.xml. The code generator produced a remote interface, a home interface and a SessionBean which serves as a delegate, a dependency injector, and a method interception dispatcher. After compilation, the directories contain the following files:
     
    hello/
      HelloContractI.class
      HelloImpl.class
      HelloImpl_EJB2.class
      HelloContractI_RemoteI.class
      HelloContractI_HomeI.class
    META-INF/
      component-jar.xml
      ejb-jar.xml

    A nasty detail of the EJB 2.1 world is the fact that almost every applicaton server requires an additional vendor-specific descriptor which often has to be edited manually. Therefore, the deployment looks very different in every application server. The following steps describe the process using the interactive deployment tool of the Sun Java System Application Server 8  which is the reference implementation for the J2EE 1.4 specification:

    1. Create a new application (Menu File->New Application...)
    2. Add the extended component archive hello.jar (Menu File->Add to Application...).
    3. This causes the JNDI name of the component to be set to the component name by default. This is a very nice convention because the same name is used by CUBA's embedded container.
    4. Add the CUBA runtime library (Button Add Library JAR... in Tab General).
    5. Deploy the application (Menu Tools->Deploy...). The server optionally returns a client library in this step which must be added at the very beginning of the classpath used to run a client.
    Users of the succeeding Sun Java System Application Server (Version 8.1 or higher) must use the server's web administration console for deployment. This is the only way to get a client library including static RMI stubs required to access the EJBs from a stand-alone client. The web console provides an appropriate check box in the deployment dialogs. The deployment tool does not!

    An EJB 2.1  application server is accessed through an EJB2ClientContext. For the Sun server, the Hello-World client looks like this:

    public class HelloClient {
      public HelloClient() throws Exception {
        Properties props = new Properties();
        props.put(javax.naming.Context.PROVIDER_URL, "iiop://localhost:3700");
        props.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY,
                  "com.sun.jndi.cosnaming.CNCtxFactory");

        ClientContextI context = new EJB2ClientContext(props);
        HelloContractI hello = (HelloContractI)context.getComponent("hello");
        System.out.println(hello.hello("CUBA"));
      }

      public static void main(String[] args) throws Exception {
        new HelloClient();
      }
    }


    HelloWorld as an interoperable AXIS WebService

    As the next step we will provide the component as an interoperable WebService in a Tomcat servlet engine based on an AXIS web application. Interoperablility requires a few common rules to follow concerning the method signatures and there apply some important restrictions - e.g. statefullness is not supported. However, this should not be discussed here - the HelloWorld component is fully suitable.
    Providing a CUBA component as an interoperable WebService requires additional adapters for the AXIS runtime environment. Strictly speaking, the generated code is not even specific for AXIS but follows the more common JAX RPC specification. Only the generated descriptors (.wsdd files) are specific for AXIS. The generators are invoked in a similar way as above, you just use another option:

        java cuba.util.codegen.AdapterGenerator –c -a 1.4 META-INF/component-jar.xml
        java cuba.util.ddgen.DDGenerator –c -a 1.4 META-INF/component-jar.xml

    The invokation of the descriptor generator requires a valid AXIS installation (version 1.1 or higher) with its libraries being accessible through the classpath. The resulting code must be compiled again and added to the component archive which should then include at least the following files:
     
    hello/
      HelloContractI.class
      HelloImpl.class
      HelloImpl_WiredEx.class
      HelloContractI_WSI.class
      HelloImpl_WS.class
    META-INF/
      component-jar.xml
      wired-jar.xml
      hello-deploy.wsdd
      hello-undeploy.wsdd
      hello.wsdl

    The descriptive files do not actually have to be archived, from a technical point of view. However, they can be seen as integral parts of a component module for the case that it should be completely self-contained and suitable for all environments supported by CUBA. As you can see above, the descriptor generator also produced a suitable WSDL file for the creation of stubs in any client environment willing to access the WebService. This is also required for Java clients, because in interoperable mode CUBA doesn't provide a client library for WebService access. By default, CUBA produces a WSDL in rpc/encoded style which can be changed to rpc/literal using option -s of the descriptor generator. However, rpc/encoded is the only style which is garuanteed to work in all AXIS versions - rpc/literal is recommended but should only be used with AXIS version 1.2 or higher.
    Taking a closer look at the generator output, it turns out that also wired-adapters have been generated. This has a simple reason: the WebService standard currently specifies WSDL and SOAP only, which is nothing more but a description format and a communication protocol like CORBA's IDL and IIOP. Absolutely nothing is mentioned about a component model or things like garuanteed container services. I.e. there is nothing more in a WebService environment than in an ordinary J2SE environment and therefore CUBA simply uses the same embedded container. In fact, the WebService adapter is just an additional layer built on top which performs the same as the Java client above: instantiation of a WiredClientContext, lookup of a component and delegation of all method invocations to that component. Accordingly, you also make use of the same wired-application.xml as in a stand-alone application. I.e. the container-side of the WebService application is already finished and needs to be deployed. Details concerning the deployment process can be found in the AXIS documentation. The following steps just give a short overview:

    1. Copy the component archive and the CUBA runtime library to the lib/ directory of the AXIS web application.
    2. Create a directory META-INF in the AXIS application's classes/ directory and copy the wired-application.xml to that directory.
    3. Announce the webservice by AXIS' AdminClient using the generated hello-deploy.wsdd file
    4. Restart the AXIS web application
    Done! If you are using the Hello-World example of the CUBA distribution, step 3 can be simplified by running "ant deploy" in the example directory. For access from a Java client, AXIS can generate appropriate stubs from the WSDL which was formerly generated by CUBA:

        java WSDL2Java -p axis META-INF/hello.wsdl

    The examples of the CUBA distribution provide an Ant target "stubs" to simplify that step. The following code shows a HelloWorld client accessing the component via AXIS. Again, only the client output appears on the caller's console. The component output is written on the server-side, and in a Tomcat engine by default appears in a file catalina.out in the installation's logging directory.
     
    package axis;

    public class AxisClient {

      public AxisClient() throws Exception {
        HelloContractIServiceLocator locator =
          new HelloContractIServiceLocator();
        HelloContractI hello = locator.gethello();
        System.out.println(hello.hello("CUBA"));
      }

      public static void main(String[] args) throws Exception {
        new AxisClient();
      }
    }

    HelloWorld as a WebService Component

    As mentioned above, the WebService standards don't define a component model. CUBA therefore provides an extended WebService mode which eliminates all interoperability limitations and provides the complete component model through a SOAP-based remote communication. In this case the data transfer between client and server is based on Java serialisation and base-64 coding leading to a WebService interface, which is not suitable for interoperable use. However, the remote access in this mode is completely encapsulated by a client-side library and generated client-side adapters so that the client code looks similar to the one for stand-alone and EJB mode. As the only difference you use the class WebserviceClientContext which gets passed the URL of the web application in which the WebServices are deployed:

    public class HelloClient {
      public HelloClient() throws Exception {
        Properties props = new Properties();
        props.put(WebserviceClientContext.WS_URL_PROPERTY,
                  "http://localhost:8080/axis");

        ClientContextI context = new WebserviceClientContext(props);
        HelloContractI hello = (HelloContractI)context.getComponent("hello");
        System.out.println(hello.hello("CUBA"));
      }

      public static void main(String[] args) throws Exception {
        new HelloClient();
      }
    }


    To tunnel the remote access through WebServices in this way, the generator must be invoked similarly as for interoperable WebServices. As an important different they must be run two times with a compilation step in between where the arguments "1" and "2" for the tunneling option -t are used to specify the generation phase.

        java cuba.util.codegen.AdapterGenerator –c -a 1.4 -t 1 META-INF/component-jar.xml
        java cuba.util.ddgen.DDGenerator –c -a 1.4 -t 1 META-INF/component-jar.xml
        javac hello/*.java
        java cuba.util.codegen.AdapterGenerator –c -a 1.4 -t 2 META-INF/component-jar.xml
        java cuba.util.ddgen.DDGenerator –c -a 1.4 -t 2 META-INF/component-jar.xml

    The results look like this:
     
    hello/
      HelloContractI.class
      HelloImpl.class
      Hello_WSTClient.class
      Hello_WSTI.class
      Hello_WSTIService.class
      Hello_WSTIServiceLocator.class
      HelloImpl_WiredEx.class
      HelloImpl_WST.class
      HelloSoapBindingStub.class
    META-INF/
      component-jar.xml
      wired-jar.xml
      Hello-deploy.wsdd
      Hello-undeploy.wsdd

    A WSDL as in the interoperable WebServie mode has not been generated as the structure of the WebServices is not suitable for using them outside CUBA. The names of the classes with prefix "Hello_" and the name of class HelloSoapBindingStub are derived from the component name and make up a client-side adaptor layer. The adapters implement the component interface HelloContractI and are responsible for the Java serialization and base-64 coding. The same takes place on server-side by class HelloImpl_WST which deserializes the client calls and forwards them to the adaptor HelloImpl_WiredEx which is already know from other modes above. I.e. CUBA's embedded container is also used in the mode on server-side. Together with the generated adapers, the servlet engine as runtime environment, and AXIS for the SOAP communication, CUBA makes up the world's smallest application server with a minimal footprint.

    Example code

    The code for the HelloWorld example above is available under examples/hello. The distribution also contains Ant scripts to simplify the adapter generation for the various modes. You just have to edit the file common.build.xml in the installation root directory to provide the build scripts with installation paths for AXIS and/or a JEE installation, depending on the generator modes you would like to work with.

    Directory and Package Structure

    The example above and most other examples included in the CUBA distribution are based on a simplified directory structure which is not representative for a real-world application. The following figure illustrates an example of a more realistic structure which should look reasonable to you if you already have experience with EJB development.
     
    myapp/
      src/
        module1/
          common/
            comp1_interface
            comp1_valuetypes
          internal/
            comp1_impl
          META-INF/
            component-jar.xml
        module2/
          common/
            comp2_interface
          internal/
            comp2_impl
            internal_comp3_interface
            internal_comp3_impl
          META-INF/
            component-jar.xml
      META-INF/
        wired-application.xml

    A mentionable application consists of multiple components which are structured in multiple modules. Each module requires its own meta information and should therefore have its own META-INF directory with a suitable component-jar.xml associated. A module's sources should be structured into different packages in order to separate the publically visible interfaces from the implementation and internal sub components. An application's meta information should not be mixed up with those of its modules and should therefore be located in an additional META-INF directory on the top level. The directory structure above is only an example, of course. However, no matter what you do, just ensure to structure you code in a way that it is suitable for a component-based architecture.

    More Details

    What has been demonstrated with a HelloWorld component above works for complex applications as well. CUBA supports almost all features of the EJB 3 SessionBean specification and the Java Persistence API, including even things like container-managed transactions, single-thread model, lifecycle management and so on. Further essential CUBA things to know about are
    Home Introduction Javadoc