Custom OpenEJB Services -- A basic (connector) service example

|  |  |  | OpenEJB OpenJMS OpenORB Castor Tyrex |

Main
Welcome!
Download
Mailing Lists
The Team

Users
Quickstart
Hello World!
CMP Example
CMP Guide
Deploy
Startup
Validation
Configuration
Support
Request Feature
FAQ

Servers
Local Server
Remote Server
Tomcat

Integrators
Why OpenEJB
Overview
Design
Specification
Presentation

Developers
Custom Services
Release Plan
Source Code
SourceForge

Custom OpenEJB Services A basic (connector) service example

1Abstract
2Introduction to OpenEJB services
3Developing a service
4Packaging a service
5Deploying a service
6Using a service
7What if it didn't work

## Abstract

This an advanced document and is not aimed begining or intermediate OpenEJB users. OpenEJB users should be able to configure any JDBC datasource using the built in JDBC Connector that ships with OpenEJB. To learn how to configure a JDBC datasource. follow this link.

The aim of this document is to show OpenEJB architects what needs to be done in order to deploy a J2EE Connector service in OpenEJB. The J2EE Connector Architecture allows virtually any type of connection-oriented resource to be plugged into a J2EE compliant container. As mentioned, OpenEJB already comes with a JDBC connector, but other implementations are possible such as a JavaMail connector or a Java Message Service (JMS) connector.

The goal of the OpenEJB Service Architecture is to make the lives of users much easier. With the services packaged into jars, each with their own service-jar.xml, it is possible to package and deploy services like components, just as you can with EJBs. Each service-jar.xml is synonymous to the ejb-jar.xml of a set of EJBs. It contains all the information needed for OpenEJB to load and use the service. It also contains the default configurations of all the services in the jar so that they can be simply dropped in and ran out-of-the-box.

It is possible to plug-in other types of services, such as new containers, but this document uses a sample connector service to illustrate the ins and outs of creating and deploying a custom service into OpenEJB.

Firstly, we take a deeper look at how OpenEJB configuration file -- openejb.conf -- looks like. It is strongly encouraged to read the Configure the data source section in Hello OpenEJB World! - A basic CMP entity bean example as you can find an introduction to it over there.

Having done that, we move on to discussing different types of OpenEJB services. One type is playing a great role in our example - the connector service. To develop our custom service we leverage what is already provided in OpenEJB - the existing "Default JDBC Service". We don't want to reinvent the wheel, do we ? So, we, real Java programmers, will subclass the service's class to give you an insight on how to introduce a new functionality in the already existing OpenEJB services. Perhaps, some time you will create a brand new production-ready service for OpenEJB. That would be great.

The last section tests it out with the CMP example from Hello OpenEJB World! - A basic CMP entity bean example. We shall see two services on the list of the services to choose from while deploying the bean -- one of them will be our newly created JDBC connector service.

## Introduction to OpenEJB services

Here is a part of OpenEJB configuration file's XML Schema. An XML Schema is like a DTD, but capable of doing much more validation than a DTD. It shows the root element of the configuration file OpenEJB will read at startup.

      <xsd:element name="openejb">
        <xsd:complexType mixed='true'>
          <xsd:sequence>
            <xsd:element ref="Container" maxOccurs="unbounded"/>
            <xsd:element ref="JndiProvider" minOccurs="0" maxOccurs="unbounded"/>
            <xsd:element ref="SecurityService" minOccurs="0" maxOccurs="1"/>
            <xsd:element ref="TransactionService" minOccurs="0" maxOccurs="1"/>
            <xsd:element ref="ConnectionManager" minOccurs="0" maxOccurs="1"/>
            <xsd:element ref="ProxyFactory" minOccurs="0" maxOccurs="1"/>
            <xsd:element ref="Connector" minOccurs="0" maxOccurs="unbounded"/>
            <xsd:element ref="Resource" minOccurs="0" maxOccurs="unbounded"/>
            <xsd:element ref="Deployments" minOccurs="0" maxOccurs="unbounded"/>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>

As you can see there are 9 different elements OpenEJB configuration file can use. 7 of them represent service-specific elements -- more about those later. Some of them may appear more than once (e.g. Connector) and some at least once (e.g. SecurityService).

The document will cover the basics about the Connector service type.

So, let's enter the scene by examining the Connector element. Here is OpenEJB's XML Schema part of the element:

      <xsd:element name="Connector">
        <xsd:complexType mixed='true'>
          <xsd:attribute name="id" type="xsd:string" use="required"/>
          <xsd:attribute name="jar" type="JarFileLocation"/>
        </xsd:complexType>
      </xsd:element>

      <xsd:simpleType name="JarFileLocation">
        <xsd:restriction base="xsd:string">
          <xsd:pattern value=".*\.jar$"/>
        </xsd:restriction>
      </xsd:simpleType>

As the above XML Schema shows the Connector element in OpenEJB configuration file must consist of at least one attribute id. The id attribute is in fact a stringified pointer to the appropriate declaration in one of the deployed services' configuration. Strictly speaking, it is the name which must appear in a service's deployment descriptor - we will talk about it later.

The another attribute - jar - is exactly the path to the jar file where OpenEJB expects to find the service's deployment descriptor (with the id name) and classes as well as other resources required by the service. The service's deployment descriptor is an XML file which is required to be available at the following paths within the service's jar file:

| META-INF/service-jar.xml
| service-jar.xml

NOTE

Although all of the above paths are supported, it is highly recommended to use the first one - META-INF/service-jar.xml. The other is still being handled to retain backword-compatibility, but may disappear in future OpenEJB releases.

In its simplest form the deployment descriptor would look like as follows:

/META-INF/service-jar.xml

    <?xml version="1.0"?>

    <ServiceJar>
      <ServiceProvider id="PostgreSQL 7.2.1 Database"
          provider-type="Connector"
          class-name="org.acme.service.connector.PostgreSQLConnectionFactory"
      />
    </ServiceJar>

Here is a description of the simplest deployment descriptor's elements and attributes:

| id - the name of a service; that is exactly the same name which appears in OpenEJB configuration file as a value of a service's id a ttribute

provider-type - the type of a service; it may be assigned to one of the following types:

| Container
| Proxy
| Security
| Transaction
| Connector
| ConnectionManager
| JNDI

| class-name - the name of a class that constitutes the service; the above provider-type's mandate what interfaces the class must implement to become a valid candidate

OpenEJB comes with several services available. Here is the list of the services' ids (stringified identifiers) with their short description:

| Default CMP Container - the default Container-Managed Persistence EntityBean Container; based on Castor
| Default BMP Container - the default Bean-Managed Persistence EntityBean Container
| Default Stateless Container - the default Stateless SessionBean Container
| Default Stateful Container - the default Stateful SessionBean Container
| Default JDK 1.2 ProxyFactory - the default Proxy Factory implementation for JDK 1.2
| Default JDK 1.3 ProxyFactory - the default Proxy Factory implementation for JDK 1.3
| Default Security Service - the default Security Service implementation
| Default Transaction Manager - the default Transaction Manager implementation
| Default JDBC Database - the default JCA ManagedConnectionFactory for JDBC
| Default Local TX ConnectionManager - the default JCA ConnectionManager

The services' default configuration file is included as default.service-jar.xml in the %OPENEJB_HOME%/lib/openejb-x.x.x.jar file.

## Developing a service

After the introductory material let's delve our hands into the real stuff -- programming. The following steps will lead you through the bumpy road of OpenEJB services programming making it easy like never before.

The service you are about to create is a Connector service named "PostgreSQL 7.2.1 Database". The purpose of the service is to carry out our custom name (so we could name it as we want), handle an additional service-specific parameter and finally refuse to connect to other databases than PostgreSQL unless the parameter mandates otherwise. Quite enough as a starter -- after all, this was meant to be a basic service example.

So, let's name our brand new service "PostgreSQL 7.2.1 Database". As it has been described in the previous section, the name shall appear in the service's deployment descriptor in META-INF/service-jar.xml. It is simply a value of the id attribute of the ServiceProvider's element.

The service's functionality is to provide PostgreSQL connections. It does represent a Connector service; thus, the provider-type is "Connector".

The last attribute to assign a value to is class-name. The class must reflect the functionality OpenEJB expects from connectors, i.e. it must implement javax.resource.spi.ManagedConnectionFactory class. Our class extends the already available OpenEJB class - org.openejb.resource.jdbc.JdbcManagedConnectionFactory, which represents "Default JDBC Database" service.

All the text between the <ServiceProvider> and </ServiceProvider> tags of the service's declaration in the service-jar.xml will be intepreted as properties and will be passed into the service by OpenEJB as an instance of java.util.Properties when OpenEJB starts up. Service providers should use this area to set default values for all the configurable properties the service has. The format of the text between the <ServiceProvider> and </ServiceProvider> tags can be any text valid for a Java propeties file. The properties themselves can be anything that the Service provider wants.

Users can easily reconfigure the service by specifying new values for the properties in the openejb.conf. This overrides the properties in the service's service-jar.xml and allows users to reconfigure the service without having to mess with the service's jar and service-jar.xml file.

In the case of our Connector, users can override the default property values we set in our service-jar.xml by going to the <Connector> and </Connector> tags in their openejb.conf file and typing in new properties and values.

An elegant aspect of this approach is that users can create as many "profiles" or configurations of OpenEJB and it's services by simply creating an openejb.conf file for each. For example, a user might have the following config files in their conf/ directory:

| development_openejb.conf
| test.openejb.conf
| my.production-openejb.conf
| no-cmp-container.openejb.conf

Create the directory where the service's files are located.

C:\> mkdir C:\my\app\org\acme\service\connector
C:\> cd C:\my\app

In your favorite editor, create the file below.

C:\my\app\org\acme\service\connector\PostgreSQLConnectionFactory.java

    package org.acme.service.connector;

    import org.openejb.resource.jdbc.JdbcManagedConnectionFactory;

    import javax.resource.ResourceException;
    import javax.resource.spi.ConnectionRequestInfo;
    import javax.resource.spi.ManagedConnection;
    import javax.resource.spi.ResourceAdapterInternalException;
    import javax.security.auth.Subject;
    import java.util.Properties;

    public class PostgreSQLConnectionFactory extends JdbcManagedConnectionFactory
    {
       public static final String STRICT_PROPERTY_NAME = "strict";

       /**
        * Allow only PostgreSQL drivers,
        * i.e. those that belong to org.postgresql package
        */
       private boolean strict = true;

       public PostgreSQLConnectionFactory()
       {
          System.out.println( "+++ Message from PostgreSQLConnectionFactory.<init>" );
       }

       public void init( Properties props )
               throws ResourceAdapterInternalException
       {
          System.out.println(
              "+++ Message from PostgreSQLConnectionFactory.init( Properties props ) method" );

          super.init( props );

          setStrict( props.getProperty( STRICT_PROPERTY_NAME ) );
       }

       public void setStrict( String strict )
       {
          this.strict = !"false".equalsIgnoreCase( strict );
          System.out.print( "+++ Setting up [" + STRICT_PROPERTY_NAME + "]");
          System.out.println( " to [" + this.strict + "]" );
       }

       public ManagedConnection createManagedConnection( Subject subject,
                                                         ConnectionRequestInfo cxRequestInfo )
               throws ResourceException
       {
          System.out.println( "+++ createManagedConnection() is being called" );

          // if strict is not set explicitly or it sets to true
          // and the driver doesn't belong to org.postgresql package
          // refuse the request for creating a new connection
          if ( this.strict && !getJdbcDriver().startsWith( "org.postgresql" ) )
          {
             throw new ResourceAdapterInternalException(
                     "", "+++ The driver " + getJdbcDriver() + " is not PostgreSQL driver" );
          }
          System.out.print("+++ createManagedConnection() - the driver ");
          System.out.println( getJdbcDriver() + " passed the test" );

          // the method returns a new physical connection
          return super.createManagedConnection( subject, cxRequestInfo );
       }
    }

C:\my\app> mkdir META-INF

In your favorite editor, create the file below.

C:\my\app\META-INF\service-jar.xml

    <?xml version="1.0"?>

    <ServiceJar>
      <ServiceProvider id="PostgreSQL 7.2.1 Database"
            provider-type="Connector"
            class-name="org.acme.service.connector.PostgreSQLConnectionFactory"
      />
    # The default properties of the connector
    JdbcDriver = org.postgresql.Driver
    JdbcUrl = jdbc:postgresql://localhost/mydb
    UserName = username
    Password = password
    strict = true
    </ServiceJar>

## Packaging a service

This step boils down to compiling the class and creating the jar file using the following commands:

C:\my\app> set CP=%OPENEJB_HOME%\lib\ejb-2.0.jar
C:\my\app> set CP=%CP%;%OPENEJB_HOME%\lib\jaas_1.0.jar
C:\my\app> set CP=%CP%;%OPENEJB_HOME%\lib\jca_1.0.jar
C:\my\app> set CP=%CP%;%OPENEJB_HOME%\dist\openejb-0.8.1.jar
C:\my\app> javac -d . -classpath %CP% org\acme\service\connector\*.java
C:\my\app> jar -cvf postgresql-service.jar org\acme\service\connector\*.class META-INF

## Deploying a service

Before using a service in OpenEJB it has to be deployed first. The process involves modyfing OpenEJB configuration file, i.e. openejb.conf. The following file is the one being used in Configure the data source section in Hello OpenEJB World! - A basic CMP entity bean example with the necessary changes pertaining to our service (note the id attribute has changed as well as the jar attribute and struct parameters have been added).

Create the file in the conf directory.

C:\my\app> mkdir conf

In your favorite editor, create the file below.

C:\my\app\conf\openejb.xml

    <?xml version="1.0"?>

    <openejb>
      <Container id="Default CMP Container" ctype="CMP_ENTITY">
        Global_TX_Database  c:/my/app/conf/postgresql.cmp_global_database.xml
        Local_TX_Database   c:/my/app/conf/postgresql.cmp_local_database.xml
      </Container>
      <Deployments jar="c:/my/app/employee.jar"/>
      <Connector id="PostgreSQL 7.2.1 Database" jar="c:/my/app/postgresql-service.jar">
        # override this property to point to an
        # actual database -- the acme_db
        JdbcUrl jdbc:postgresql://localhost/acme_db

        # override these two properties to
        # specify an actual user authorized to
        # access acme_db
        UserName roadrunner
        Password beepbeep
      </Connector>
      <SecurityService id="Default Security Service"/>
      <TransactionService id="Default Transaction Manager"/>
    </openejb>

## Using a service

Depending on the service type its usage may vary. In case of our Connector service, we leverage the sample entity bean described in Hello OpenEJB World! - A basic CMP entity bean example document. The sample needs to have a connector service configured in OpenEJB before being deployed. Our service is an excellent candidate.

NOTE

The Configure the data source section ought to embrace the configuration file from the Deploying a service section above.

While performing this step you ought to see the following output on OpenEJB console:

    OpenEJB Remote Server 0.8.1    build: 20020731-0133
    http://openejb.sf.net
    ----------------STARTUP----------------
    [init] OpenEJB Container System
    +++ Message from PostgreSQLConnectionFactory.<init>
    +++ Message from PostgreSQLConnectionFactory.init( Properties props ) method
    +++ Setting up [strict] to [true]
    [init] OpenEJB Remote Server
      ** Starting Services **
      NAME             IP              PORT
      ejb server       127.0.0.1       4201
      admin console    127.0.0.1       4200
    -----------------INFO------------------
    To log into the admin console, telnet to:
     telnet 127.0.0.1 4200
    ---------------------------------------
    Ready!
    +++ createManagedConnection() is being called
    +++ createManagedConnection() - the driver org.postgresql.Driver passed the test
    [Jacek Laskowski(5) OpenEJB-1030222628219@SF.net] Hello OpenEJB World!

The above lines, which begin with '+++' are those from the PostgreSQL 7.2.1 Database service. It should get you a feeling on how OpenEJB initializes services, i.e. what service's methods are being invoked upon OpenEJB startup.

NOTE

Change the JdbcDriver parameter to any class like java.lang.String to see the failure when OpenEJB refuses to create a connection because of the invalid driver class.

## What if it didn't work

If you ran into any problems, first check your openejb.log file at %OPENEJB_HOME%\openejb.log. Look for any lines that begin with "WARN", "ERROR", or "FATAL".

There is a list of the most common problems with possible solutions:

Problem:

    OpenEJB Remote Server 0.8.1    build: 20020731-0133
    http://openejb.sf.net
    ----------------STARTUP----------------
    [init] OpenEJB Container System
    Message from PostgreSQLConnectionFactory.<init>
    java.lang.ClassNotFoundException: org.postgresql.Driver
            at java.net.URLClassLoader$1.run(URLClassLoader.java:195)
            at java.security.AccessController.doPrivileged(Native Method)
            ...

Solution:

Add the PostgreSQL JDBC2 driver to OpenEJB's CLASSPATH. See the Configure the data source section for more details.

Java, EJB, JDBC, JNDI, JTA, Sun, Sun Microsystems are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and in other countries. XML, XML Schema, XSLT and related standards are trademarks or registered trademarks of MIT, INRIA, Keio or others, and a product of the World Wide Web Consortium. All other product names mentioned herein are trademarks of their respective owners.