Skip to content

Classes holding and wrapping OSGi services, avoiding NullPointerExceptions and null checks.

License

Notifications You must be signed in to change notification settings

steinarb/adapters-for-osgi-services

Repository files navigation

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

https://github.com/steinarb/adapters-for-osgi-services/actions/workflows/adapters-for-osgi-services-maven-ci-build.yml/badge.svg https://coveralls.io/repos/github/steinarb/adapters-for-osgi-services/badge.svg https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=alert_status#.svg https://maven-badges.herokuapp.com/maven-central/no.priv.bang.osgi.service.adapters/adapters/badge.svg https://www.javadoc.io/badge/no.priv.bang.osgi.service.adapters/adapters.svg

https://sonarcloud.io/images/project_badges/sonarcloud-white.svg

https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=sqale_index#.svg https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=coverage#.svg https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=ncloc#.svg https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=code_smells#.svg https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=sqale_rating#.svg https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=security_rating#.svg https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=bugs#.svg https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=vulnerabilities#.svg https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=duplicated_lines_density#.svg https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=reliability_rating#.svg

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:

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.

About

Classes holding and wrapping OSGi services, avoiding NullPointerExceptions and null checks.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages