Access OSGi Services via web interface (2024 Edition)
In this blog post I want to share a simple approach to make OSGi services available via web interface. I will show a simple approach that includes the following:
- Embedding a Jetty Webserver in an OSGi application
- Registering a Servlet via OSGi DS using the Whiteboard Specification for Jakarta™ Servlet
Note:
This blog post is an update to the version from 2017.
In the current OSGi Compendium Specification 8.1 the HTTP Whiteboard Specification was replaced with the Whiteboard Specification for Jakarta™ Servlet because of the switch from the Java Servlet API to the Jakarta Servlet API.
Additionally the HTTP Service Specification was removed.
This blog post covers the usage of the Servlets and focus on embedding a Jetty Server and deploy some resources. It will not cover accessing OSGi services via REST interface. If you are interested in creating REST services have a look at the Whiteboard Specification for Jakarta™ RESTful Web Services, which I described in my blog post Build REST services with the OSGi Whiteboard Specification for Jakarta™ RESTful Web Services.
I will skip the introduction on OSGi DS and extend the examples from my Getting Started with OSGi Declarative Services blog. It is easier to follow this post when done the other tutorial first, but it is not required if you adapt the contents here to your environment.
First we need to ensure that the necessary dependencies are available for our project.
PDE - Target Platform
In PDE it is best practice to create a Target Definition so the work is based on a specific set of bundles and we don’t need to install bundles in our IDE.
If you already have a Target Definition, ensure that the following items are part of it:
org.eclipse.equinox.compendium.sdk.feature.group
org.osgi.service.servlet
org.apache.felix.http.jetty
org.apache.felix.http.servlet-api
The reason for using org.apache.felix.http.jetty
instead of the Jetty from the Eclipse Update Site is, that Equinox does not provide an implementation of the Whiteboard Specification for Jakarta™ Servlet that is using the Jakarta Servlet API (jakarta.servlet
). Equinox has only an implementation of the Http Service and the Http Whiteboard Specification, which are using the Java Servlet API (javax.servlet
). The org.apache.felix.http.jetty
bundle contains Jetty bundles and the implementation of the R8.1 OSGi Jakarta Servlet Whiteboard, the R7 OSGi Http Service and the R7 OSGi Http Whiteboard Specification.
The reason for using org.apache.felix.http.servlet-api
is that it wraps the Java Servlet API and the Jakarta Servlet API in one bundle, which is needed by org.apache.felix.http.jetty
, as it currently supports both implementations in one bundle.
It also provides several versions of the APIs, which helps in resolving the OSGi bundle dependencies.
If you don’t have a Target Definition in your project, create a new Target Definition with the following steps:
- Create the target platform project
- Main Menu → File → New → Project → General → Project
- Set name to org.fipro.osgi.target
- Click Finish
- Create a new target definition
- Right click on project → New → Target Definition
- Set the filename to org.fipro.osgi.target.target
- Initialize the target definition with: Nothing: Start with an empty target definition
- Click Finish
After you created a new Target Definition, or opening an existing Target Definition, add the required dependencies for this tutorial:
- Add a new Software Site in the opened Target Definition Editor
- Alternative A
- Switch to the Source tab and add the following snippet to the editor
```xml <?xml version=”1.0” encoding=”UTF-8” standalone=”no”?> <?pde version=”3.8”?>
- Switch to the Source tab and add the following snippet to the editor
org.osgi org.osgi.service.servlet 2.0.0 jar org.apache.felix org.apache.felix.http.jetty 5.1.26 jar org.apache.felix org.apache.felix.http.servlet-api 3.0.0 jar ```
- Alternative B
- By clicking Add… in the Locations section
- Select Software Site
- Software Site https://download.eclipse.org/releases/2024-09
- Disable Group by Category
- Select the following entries
- Eclipse Project SDK
- Equinox Compendium SDK
- Click Finish
- Click Add… in the Locations section
- Select Maven
- Add the GAVs to
- org.osgi:org.osgi.service.servlet:2.0.0
- org.apache.felix:org.apache.felix.http.jetty:5.1.26
- org.apache.felix:org.apache.felix.http.servlet-api:3.0.0
- Set a Label: Servlet
- Dependencies depth: none
- Click Finish
- By clicking Add… in the Locations section
- Alternative A
- Switch to the Definition tab
- Wait until the Target Definition is completely resolved (check the progress at the bottom right)
- Activate the target platform by clicking Set as Target Platform in the upper right corner of the Target Definition Editor
Bndtools - Repository
Using the Bndtools project setup, the dependencies are configured via repositories. The dependencies needed for this tutorial are already included in the predefined repositories, so there are no additional actions needed here. In case you want to update the dependencies or add new entries, have a look at the file cnf/ext/runtime.mvn.
For the Bndtools Maven project setup, of course the dependencies need to be added in the pom.xml files.
Servlet Provider Bundle
The next step is to create a new plug-in / bundle project org.fipro.inverter.http in which we will add the resources that we create in this tutorial.
- Create a new plug-in project
- Main Menu → File → New → Plug-in Project
- Set name to org.fipro.inverter.http
- In the Target Platform section select
- This plug-in is targeted to run with: an OSGi framework:
- Select standard in the combobox
- Check Generate OSGi metadata automatically
- Click Next
- Set Name to Inverter Servlet
- Select Execution Environment JavaSE-17
- Ensure that Generate an Activator and This plug-in will make contributions to the UI are disabled
- Click Finish
- If you do not see the tabs at the bottom of the recently opened editor with name org.fipro.inverter.http, close the editor and open the pde.bnd file in the project org.fipro.inverter.http.
- Switch to the pde.bnd tab
- Add the
Bundle-ActivationPolicy
to get the bundle automatically started in an Equinox runtime - Add the
-runee
instruction to create the requirement on Java 17 - Add the
-buildpath
instruction to specify the dependency to the Inverter API and the Jakarta Servlet bundles.Bundle-Name: Inverter Servlet Bundle-SymbolicName: org.fipro.inverter.http Bundle-Vendor: Bundle-Version: 1.0.0.qualifier Bundle-ActivationPolicy: lazy -runee: JavaSE-17 -buildpath: \ org.fipro.inverter.api, \ org.apache.felix.http.servlet-api,\ org.osgi.service.servlet
- Add the
- Switch to the pde.bnd tab
Bndtools
With Bndtools create a new Bnd OSGi Project using the Component Development template. Since we use the Automatic Manifest Generation PDE project layout, the depenency management is actually quite the same.
- Open the bnd.bnd file of the org.fipro.inverter.http project and switch to the Build tab
- Add the following bundles to the Build Path
- org.apache.http.felix.jetty
- org.apache.http.felix.servlet-api
- org.fipro.inverter.api
- org.osgi.service.servlet
Create a Servlet
implementation
- Create a new package
org.fipro.inverter.http
- Create a new class
InverterServlet
- It should be a typical
Servlet
implementation that extendsjakarta.servlet.http.HttpServlet
- It should also be an OSGi Declarative Service that is registered as service of type
jakarta.servlet.Servlet
- The service should have
ServiceScope.PROTOTYPE
- A special property
osgi.http.whiteboard.servlet.pattern
needs to be set to /invert. This configures the context path of theServlet
. Instead of using thepropery
attribute of the@Component
annotation, use the@HttpWhiteboardServletPattern
component property type. - Add a references to the
StringInverter
OSGi service from the Getting Started tutorial via field reference.
- It should be a typical
package org.fipro.inverter.http;
import java.io.IOException;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.fipro.inverter.StringInverter;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ServiceScope;
import org.osgi.service.servlet.whiteboard.propertytypes.HttpWhiteboardServletPattern;
@Component(
service=Servlet.class,
scope=ServiceScope.PROTOTYPE)
@HttpWhiteboardServletPattern("/invert")
public class InverterServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Reference
private StringInverter inverter;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String input = req.getParameter("value");
if (input == null) {
throw new IllegalArgumentException("input can not be null");
}
String output = inverter.invert(input);
resp.setContentType("text/html");
resp.getWriter().write( "<html><body>Result is " + output + "</body></html>");
}
}
PDE - Launch the example
Before explaining the details further, launch the example to see if our servlet is available via standard web browser. For this we create a launch configuration, so we can start directly from the IDE.
- Select the menu entry Run -> Run Configurations…
- In the tree view, right click on the OSGi Framework node and select New from the context menu
- Specify a name, e.g. OSGi Inverter Http
- Deselect All
- Select the following bundles
- Application bundles
- org.fipro.inverter.api
- org.fipro.inverter.http
- org.fipro.inverter.provider
- OSGi framework and DS bundles
- org.apache.felix.scr
- org.eclipse.osgi
- org.osgi.service.component
- org.osgi.util.function
- org.osgi.util.promise
- Jetty
- org.osgi.service.servlet
- org.apache.felix.http.jetty
- org.apache.felix.http.servlet-api
- slf4j.api
- slf4j.nop
- SPI Fly
- org.apache.aries.spifly.dynamic.bundle
- org.objectweb.asm
- org.objectweb.asm.commons
- org.objectweb.asm.tree
- org.objectweb.asm.tree.analysis
- org.objectweb.asm.util
- Application bundles
- Ensure that Default Auto-Start is set to true
- Switch to the Arguments tab
- Remove
-consoleLog -console
from the Program arguments - Remove
-Dorg.eclipse.swt.graphics.Resource.reportNonDisposed=true
from the VM arguments - Add
-Dorg.osgi.service.http.port=8080
to the VM arguments
- Remove
- Click Run
Note:
The SPI Fly bundles add support for JRE SPI mechanisms and are required by SLF4J 2.x.
Note:
If you include the above bundles in an Eclipse RCP application, ensure that you auto-start the org.apache.aries.spifly.dynamic.bundle and the org.apache.felix.http.jetty bundle to automatically start the Jetty server. This can be done on the Configuration tab of the Product Configuration Editor.
If you now open a browser and go to the URL http://localhost:8080/invert?value=Eclipse you should get a response with the inverted output.
Bndtools - Launch the example
- Open the launch.bndrun file in the org.fipro.inverter.http project
- On the Run tab add the following bundles to the Run Requirements
- org.fipro.inverter.http
- org.fipro.inverter.provider
- Click Resolve to ensure all required bundles are added to the Run Bundles via auto-resolve
- Add
-Dorg.osgi.service.http.port=8080
to the JVM Arguments - Click Run OSGi
Interlude: @RequireHttpWhiteboard
As with most of the OSGi specifications, it is possible to specify a requirement on a specification implementation. For the Jakarta Servlet Whiteboard this means the following entry in the MANIFEST.MF:
Require-Capability: osgi.implementation;
filter:="(&(osgi.implementation=osgi.http)
(version>=2.0)(!(version>=2.0)))"
To simplify the creation of the requirement, the @RequireHttpWhiteboard
annotation can be used.
If you use one of the Jakarta Servlet Whiteboard Component Property Type annotations, it is not necessary to explicitly add the @RequireHttpWhiteboard
.
It is used by those annotations already and therefore added transitively.
Jakarta Servlet Whiteboard
Now why is this simply working? We only implemented a Servlet
and provided it as OSGi DS. And it is “magically” available via web interface.
The answer to this is the OSGi Whiteboard Specification for Jakarta™ Servlet.
It provides the same functionality as its predecessor, Http Whiteboard Specification in the OSGi Compendium Specification 8.0, with the switch from the Java Servlet API to the Jakarta Servlet API.
Servlets are used to provide dynamic content on the Internet using Java. The Jakarta Servlet Whiteboard Specification allows to register servlets and resources via the Whiteboard Pattern, without the need to know the how this is done in detail. I always think about the whiteboard pattern as a “don’t call us, we will call you” pattern. That means you don’t need to register servlets with a web application server directly, you will provide it as a service to the service registry, and the Jakarta Servlet Whiteboard implementation will take it and register it.
Via Jakarta Servlet Whiteboard it is possible to register:
- Servlets
- Servlet Filters
- Resources
- Servlet Listeners
I will show some examples to be able to play around with the Jakarta Servlet Whiteboard.
Register Servlets
An example on how to register a servlet via the Servlet Whiteboard is shown above. The main points are:
- The servlet needs to be registered as OSGi service of type
jakarta.servlet.Servlet
. - The component property osgi.http.whiteboard.servlet.pattern needs to be set to specify the request mappings.
- The service scope should be
PROTOTYPE
.
For registering servlets the following component properties are supported. (see OSGi Compendium Specification - Table 140.4):
Component Property | Description | Component Property Type |
---|---|---|
osgi.http.whiteboard.servlet.asyncSupported | Declares whether the servlet supports the asynchronous operation mode. Allowed values are true and false independent of case. Defaults to false . |
@HttpWhiteboardServletAsyncSupported |
osgi.http.whiteboard.servlet.errorPage | Register the servlet as an error page for the error code and/or exception specified; the value may be a fully qualified exception type name or a three-digit HTTP status code in the range 400-599. Special values 4xx and 5xx can be used to match value ranges. Any value not being a three-digit number is assumed to be a fully qualified exception class name. | @HttpWhiteboardServletErrorPage |
osgi.http.whiteboard.servlet.name | The name of the servlet. This name is used as the value of the jakarta.servlet.ServletConfig.getServletName() method and defaults to the fully qualified class name of the service object. |
@HttpWhiteboardServletName |
osgi.http.whiteboard.servlet.pattern | Registration pattern(s) for the servlet. | @HttpWhiteboardServletPattern |
servlet.init.* | Properties starting with this prefix are provided as init parameters to the jakarta.servlet.Servlet.init(ServletConfig) method. The servlet.init. prefix is removed from the parameter name. |
- |
The Jakarta Servlet Whiteboard service needs to call jakarta.servlet.Servlet.init(ServletConfig)
to initialize the servlet before it starts to serve requests, and when it is not needed anymore jakarta.servlet.Servlet.destroy()
to shut down the servlet.
If more than one Servlet Whiteboard implementation is available in a runtime, the init()
and destroy()
calls would be executed multiple times, which violates the Servlet specification.
It is therefore recommended to use the PROTOTYPE
service scope for servlets to ensure that every Servlet Whiteboard implementation gets its own service instance.
Note:
In a controlled runtime, like an RCP application that is delivered with one Jakarta Whiteboard implementation and that does not support installing bundles at runtime, the usage of the PROTOTYPE
scope is not required.
Actually such a runtime ensures that the servlet is only instantiated and initialized once. But if possible it is recommended that the PROTOTYPE
scope is used.
Register Error Pages
To register a servlet as an error page, the service property osgi.http.whiteboard.servlet.errorPage needs to be set.
The value can be either a three-digit HTTP error code, the special codes 4xx or 5xx to specify a range or error codes, or a fully qualified exception class name.
To configure the service property osgi.http.whiteboard.servlet.errorPage you can use the @HttpWhiteboardServletErrorPage
component property type.
The service property osgi.http.whiteboard.servlet.pattern is not required for servlets that provide error pages.
The following snippet shows an error page servlet that deals with IllegalArgumentExceptions
and the HTTP error code 500. It can be tested by calling the inverter servlet without a query parameter.
package org.fipro.inverter.http;
import java.io.IOException;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ServiceScope;
import org.osgi.service.servlet.whiteboard.propertytypes.HttpWhiteboardServletErrorPage;
@Component(
service=Servlet.class,
scope=ServiceScope.PROTOTYPE)
@HttpWhiteboardServletErrorPage(errorPage = { "java.lang.IllegalArgumentException" , "500"} )
public class ErrorServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html");
resp.getWriter().write("<html><body>You need to provide an input!</body></html>");
}
}
Register Filters
Via servlet filters it is possible to intercept servlet invocations. They are used to modify the ServletRequest
and ServletResponse
to perform common tasks before and after the servlet invocation.
The example below shows a servlet filter that adds a simple header and footer on each request to the servlet with the /invert pattern:
package org.fipro.inverter.http;
import java.io.IOException;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ServiceScope;
import org.osgi.service.servlet.whiteboard.propertytypes.HttpWhiteboardFilterPattern;
@Component(scope=ServiceScope.PROTOTYPE)
@HttpWhiteboardFilterPattern("/invert")
public class SimpleServletFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException { }
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
response.setContentType("text/html");
response.getWriter().write("<b>Inverter Servlet</b><p>");
chain.doFilter(request, response);
response.getWriter().write("</p><i>Powered by fipro</i>");
}
@Override
public void destroy() { }
}
To register a servlet filter the following criteria must match:
- It needs to be registered as OSGi service of type
jakarta.servlet.Filter
. - One of the given component properties needs to be set:
- osgi.http.whiteboard.filter.pattern
- osgi.http.whiteboard.filter.regex
- osgi.http.whiteboard.filter.servlet
- The service scope should be
PROTOTYPE
.
For registering servlet filters the following service properties are supported. (see OSGi Compendium Specification - Table 140.5):
Service Property | Description | Component Property Type |
---|---|---|
osgi.http.whiteboard.filter.asyncSupported | Declares whether the servlet filter supports asynchronous operation mode. Allowed values are true and false independent of case. Defaults to false . |
@HttpWhiteboardFilterAsyncSupported |
osgi.http.whiteboard.filter.dispatcher | Select the dispatcher configuration when the servlet filter should be called. Allowed string values are REQUEST, ASYNC, ERROR, INCLUDE, and FORWARD. The default for a filter is REQUEST. | @HttpWhiteboardFilterDispatcher |
osgi.http.whiteboard.filter.name | The name of a servlet filter. This name is used as the value of the FilterConfig.getFilterName() method and defaults to the fully qualified class name of the service object. |
@HttpWhiteboardFilterName |
osgi.http.whiteboard.filter.pattern | Apply this servlet filter to the specified URL path patterns. The format of the patterns is specified in the servlet specification. | @HttpWhiteboardFilterPattern |
osgi.http.whiteboard.filter.regex | Apply this servlet filter to the specified URL paths. The paths are specified as regular expressions following the syntax defined in the java.util.regex.Pattern class. |
@HttpWhiteboardFilterRegex |
osgi.http.whiteboard.filter.servlet | Apply this servlet filter to the referenced servlet(s) by name. | @HttpWhiteboardFilterServlet |
filter.init.* | Properties starting with this prefix are passed as init parameters to the Filter.init() method. The filter.init. prefix is removed from the parameter name. |
- |
Register Resources
It is also possible to register a service that informs the Jakarta Servlet Whiteboard service about static resources like HTML files, images, CSS- or Javascript-files. For this a simple service can be registered that only needs to have the following two mandatory service properties set:
Service Property | Description | Component Property Type |
---|---|---|
osgi.http.whiteboard.resource.pattern | The pattern(s) to be used to serve resources. As defined by the Jakarta Servlet 5.0 Specification in section 12.2, Specification of Mappings. This property marks the service as a resource service. | @HttpWhiteboardResource#pattern() |
osgi.http.whiteboard.resource.prefix | The prefix used to map a requested resource to the bundle’s entries. If the request’s path info is not null, it is appended to this prefix. The resulting string is passed to the getResource(String) method of the associated Servlet Context Helper. |
@HttpWhiteboardResource#prefix() |
The service does not need to implement any specific interface or function. All required information is provided via the component properties.
To create a resource service follow these steps:
- Create a folder resources in the project org.fipro.inverter.http
- Add an image in that folder, e.g. eclipse_logo.png
- Open the .bnd file (either pde.bnd or bnd.bnd)
- Switch to the Source tab (pde.bnd tab for PDE Tools) and add the following line
-includeresource: resources=resources
Note:
Theincluderesource
instruction is not necessary with the Bndtools Maven project setup. Place the resource in src/main/resources and the folder will be automatially included to the resulting bundle.
- Switch to the Source tab (pde.bnd tab for PDE Tools) and add the following line
- Create resource service
package org.fipro.inverter.http;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.servlet.whiteboard.propertytypes.HttpWhiteboardResource;
@Component(service = ResourceService.class)
@HttpWhiteboardResource(pattern = "/files/*", prefix = "/resources")
public class ResourceService { }
After starting the application the static resources located in the resources folder are available via the /files path in the URL, e.g. http://localhost:8080/files/eclipse_logo.png
Note:
While writing this blog post I came across a very nasty issue. Because I initially registered the servlet filter for the /* pattern, the simple header and footer where always added. This also caused setting the content type, that didn’t match the content type of the image of course. And so the static content was never shown correctly. So if you want to use servlet filters to add common headers and footers, you need to take care of the pattern so the servlet filter is not applied to static resources.
Register Servlet Listeners
It is also possible to register different servlet listeners as whiteboard services. The following listeners are supported according to the servlet specification:
ServletContextListener
Receive notifications when Servlet Contexts are initialized and destroyed.ServletContextAttributeListener
Receive notifications for Servlet Context attribute changes.ServletRequestListener
Receive notifications for servlet requests coming in and being destroyed.ServletRequestAttributeListener
Receive notifications when servlet Request attributes change.HttpSessionListener
Receive notifications when Http Sessions are created or destroyed.HttpSessionAttributeListener
Receive notifications when Http Session attributes change.HttpSessionIdListener
Receive notifications when Http Session ID changes.
There is only one component property needed to be set so the Jakarta Servlet Whiteboard implementation is handling the listener.
Service Property | Description | Component Property Type |
---|---|---|
osgi.http.whiteboard.listener | When set to true this listener service is handled by the Jakarta Servlet Whiteboard implementation. When not set or set to false the service is ignored. Any other value is invalid. |
@HttpWhiteboardListener |
The following example shows a simple ServletRequestListener
that prints out the client address on the console for each request (borrowed from the OSGi Compendium Specification):
package org.fipro.inverter.http;
import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.servlet.whiteboard.propertytypes.HttpWhiteboardListener;
@Component
@HttpWhiteboardListener
public class SimpleServletRequestListener implements ServletRequestListener {
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("Request initialized for client: "
+ sre.getServletRequest().getRemoteAddr());
}
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("Request destroyed for client: "
+ sre.getServletRequest().getRemoteAddr());
}
}
Servlet Context and Common Whiteboard Properties
The ServletContext
is specified in the servlet specification and provided to the servlets at runtime by the container.
By default there is one ServletContext
and without additional information the servlets are registered to that default ServletContext
via the Jakarta Servlet Whiteboard implementation.
This could lead to scenarios where different bundles provide servlets for the same request mapping.
In that case the service.ranking will be inspected to decide which servlet should be delivered.
If the servlets belong to different applications, it is possible to specify different contexts.
This can be done by registering a custom ServletContextHelper
as whiteboard service and associate the servlets to the corresponding context.
The ServletContextHelper
can be used to customize the behavior of the ServletContext
(e.g. handle security, provide resources, …) and to support multiple web-applications via different context paths.
A custom ServletContextHelper
it needs to be registered as service of type ServletContextHelper
and needs to have the following two service properties set:
- osgi.http.whiteboard.context.name
- osgi.http.whiteboard.context.path
To make the configuration more convenient, you can use the @HttpWhiteboardContext
component property type.
Service Property | Description | Component Property Type |
---|---|---|
osgi.http.whiteboard.context.name | Name of the Servlet Context Helper. This name can be referred to by Whiteboard services via the osgi.http.whiteboard.context.select property. The syntax of the name is the same as the syntax for a Bundle Symbolic Name. The default Servlet Context Helper is named default. To override the default, register a custom ServletContextHelper service with the name default. If multiple Servlet Context Helper services are registered with the same name, the one with the highest Service Ranking is used. In case of a tie, the service with the lowest service ID wins. In other words, the normal OSGi service ranking applies. |
@HttpWhiteboardContext#name |
osgi.http.whiteboard.context.path | Additional prefix to the context path for servlets. This property is mandatory. Valid characters are specified in IETF RFC 3986, section 3.3. The context path of the default Servlet Context Helper is /. A custom default Servlet Context Helper may use an alternative path. | @HttpWhiteboardContext#path |
context.init.* | Properties starting with this prefix are provided as init parameters through the ServletContext.getInitParameter() and ServletContext.getInitParameterNames() methods. The context.init. prefix is removed from the parameter name. |
- |
The following example will register a ServletContextHelper
for the context path /eclipse and will retrieve resources from http://eclipse.dev.
It is registered with BUNDLE
service scope to ensure that every bundle gets its own instance, which is for example important to resolve resources from the correct bundle.
- Create a new package
org.fipro.inverter.http.eclipse
in the org.fipro.inverter.http project - Add the following class to the new package
package org.fipro.inverter.http.eclipse;
import java.net.MalformedURLException;
import java.net.URL;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ServiceScope;
import org.osgi.service.servlet.context.ServletContextHelper;
import org.osgi.service.servlet.whiteboard.propertytypes.HttpWhiteboardContext;
@Component(
service = ServletContextHelper.class,
scope = ServiceScope.BUNDLE)
@HttpWhiteboardContext(name = "eclipse", path = "/eclipse")
public class EclipseServletContextHelper extends ServletContextHelper {
public URL getResource(String name) {
// remove the path from the name
name = name.replace("/eclipse", "");
try {
return new URL("https://eclipse.dev/" + name);
} catch (MalformedURLException e) {
return null;
}
}
}
To associate servlets, servlet filter, resources and listeners to a ServletContextHelper
, they share common service properties (see OSGi Compendium Specification - Table 140.3) additional to the service specific properties:
Service Property | Description | Component Property Type |
---|---|---|
osgi.http.whiteboard.context.select | An LDAP-style filter to select the associated ServletContextHelper service to use. Any service property of the Servlet Context Helper can be filtered on. If this property is missing the default Servlet Context Helper is used.For example, to select a Servlet Context Helper with name myCTX provide the following value: (osgi.http.whiteboard.context.name=myCTX) To select all Servlet Context Helpers provide the following value: (osgi.http.whiteboard.context.name=*) |
@HttpWhiteboardContextSelect |
osgi.http.whiteboard.target | The value of this service property is an LDAP style filter expression to select the Jakarta Whiteboard implementation(s) to handle this Whiteboard service. The LDAP filter is used to match HttpServiceRuntime services. Each Servlet Whiteboard implementation exposes exactly one HttpServiceRuntime service. This property is used to associate the Whiteboard service with the Jakarta Whiteboard implementation that registered the HttpServiceRuntime service. If this property is not specified, all Jakarta Whiteboard implementations can handle the service. |
@HttpWhiteboardTarget |
The following example will register a servlet only for the introduced /eclipse context:
package org.fipro.inverter.http.eclipse;
import java.io.IOException;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ServiceScope;
import org.osgi.service.servlet.whiteboard.propertytypes.HttpWhiteboardContextSelect;
import org.osgi.service.servlet.whiteboard.propertytypes.HttpWhiteboardServletPattern;
@Component(
service=Servlet.class,
scope=ServiceScope.PROTOTYPE)
@HttpWhiteboardServletPattern("/image")
@HttpWhiteboardContextSelect("(osgi.http.whiteboard.context.name=eclipse)")
public class ImageServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html");
resp.getWriter().write("Show an image from https://eclipse.dev");
resp.getWriter().write( "<p><img src='img/nattable/FeatureScreenShot.png'/></p>");
}
}
And to make this work in combination with the introduced ServletContextHelper
we need to additionally register the resources for the /img context, which is also only assigned to the /eclipse context:
package org.fipro.inverter.http.eclipse;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.servlet.whiteboard.propertytypes.HttpWhiteboardContextSelect;
import org.osgi.service.servlet.whiteboard.propertytypes.HttpWhiteboardResource;
@Component(service = EclipseImageResourceService.class)
@HttpWhiteboardResource(pattern = "/img/*", prefix = "/eclipse")
@HttpWhiteboardContextSelect("(osgi.http.whiteboard.context.name=eclipse)")
public class EclipseImageResourceService { }
If you start the application and browse to http://localhost:8080/eclipse/image you will see an output from the servlet together with an image that is loaded from https://eclipse.dev.
Note:
The component properties and predefined values are available via org.osgi.service.http.whiteboard.HttpWhiteboardConstants
.
So you don’t need to remember them all and can also retrieve some additional information about the properties via the corresponding Javadoc.
The sources for this tutorial are hosted on GitHub in the already existing projects:
- OSGi DS Getting Started (PDE)
This repository contains the sources in PDE project layout. - OSGi DS Getting Started (Bndtools)
This repository contains the sources in Bndtools project layout using a Bndtools workspace. - OSGi DS Gettings Started (Bnd with Maven)
This repository contains the sources in a Maven project layout that uses the bnd Maven plugins.