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

Steinar Bang 0bab3589a3 Upgrade parent to use maven-compiler-plugin 3.12.1, maven-javadoc-plugin 3.6.3, maven-surefire-plugin 3.2.3, jacoco-maven-plugin 0.8.11, maven-clean-plugin 3.3.2 3 months ago
.github 5d1034f50f Replace java 11 with java 17 in github actions CI build 9 months ago
adapters aa983e0284 Exclude karaf feature file generation from eclipse m2e lifecycle handling 1 year ago
adapters-bom f57cc36559 [maven-release-plugin] prepare for next development iteration 1 year ago
jacoco-coverage-report f57cc36559 [maven-release-plugin] prepare for next development iteration 1 year ago
.editorconfig f43b969ffc Implement an adapter for the OSGi LogService. 6 years ago
.gitignore f43b969ffc Implement an adapter for the OSGi LogService. 6 years ago
LICENSE f43b969ffc Implement an adapter for the OSGi LogService. 6 years ago
README.org 2c2420520f Replace travis-ci CI build with github actions CI build 1 year ago
pom.xml 0bab3589a3 Upgrade parent to use maven-compiler-plugin 3.12.1, maven-javadoc-plugin 3.6.3, maven-surefire-plugin 3.2.3, jacoco-maven-plugin 0.8.11, maven-clean-plugin 3.3.2 3 months ago

README.org

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

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

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

file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=sqale_index#.svg file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=coverage#.svg file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=ncloc#.svg file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=code_smells#.svg file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=sqale_rating#.svg file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=security_rating#.svg file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=bugs#.svg file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=vulnerabilities#.svg file:https://sonarcloud.io/api/project_badges/measure?project=steinarb_adapters-for-osgi-services&metric=duplicated_lines_density#.svg file: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 "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 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 "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 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

Maven dependency

This is a library of implementations of the OSGi services interfaces that are intended for use in unit tests. 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.

Version 1.0.1

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.0

The initial release.

License

Contains just the adapter for the OSGi LogService.

This software is licensed under the Apache License, version 2.

See the LICENSE files for details.