Developing OSGi-enabled Jakarta EE Applications

This chapter describes the features and interfaces that Payara Server provides to develop OSGi-enabled Enterprise applications.

Overview of OSGi Application and Payara Server

Payara Server is a certified Jakarta EE implementation, so it provides the latest Jakarta EE APIs and frameworks. It is built using OSGi technology, and includes as its OSGi module management subsystem the Apache Felix OSGi framework, which is a fully-compliant implementation of the OSGi Service Platform R7 specification. Payara Server supports deployment of OSGi-based applications using this framework.

OSGi applications can make use of core as well as enterprise OSGi features. Payara Server makes available many of its Jakarta EE platform services, such as the transaction service, HTTP service, JDBC Service and JMS, as OSGi services. It also enables use of Jakarta EE programming model in OSGi applications, so enterprise Jakarta application developers can continue to leverage their existing skills in OSGi-based applications. See Benefits of Using OSGi in Enterprise Jakarta Applications for more information.

OSGi applications are deployed as one or more OSGi bundles, and the Payara Server deployment and administration infrastructure enables you to deploy and manage your OSGi bundles. This chapter classifies OSGi bundles into two categories based on the features they use:

Plain OSGi Application Bundles

Bundles that do not contain any Jakarta EE components. See Developing Plain OSGi Bundles.

Hybrid Application Bundles

Bundles that are an OSGi bundle as wells as a Jakarta EE module. At runtime, such modules have both an OSGi bundle context and a Jakarta EE context. Payara Server supports the following hybrid application bundles:

Benefits of Using OSGi in Enterprise Jakarta Applications

Enterprise applications typically need transactional, secured access to data stores, messaging systems and other such enterprise information systems, and have to cater to a wide variety of clients such as web browsers and desktop applications, and so on. Jakarta EE makes development of such applications easier with a rich set of APIs and frameworks. It also provides a scalable, reliable and easy to administer runtime to host such applications.

The OSGi platform complements these features with modularity. It enables applications to be separated into smaller, reusable modules with a well-defined and robust dependency specification.

A module explicitly specifies its capabilities and requirements. This explicit dependency specification encourages developers to visualize dependencies among their modules and help them make their modules highly cohesive and less coupled. The OSGi module system is dynamic: It allows modules to be added and removed at runtime.

OSGi has very good support for versioning too: It supports package versioning as well as module versioning. In fact, it allows multiple versions of the same package to coexist in the same runtime, thus allowing greater flexibility to deployers. The service layer of the OSGi platform encourages a more service-oriented approach to build complex systems.

The service-oriented approach and dynamic module system used together allow a system to be more agile during development as well as in production. It makes them better suited to run in a Platform-as-a-Service (PaaS) environment.

With Payara Server, you do not have to chose one of the two platforms. A hybrid approach like OSGi enabling your Jakarta EE applications allows new capabilities to applications hitherto unavailable to applications built using just one of the two platforms.

Developing OSGi Application Bundles for Payara Server

Payara Server enables interaction between OSGi components and Jakarta EE components.

OSGi services managed by the OSGi framework can invoke Jakarta EE components managed by the Jakarta EE container and vice versa. For example, developers can declaratively export EJBs as OSGi services without having to write any OSGi code. This allows any plain OSGi component, which is running without the Jakarta EE context, to discover the EJB and invoke it.

Similarly, Jakarta EE components can locate OSGi services provided by plain OSGi bundles and use them as well. Payara Server extends the Jakarta EE Context and Dependency Injection (CDI) framework to make it easier for Jakarta EE components to consume dynamic OSGi services in a type-safe manner.

Developing Plain OSGi Bundles

Jakarta EE components (like an EJB or Servlet) can look up Jakarta EE platform services using JNDI names in the associated Jakarta EE naming context. Such code can rely on the Jakarta EE container to inject the required services as well. Unfortunately, neither of them works when the code runs outside a Jakarta EE context.

An example of such code is the BundleActivator of an OSGi bundle. For such code to access Jakarta EE platform services, Payara Server makes key services and resources of the underlying Jakarta EE platform available as OSGi services.

Thus, an OSGi bundle deployed in Payara Server can access these services using OSGi Service look-up APIs or by using a white board pattern. The following Jakarta EE services are available as OSGi services:

  • HTTP Service

  • Transaction Service

  • JDBC Data Source Service

  • JMS Resource Service

HTTP Service

The Payara Server web container is made available as a service for OSGi users who do not use OSGi Web Application Bundles (WABs).

This service is made available using the standard OSGi/HTTP service specification, which is a lightweight API that predates the concept of a web application as we know it today.

This simple API allows users to register servlets and static resources dynamically and draw a boundary around them in the form of a HttpContext.

This simple API can be used to build feature-rich web application, such as the Felix Web Console for example.

The Payara Server web container has one or more virtual servers. A virtual server has one or more web application deployed in it. Each web application has a distinct context path.

Each virtual server has a set of HTTP listeners. Each HTTP listener listens on a particular port. When multiple virtual servers are present, one of them is treated as the default virtual server. Every virtual server comes configured with a default web application. The default web application is used to serve static content from the docroot directory of a Payara Server domain. This default web application uses / as the context path. A web application contains static and dynamic resources. Each virtual server is mapped to an org.osgi.services.http.HttpService instance. When there are multiple virtual servers present, there will be multiple occurrences of HttpService registered in the service registry.

In order to distinguish one service from another, each service is registered with a service property named VirtualServer, whose value is the name of the virtual server.

The service corresponding to default virtual server has the highest ranking, so when looking up a service of type HttpService without any additional criteria returns the HttpService corresponding to the default virtual server. In a typical installation, the default virtual server is configured to listen on port 8080 for the HTTP protocol and port 8181 for the HTTPS protocol.

The context path / is reserved for the default web application. Every resource and servlet registered using the registerResource() and registerServlet() methods of HttpService are made available under a special context path named /osgi in the virtual server. The /osgi context path can be changed to some other value by setting an appropriate value in the OSGi configuration property or in a system property called org.glassfish.osgihttp.ContextPath.

For example, a HelloWorldServlet component will be available at http://localhost:8080/osgi/helloworld when the following code is executed:

HttpService httpService = getHttpService(); // Obtain HttpService
httpService.registerServlet(httpService.registerServlet("/helloworld", new HelloWorldServlet(), null, ctx);

Transaction Service

The Jakarta Transaction API (JTA) defines three interfaces to interact with the transaction management system: UserTransaction, TransactionManager, and TransactionSynchronizationRegistry. They all belong to the jakarta.transaction package. TransactionManager`and `TransactionSynchronizationRegistry are intended for system level code, such as a persistence provider. Whereas, UserTransaction is the entity that you should use to control user-managed transactions.

All the objects of the underlying JTA layer are made available in the OSGi service registry using the following service interfaces:

  • jakarta.transaction.UserTransaction

  • jakarta.transaction.TransactionManager

  • jakarta.transaction.TransactionSynchronisationRegistry

There is no additional service property associated with them. Although UserTransaction appears to be a singleton, in reality any call to it gets rerouted to the actual transaction associated with the calling thread. Code that runs in the context of a Jakarta EE component typically gets a handle on UserTransaction by doing a JNDI lookup in the component naming context or by using injection, as shown here:

var utx = (UserTransaction)(new InitialContext().lookup("java:comp/UserTransaction"));

or

@Resource
UserTransaction utx;

When certain code (such as an OSGi Bundle Activator), which does not have a Jakarta EE component context, wants to get hold of UserTransaction, or any of the other JTA artifacts, then they can look it up in the service registry. Here is an example of such code:

BundleContext context; // An OSGi context must be present
var txRef = context.getServiceReference(UserTransaction.class.getName());
var utx = (UserTransaction)context.getService(txRef);

JDBC Data Source Service

Any JDBC data source created in Payara Server is automatically made available as an OSGi Service; therefore, OSGi bundles can track availability of JDBC data sources using the ServiceTracking facility of the OSGi platform.

The life of the OSGi service matches that of the underlying data source created in Payara Server.

Payara Server registers each JDBC data source as an OSGi service with objectClass = "javax.sql.DataSource" and a service property called jndi-name, which is set to the JNDI name of the data source.

Here is a code sample that looks up a data source service:

@Inject
@OSGiService(filter = "(jndi-name=jdbc/MyDS)")
private DataSource ds;

JMS Resource Service

Like JDBC data sources, JMS administered objects, such as destinations and connection factories, are also automatically made available as OSGi services.

Their service mappings are as follows.

JMS Object Service Interface Service Properties Comments

JMS Queue destination

jakarta.jms.Queue

jndi-name

jndi-name is set to the JNDI name of the queue

JMS Topic destination

jakarta.jms.Topic

jndi-name

jndi-name is set to the JNDI name of the topic

JMS connection factory

  • jakarta.jms.QueueConnectionFactory

  • jakarta.jms.TopicConnectionFactory

  • jakarta.jms.ConnectionFactory

jndi-name

jndi-name is set to the JNDI name of the topic.

The actual service interface depends on which type of connection factory was created.

Developing Web Application Bundles

When a web application is packaged and deployed as an OSGi bundle, it is called a Web Application Bundle (WAB). WAB support is based on the OSGi Web Application specification, which is part of the OSGi Service Platform, Enterprise Specification, Release 7. A WAB is packaged as an OSGi bundle, so all the OSGi packaging rules apply to WAB packaging.

When a WAB is not packaged like a WAR, the OSGi Web Container of Payara Server maps the WAB to the hierarchical structure of web application using the following rules:

  • The root of the WAB corresponds to the docroot of the web application.

  • Every JAR in the Bundle-ClassPath attribute of the WAB is treated like a JAR located in WEB-INF/lib/.

  • Every directory except "." in the Bundle-ClassPath attribute of the WAB is treated like the WEB-INF/classes/. directory.

  • A Bundle-ClassPath entry of type "." is treated as if the entire WAB is a JAR located in WEB-INF/lib/.

  • Bundle-ClassPath includes the Bundle-ClassPath entries of any attached fragment bundles.

We strongly recommend that you package the WAB exactly like a WAR, with only additional OSGi metadata to prevent triggering any conflicts with the rules mentioned above.

WAB Metadata

In addition to the standard OSGi metadata, the main attributes of the META-INF/MANIFEST.MF of the WAB must have an additional attribute called Web-ContextPath.

The Web-ContextPath attribute specifies the value of the context path of the web application. Since the root of a WAB is mapped to the docroot of the web application, it should not be used in the Bundle-ClassPath attribute.

Moreover, WEB-INF/classes/ should be specified ahead of WEB-INF/lib/ in the Bundle-ClassPath in order to be compliant with the search order used for traditional WAR files.

Assuming the WAB is structured as follows:

foo.war/
   index.html
   foo.jsp
   WEB-INF/classes/
      foo/BarServlet.class
   WEB-INF/lib/lib1.jar
   WEB-INF/lib/lib2.jar

Then the OSGi metadata for the WAB as specified in META-INF/MANIFEST.MF of the WAB would appear as follows:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.acme.foo
Bundle-Version: 1.0
Bundle-Name: Foo Web Application Bundle Version 1.0
Import-Package: jakarta.servlet; jakarta.servlet.http, version=[3.0, 4.0, 5.0)
Bundle-ClassPath: WEB-INF/classes, WEB-INF/lib/lib1.jar, WEB-INF/lib/lib2.jar
Web-ContextPath: /foo

How WABs Consume OSGi Services

Since a WAB has a valid Bundle-Context, it can consume OSGi services. Although you are free to use any OSGi API to locate OSGi services, Payara Server makes it easy for WAB users to use OSGi services by virtue of extending the Context and Dependency Injection (CDI) framework. Here’s an example of the injection of an OSGi Service into a Servlet:

@WebServlet
public class MyServlet extends HttpServlet {

    @Inject
    @OSGiService(dynamic=true)
    FooService fooService;
}

OSGi CDI Extension for WAB

Payara Server includes a CDI extension that enables web applications, such as servlets, that are part of WABs to express a type-safe dependency on an OSGi service using CDI APIs.

An OSGi service can be provided by any OSGi bundle without any knowledge of Jakarta EE/CDI, and they are allowed to be injected transparently in a type-safe manner into a web application.

A custom CDI Qualifier, @org.glassfish.osgicdi.OSGiService, is used by the component to represent dependency on an OSGi service. The qualifier has additional metadata to customize the service discovery and injection behavior.

The following @OsgiService attributes are currently available:

  • serviceCriteria — An LDAP filter query used for service selection in the OSGi service registry.

  • waitTimeout — Waits the specified duration for a service that matches the criteria specified to appear in the OSGi service registry.

  • dynamic — Dynamically obtain a service reference (true/false).

Since OSGi services are dynamic, they may not match the life cycle of the application component that has injected a reference to the service. Through the dynamic attribute, you can indicate that a service reference can be obtained dynamically or not.
For stateless or idempotent services, a dynamic reference to a service implementation would be useful. The container then injects a proxy to the service and dynamically switches to an available implementation when the current service reference is invalid.
Example

In this example, Bundle B0 defines a service contract called com.acme.Foo and exports the com.acme package for use by other bundles.

Bundle B1 in turn provides a service implementation, FooImpl, of the com.acme.Foo interface. It then registers the service FooImpl service with the OSGi service registry with com.acme.Foo as the service interface.

Bundle B2 is a hybrid application bundle that imports the com.acme package. It has a component called BarServlet that expresses a dependency to com.acme.Foo by adding a field/setter method and qualifies that injection point with a @OsgiService annotation.

For instance, BarServlet looks like this:

@Servlet
public class BarServlet extends HttpServlet{

    @Inject
    @OSGiService(dynamic=true)
    private com.acme.Foo f;
}

EJB Application Bundles

Another type of hybrid application bundle is the EJB Application Bundle. When an EJB Jar is packaged with additional OSGi metadata and deployed as an OSGi bundle it is called an EJB Application Bundle.

Payara Server supports only packaging the OSGi bundle as a simple JAR file with the required OSGi metadata, just as you would package an ejb-jar file.

Required EJB Metadata

An EJB Application Bundle must have a manifest metadata attribute called Export-EJB in order to be considered as an EJB Bundle. Here’s an example of an EJB Application Bundle with this metadata attribute:

myEjb.jar/
   com/acme/Foo
   com/acme/impl/FooEJB
   META-INF/MANIFEST.MF

And here’s how its Manifest entries look like:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.acme.foo EJB bundle
Bundle-Version: 1.0.0.BETA
Bundle-Name: com.acme.foo EJB bundle version 1.0.0.BETA
Export-EJB: ALL
Export-Package: com.acme; version=1.0
Import-Package: jakarta.ejb; version=[3.0, 4.0), com.acme; version=[1.0, 1.1)

How EJB Bundles Consume OSGi Services

Since an EJB has a valid Bundle-Context, it can consume OSGi services. Although you are free to use any OSGi API to locate OSGi services, Payara Server makes it easy to use OSGi services by virtue of extending the Context and Dependency Injection (CDI) framework.

Here’s an example of injection of an OSGi Service into a stateless bean:

@Stateless
public class MyEJB {

    @Inject
    @OSGiService(dynamic=true)
    Foo foo;
}

Using the OSGi CDI Extension With EJB Bundles

Payara Server includes a CDI extension that enables EJB application bundles to express a type-safe dependency on an OSGi Service using CDI APIs.

An OSGi service can be provided by any OSGi bundle without any knowledge of Jakarta EE/CDI, and they are allowed to be injected transparently in a type-safe manner into an EJB bundle.

A custom CDI Qualifier, @org.glassfish.osgicdi.OSGiService, is used by the component to represent dependency on an OSGi service. The qualifier has additional metadata to customize the service discovery and injection behavior.

The following @OsgiService attributes are currently available:

  • serviceCriteria — An LDAP filter query used for service selection.

  • waitTimeout — Waits for specified duration for a service to appear in the OSGi service registry.

  • dynamic — Dynamically obtain a service reference (true/false).

Deploying OSGi Bundles in Payara Server

For instruction on deploying OSGi bundle, see "OSGi Bundle Deployment Guidelines" in the Payara Server Application Deployment section.