Use JBI components for integration

An introduction to Java Business Integration components

The Java Business Integration specification (Java Specification Request 208) defines a standard means for assembling integration components to create integration solutions that enable a service-oriented architecture (SOA) in an enterprise information system. These components are plugged into a JBI environment and can provide or consume services through it in a loosely coupled way. The JBI environment then routes the exchanges between those components and offers a set of technical services.

A component is the main detail a user or developer faces when working in the JBI environment. Understanding how a component interacts with the JBI environment is just as important as understanding the environment black-box itself.

The JBI environment

JBI defines a container that can host components. Two kinds of components can be plugged into a JBI environment:

  • Service engines provide logic in the environment, such as XSL (Extensible Stylesheet Language) transformation or BPEL (Business Process Execution Language) orchestration.
  • Binding components are sort of "connectors" to external services or applications. They allow communication with various protocols, such as SOAP, Java Message Service, or ebXML.

JBI is built on top of state-of-the-art SOA standards: service definitions are described in WSDL (Web Services Description Language), and components exchange XML messages in a document-oriented-model way.

The JBI environment glues together the JBI components by acting as a message router to:

  • Find a service provided by a component
  • Send a request to a service provider
  • Manage the message exchange lifecycle (request, response, acknowledgements, etc.)

It also provides a set of services (support of naming context, transactional context, etc.) to the components.

In addition, the JBI container (shown in Figure 1) manages, through a rich management API, the installation and lifecycle management of the components, and the deployment of artifacts to configure an installed component (for instance, deployment of XSL stylesheets to a transformation service engine).

Figure 1. JBI container. Click on thumbnail to view full-sized image.

JBI components

JBI components are the base elements composed by the JBI container to create an integration solution. The components are plug-ins for the container and are considered "external." As a Java EE (Java EE is Sun's new name for J2EE) container hosts Enterprise JavaBeans (EJB) components, or as a portal hosts portlets, the JBI container hosts integration components. You can write your own components, or you can use components written by someone else, in the same way you write a portlet or use an existing one.

A JBI component comes with several main objects represented by the following SPI (system programming interface):

  • The Component object, with which the container interacts to retrieve component information (such as a services description)
  • The LifeCycle object, used by the container to manage the component's lifecycle
  • The ComponentContext, given by the container to the component, for communication with the JBI environment

Additional objects are:

  • The Bootstrap object, which provides all operations required at install/uninstall time
  • The ServiceUnitManager object, used to manage deployment of artifacts on a component

A component is packaged as an archive (a zip or jar file). This archive contains the component's classes, the required libraries, and a descriptor file.

To plug a component into a JBI container, use the management API provided by the container. This API allows you to provide to the container your component package's location. Then, the container processes the component archive and installs the component.

From the component viewpoint, two different phases are defined:

  • The installation phase, in which the JBI container installs the component and plugs it into the bus
  • The execution phase, in which the JBI container interacts with the component

Installation

During the installation phase, the component must perform all extra processes needed for its execution, such as the creation of mandatory folders or the installation of a database. As illustrated in Figure 2, the Bootstrap object completes the installation and receives an onInstall() event from the container with an associated InstallationContext object, which provides to Bootstrap some installation information (the path of the installation, a naming context, etc.).

Figure 2. Interactions during the installation phase. Click on thumbnail to view full-sized image.

Execution

The container starts, stops, and shuts down the component. As shown in Figure 3, the container initializes a component by passing it a ComponentContext, which is the entry point to the JBI environment. While the component runs, it interacts with the JBI environment through this ComponentContext.

The component can consume services (exposed on the bus by other components) by sending messages to a service provider. The component can also act as the service provider, accepting and processing such messages, and returning an answer to the consumer through the bus.

Figure 3. Interactions during the execution phase. Click on thumbnail to view full-sized image.

Component interactions

To illustrate the interactions between a service consumer and a service provider, let's take a simple request-response exchange as an example. The corresponding message exchange pattern (MEP) is an InOut exchange pattern, as defined in the JBI specification (see section 5.4.2). JBI supports four WSDL pattern exchanges: In, InOut, InOptionalOut, and RobustIn. Each pattern defines a particular exchange sequence.

Let's begin with service consumer interactions.

Service consumer

Once a component is running, it can find and consume the services registered in the JBI environment. Therefore, the component is in the role of a service consumer.

Find a service endpoint

A ServiceEndpoint represents an address where a service provided by a component can be found. Several components can provide the same service (with eventually different implementations), but each of those components has a unique endpoint.

As shown in Figure 4, to find a service, the consumer asks its ComponentContext for the list of all endpoints matching the service name by using the getEndpointsForService(serviceName) method. The consumer then chooses the provider it wants to reach. If the consumer already knows the address of the provider that it wants to contact, it can retrieve the provider ServiceEndpoint object by using a getEndpoint(serviceName, endpointName) method.

Figure 4. Get endpoints for a given service. Click on thumbnail to view full-sized image.

Create a message exchange

To manipulate messages, the ComponentContext provides a DeliveryChannel object, which represents a bidirectional communication channel between the component and the JBI environment's message router (called normalized message router, or NMR). The DeliveryChannel is in charge of message-exchange instantiation and is the path through which the messages advance to the NMR. The NMR routes the message to the component that provides the requested service. An exchange between the consumer and the provider is materialized by a MessageExchange object. This object serves during the exchange's entire lifecycle.

When the consumer wants to initialize a new exchange, it asks a MessageExchangeFactory (provided by the DeliveryChannel object) to create a new MessageExchange. This MessageExchange contains the actual message content and a set of metadata, such as the provider endpoint, the status of the exchange (active, done, in error), or identification of the exchange "owner" (the consumer or the provider). The consumer and provider share MessageExchange during the exchange (request, response, etc.).

Figure 5. Create a message exchange. Click on thumbnail to view full-sized image.

Send the message

Now that the consumer has instantiated a MessageExchange, it can set on this object the message it wants to send to the consumer. The consumer has to set a NormalizedMessage as the exchange's "in" message. The NormalizedMessage is a JBI definition of a message. The consumer asks the MessageExchange to create a new NormalizedMessage.

Then, the consumer sets on this NormalizedMessage the content of the message (an XML payload) and eventually some attachments. The consumer must set on the MessageExchange the provider's ServiceEndpoint (previously retrieved) and the name of the operation to be performed.

Note: The consumer can omit stipulation of the provider's ServiceEndpoint and just specify a service name. In this case, the NMR will search all matching endpoints and choose one of them.

Finally, the consumer sends the MessageExchange using the DeliveryChannel. Figure 6 illustrates this entire process.

Figure 6. Send a message. Click on thumbnail to view full-sized image.

Service provider

Once a component is running, it can also provide services. This component acts as a service provider.

Activate an endpoint

The provider must publish the services it wants to offer. As shown in Figure 7, the ComponentContext's activateEndpoint(serviceName, endpointName) method publishes the service publication. This method returns a generated ServiceEndpoint object that references the new service in the JBI environment.

With publication completed, other components can access the activated services by finding the corresponding endpoint with their ComponentContext's getEndpointForService() method.

Figure 7. Activate a ServiceEndpoint. Click on thumbnail to view full-sized image.

Receive a message

Now that the provider has published some services, it can receive messages from other components (consumers). When a consumer sends a message to a provider, the message (MessageExchange) is pushed in the message queue of the provider's DeliveryChannel. The provider retrieves the received messages from the message queue by calling accept() on its DeliveryChannel.

Once the provider obtains a MessageExchange, it can process it. The provider gets the "in" message (the NormalizedMessage object set by the consumer), the name of the operation to perform, the payload of the message, and so on.

Figure 8. Receive a message. Click on thumbnail to view full-sized image.

Send the response

If the operation requires an answer, the provider can set an "out" message on the MessageExchange and send it again to the NMR via its DeliveryChannel. The NMR routes the MessageExchange to the consumer that previously initiated this MessageExchange.

Figure 9. Send the response. Click on thumbnail to view full-sized image.

Close the exchange

After the provider sends its response, the exchange is nearly complete. The NMR routes the answer to the consumer, which receives it with an accept() call on its DeliveryChannel. The consumer processes the MessageExchange's "out" content and then must close the exchange. To do this, the consumer sets the status of MessageExchange to DONE and sends it again to the NMR via its DeliveryChannel's send() method. The exchange is terminated, and the provider learns of this termination upon receiving the MessageExchange with its DONE status.

The following schema describes the whole exchange process.

Figure 10. The whole in-out exchange. Click on thumbnail to view full-sized image.

HelloworldService component

Now it's time to see more practically how the components interact with the JBI environment. In this section, I show most of the code that needs to be implemented to create a simple Helloworld service engine. As shown earlier in this article, a JBI component is a set of objects that must implement some JBI interfaces. In addition, a descriptor file must be provided with this component. As just a few lines of codes are necessary to implement the Component and the ComponentLifeCycle interfaces, a single object can implement these two interfaces.

To process requests, there is no listener mechanism proposed by the JBI specification. To receive a message, we must block on an accept() method. A good pattern is to create a separate object executed in another thread. This "listener" makes a loop on the accept() method, avoiding the Component to be blocked.

See Resources to download this example's complete source code. You can test this example with the Petals ESB.

Component and ComponentLifeCycle

The object that implements the Component and the ComponentLifeCycle interfaces just registers the HelloworldService in the JBI environment, then creates and starts the HelloworldListener thread, which processes the incoming messages:

1 2 Page 1