Adapters for OSGi Services
When using OSGi Declarative Services or any other form of OSGi injections, it is nice not to have to clutter up the code with checks for whether the service is present or not. An example: if your DS component gets both an OSGi log service and a DataSourceFactory service injected, and you would like to set up your database at the point of injection, and the database setup fails before the log service has been injected, you still would like the log message to be preserved.
Also, when exposing servlets as declarative services, setting injections directly into servlet fields is frowned upon by analyisis tools like SonarQube. SonarQube would like all fields of a servlet to be final.
At that point you will either have to resolve a SonarQube bug as a false positive, or live with that bug forever, or use some kind of adapter for the service. If you go for the final solution you can write an adapter on your own, or you can use one of these.
Status
List of Adapters
OSGi Log Service Adapter
Setting up maven dependencies for the LogService adapter
This is the compile and test dependency for the LogService adapter. The “<scope>provided</provided>” setting makes the maven-bundle-plugin understand that this is a dependency that will be provided at runtime. The setting also makes karaf-maven-plugin understand that the bundle should not be loaded and started by the karaf feature attached to this bundle project.
Add the following to the <dependencies> section of the POM of your OSGi bundle project:
<dependency>
<groupId>no.priv.bang.osgi.service.adapters</groupId>
<artifactId>logservice</artifactId>
<version>1.2.0</version>
<scope>provided</scope>
</dependencyS
Setting up a karaf feature dependency
In the karaf feature template file for your bundle project, i.e. src/main/feature/feature.xml, add the maven coordinates of the feature repository holding the feature, and add a feature dependency to the log service adapter feature:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<features>
<repository>mvn:no.priv.bang.osgi.service.adapters/service-adapters-karaf/1.2.0/xml/features</repository>
<feature name="${karaf-feature-name}">
<feature>adapter-for-osgi-logservice</feature>
</feature>
</features>
Using the LogService adapter in Java code
The code example for using the LogService adapter, is an OSGi Declarative Services (DS) component.
In the example, the @Component has a final field of type LogServiceAdapter. This LogServiceAdapter can be used as a LogService whether the service has been injected or not. When the LogService is injected the saved log messages are output (the original time stamp of the log message is not preserved, because the LogService doesn’t have any way of setting it.
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import no.priv.bang.osgi.service.adapters.logservice.LogServiceAdapter;
@Component(service={Servlet.class}, property={"alias=/my-servlet"} )
public class MyServlet extends HttpServlet {
private final LogServiceAdapter logservice;
@Reference
public void setLogservice(LogService logservice) {
this.logservice.setLogService(logservice);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
...
}
}
OSGi JDBC DataSourceFactory
The OSGi DataSourceFactory service works as a plugin point for various JDBC drivers. Implementations for some RDBMSes are provided by pax-jdbc (e.g. derby, hsql, mssql, oracle, mysql, maridb), implementations for others are provided natively (e.g. The PostgreSQL JDBC driver).
Setting up maven dependencies for the DataSourceFactory adapter
This is the compile and test dependency for the DataSourceFactory adapter. The “<scope>provided</provided>” setting makes the maven-bundle-plugin understand that this is a dependency that will be provided at runtime. The setting also makes karaf-maven-plugin understand that the bundle should not be loaded and started by the karaf feature attached to this bundle project.
Add the following to the <dependencies> section of the POM of your OSGi bundle project:
<dependency>
<groupId>no.priv.bang.osgi.service.adapters</groupId>
<artifactId>jdbc</artifactId>
<version>1.2.0</version>
<scope>provided</scope>
</dependencyS
Setting up a karaf feature dependency for the DataSourceFactory adapter
This helps apache karaf find the OSGi bundle for the DataSourceFactory adapter at runtime. Adding a feature depdency like this, will make Apache karaf download the DatSourceFactory adapter’s OSGi bundle from maven central and install it in its OSGi runtime, and start the bundle, when you load and start the bundle that needs it.
In the karaf feature template file for your bundle project, i.e. src/main/feature/feature.xml, add the maven coordinates of the feature repository holding the feature, and add a feature dependency to the log service adapter feature:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<features>
<repository>mvn:no.priv.bang.osgi.service.adapters/service-adapters-karaf/1.2.0/xml/features</repository>
<feature name="${karaf-feature-name}">
<feature>adapters-for-osgi-jdbc-services</feature>
</feature>
</features>
Using the DataSourceFactory adapter i Java code
The code example for using the DataSourceFactory adapter, is an OSGi Declarative Services (DS) component.
In the example, the @Component has a final field of type DataSourceFactoryAdapter. This DataSourceFactoryAdapter can be used as a LogService whether the service has been injected or not. When the LogService is injected the saved log messages are output (the original time stamp of the log message is not preserved, because the LogService doesn’t have any way of setting it.
The interesting bits happens in the activate() method: this is where a new database connection is created.
The activate() method is called initially when the component is activated. The method will also be called when the component’s configuration is created from the command line of the apache karaf console.
package myservlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import org.osgi.service.jdbc.DataSourceFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import no.priv.bang.osgi.service.adapters.jdbc.DataSourceAdapter;
import no.priv.bang.osgi.service.adapters.jdbc.DataSourceFactoryAdapter;
@Component(service={Servlet.class}, property={"alias=/my-servlet"} )
public class MyServlet extends HttpServlet {
private final DataSourceAdapter datasourcefactory;
private final DataSourceFactoryAdapter datasourcefactory;
@Reference
public void setDataSourceFactory(DataSourceFactory factory) {
this.datasourcefactory.setDataSourceFactory(factory);
}
@Activate
public void activate(Map<String, Object> config) {
Properties properties = new Properties();
properties.setProperty(DataSourceFactory.JDBC_URL, config.get("myservlet.jdbc.url"));
properties.setProperty(DataSourceFactory.JDBC_USER, config.get("myservlet.jdbc.user"));
properties.setProperty(DataSourceFactory.JDBC_PASSWORD, config.get("myservlet.jdbc.password"));
try {
datasource.setDatasource(datasourcefactory.createDataSource(properties));
} catch (SQLException e) {
datasource.setDatasource(null);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
...
try {
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement statement = connection.prepareStatement("select * from some_table")) {
...
}
}
} catch (SQLException e) {
throw new ServletException("Failed to read from database when handling POST", e); // Note: SonarQube doesn't like this throw
}
}
}
NOTE: The DataSourceAdapter.getConnetion() method will never throw a NullPointerException. If the adapter doesn’t wrap anything, this method won’t fail, but return a null connection, which is safe to use in a try-with-resource: the try clause won’t be entered and no close will be attempted.
Creating JDBC configuration for the example component
Configuration for a component in apache karaf can be created from the command line. To create the JDBC configuration for the code example above, go to the karaf console (the command line presented when starting karaf from a command line, or when doing SSH to a running karaf), and give the following commands:
config:edit myservlet.MyServlet config:property-set myservlet.jdbc.url "jdbc:postgresql://db.server.com/myservletdb" config:property-set myservlet.jdbc.user "karaf" config:property-set myservlet.jdbc.password "supersecretdonttellanyone" config:update
The configuration name argument to the “config:edit” command should match the fully qualified classname of the OSGi component.
When ENTER is pressed on the config:update command, the activate() method of the component is called and given the updated configuration.
The configuration created this way is persisted in karaf’s “etc” directory and survives both stops and starts of the karaf service, and uninstalls, reinstalls and updates of the OSGi component.
Test utilities
This is a library of implementations of the OSGi services interfaces that are intended for use in unit tests.
Maven dependency
Add the following to the POM of the project(s) that wants to use these classes:
<dependency>
<groupId>no.priv.bang.osgi.service.adapters</groupId>
<artifactId>service-mocks</artifactId>
<version>1.2.0</version>
<scope>test</scope>
</dependency>
The MockLogService
The MockLogService has a method getLogmessages() that can be used to retrieve the messages that have been logged.
In 90% of the cases it’s enough to just verify that messages have been logged at all (or verify that messages have not been logged).
Code example:
@Test
public void testGetJdbcConnectionPropertiesApplicationPropertiesThrowsIOException() throws IOException {
MockLogService logservice = new MockLogService();
// Verify that there are no log messages before the configuration property class is created
assertEquals(0, logservice.getLogmessages().size());
SonarCollectorConfiguration configuration = new SonarCollectorConfigurationWithApplicationPropertiesThrowingIOException(logservice);
// Verify that a single log message had been logged
assertEquals(1, logservice.getLogmessages().size());
}
Release history
Version 1.2.0
Changes:
- Use karaf 4.4.0 and OSGi 8
Version 1.1.4
Changes:
- Avoid inherited imported dependencies leaking out in the adapters BoM
Version 1.1.3
Changes:
- Use karaf 4.3.2 for build and BoM
- Use the osgi.cmpn maven dependency for the OSGi compendium
Version 1.1.2
Changes:
- Provide a Bill of Materials (BoM)
Version 1.1.1
Changes:
- Get common maven dependencies and maven plugin config from a parent pom
Version 1.1.0
Changes:
- Built with karaf 4.3.0 and OSGi 7 and OSGi LogService 1.4.0
- Adapts the LogServiceAdapter and the MockLogService to LogService 1.4.0
Many sonar errors because the LogService interface now has many deprecated methods.
But the methods must be implemented and suppressing the warnings are both a lot of work, and the wrong thing to do.
Version 1.0.1
Changes:
- Adds an adapter for the OSGi DataSourceFactory service
- Adds a library of mock services for use in JUnit tests
Version 1.0.0
The initial release.
Contains just the adapter for the OSGi LogService.
License
This software is licensed under the Apache License, version 2.
See the LICENSE files for details.