Search code examples
javadeploymentosgiapache-felix

OSGi deployment on Apache Felix


I have created my first OSGi service and was trying to deploy it on Apache Felix. When I looked at the system console afterwards I saw that the service was not active and some problems occured:

org.springframework.ws.client.core,version=[2.1,3) -- Cannot be resolved
org.springframework.ws.soap,version=[2.1,3) -- Cannot be resolved
org.springframework.ws.soap.axiom,version=[2.1,3) -- Cannot be resolved
org.springframework.ws.soap.saaj,version=[2.1,3) -- Cannot be resolved

So I looked which jar contained these packages and this comes from spring-ws-core-2.1.2.RELEASE.jar, which is also an OSGi bundle. I deployed that one as well but then again the following error messages occurred:

org.springframework.web.servlet,version=[3.1.0, 4.0.0) -- Cannot be resolved

Again a dependency, this time on spring-webmvc-3.2.17.RELEASE.jar. The problem however is that this one is not an OSGi bundle, how do I solve this issue then? Since it is a third party library there is not much I can think of.

So how can I use non bundle jars in an OSGi container? And how can I automatically resolve the dependency tree to not having to resolve everything by hand?


Solution

  • I have created the osgi-run project to address this very issue of solving bundles dependencies using the standard Maven dependency resolution (instead of OBR which is not widely supported, unfortunately), supported by Gradle.

    However, the Spring jars are a terrible nightmare to resolve since the Spring Project abandoned support for OSGi, several years ago.

    In theory, with osgi-run, you should be able to create a OSGi environment containing the spring-ws-core bundle by using the following gradle file:

    plugins {
        id "com.athaydes.osgi-run" version "1.5.1"
    }
    
    repositories {
        mavenLocal()
        jcenter()
    }
    
    dependencies {
        osgiRuntime 'org.springframework.ws:spring-ws-core:2.1.1.RELEASE'
    }
    

    This relies on the information in the poms being consistent. And if any non-bundle is found, it is automatically converted into a OSGi bundle (see wrapping jars).

    However, this is not working... Gradle can print the dependency hierarchy for the spring-ws-core jar, and here's what I get when I use that:

    +--- org.springframework.ws:spring-ws-core:2.1.1.RELEASE
    |    +--- org.springframework.ws:spring-xml:2.1.1.RELEASE
    |    |    +--- org.springframework:spring-context:3.1.2.RELEASE
    |    |    |    +--- org.springframework:spring-aop:3.1.2.RELEASE
    |    |    |    |    +--- aopalliance:aopalliance:1.0
    |    |    |    |    +--- org.springframework:spring-asm:3.1.2.RELEASE
    |    |    |    |    +--- org.springframework:spring-beans:3.1.2.RELEASE
    |    |    |    |    |    \--- org.springframework:spring-core:3.1.2.RELEASE
    |    |    |    |    |         +--- org.springframework:spring-asm:3.1.2.RELEASE
    |    |    |    |    |         \--- commons-logging:commons-logging:1.1.1
    |    |    |    |    \--- org.springframework:spring-core:3.1.2.RELEASE (*)
    |    |    |    +--- org.springframework:spring-beans:3.1.2.RELEASE (*)
    |    |    |    +--- org.springframework:spring-core:3.1.2.RELEASE (*)
    |    |    |    +--- org.springframework:spring-expression:3.1.2.RELEASE
    |    |    |    |    \--- org.springframework:spring-core:3.1.2.RELEASE (*)
    |    |    |    \--- org.springframework:spring-asm:3.1.2.RELEASE
    |    |    +--- commons-logging:commons-logging:1.1.1
    |    |    +--- org.springframework:spring-core:3.1.2.RELEASE (*)
    |    |    \--- org.springframework:spring-beans:3.1.2.RELEASE (*)
    |    +--- org.springframework:spring-context:3.1.2.RELEASE (*)
    |    +--- org.springframework:spring-aop:3.1.2.RELEASE (*)
    |    +--- org.springframework:spring-oxm:3.1.2.RELEASE
    |    |    +--- commons-lang:commons-lang:2.5
    |    |    +--- org.springframework:spring-beans:3.1.2.RELEASE (*)
    |    |    +--- org.springframework:spring-context:3.1.2.RELEASE (*)
    |    |    \--- org.springframework:spring-core:3.1.2.RELEASE (*)
    |    +--- org.springframework:spring-web:3.1.2.RELEASE
    |    |    +--- aopalliance:aopalliance:1.0
    |    |    +--- org.springframework:spring-beans:3.1.2.RELEASE (*)
    |    |    +--- org.springframework:spring-context:3.1.2.RELEASE (*)
    |    |    \--- org.springframework:spring-core:3.1.2.RELEASE (*)
    |    +--- org.springframework:spring-webmvc:3.1.2.RELEASE
    |    |    +--- org.springframework:spring-asm:3.1.2.RELEASE
    |    |    +--- org.springframework:spring-beans:3.1.2.RELEASE (*)
    |    |    +--- org.springframework:spring-context:3.1.2.RELEASE (*)
    |    |    +--- org.springframework:spring-context-support:3.1.2.RELEASE
    |    |    |    +--- org.springframework:spring-beans:3.1.2.RELEASE (*)
    |    |    |    +--- org.springframework:spring-context:3.1.2.RELEASE (*)
    |    |    |    \--- org.springframework:spring-core:3.1.2.RELEASE (*)
    |    |    +--- org.springframework:spring-core:3.1.2.RELEASE (*)
    |    |    +--- org.springframework:spring-expression:3.1.2.RELEASE (*)
    |    |    \--- org.springframework:spring-web:3.1.2.RELEASE (*)
    |    +--- wsdl4j:wsdl4j:1.6.1
    |    +--- commons-logging:commons-logging:1.1.1
    |    +--- org.springframework:spring-core:3.1.2.RELEASE (*)
    |    \--- org.springframework:spring-beans:3.1.2.RELEASE (*)
    

    I thought there could be some bug in the dependency resolution because it seems that Spring 2 jars are mixed up with Spring 3 jars, according to the resolved dependencies graph above! But no... it's exactly right.

    But anyway, I got this working after some investigation...

    Investigating unmet bundle requirements

    First of all, I noticed that Spring AOP did not resolve because of its org.aopalliance.aop requirement.

    This should come, obviously, from the aopalliance jar (which is not a bundle, at least not the one in Maven Central/JCenter).

    I added this instruction inside the runOsgi block in the Gradle file, so that I could see how osgi-run wrapped the jar into a bundle:

    wrapInstructions {
        printManifests = true
    }
    

    Running gradle clean createOsgi again, the manifest gets printed... it looks like this:

    --------------------------------- Manifest for aopalliance-1.0.jar ---------------------------------
    Manifest-Version: 1.0
    Bundle-SymbolicName: aopalliance
    Bundle-ManifestVersion: 2
    Bnd-LastModified: 1474120107912
    Import-Package: org.aopalliance.aop
    Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.3))"
    Tool: Bnd-3.1.0.201512181341
    Ant-Version: Apache Ant 1.5.4 
    Originally-Created-By: 1.4.2_01-b06 (Sun Microsystems Inc.)
    Export-Package: org.aopalliance.aop,org.aopalliance.intercept;uses:="o
     rg.aopalliance.aop"
    Bundle-Version: 1.0.0
    Bundle-Name: aopalliance
    Created-By: 1.8.0_60 (Oracle Corporation)
    
    ----------------------------------------------------------------------------------------------------
    

    Notice that there is a correctly generated Bundle-Version, but the packages are not exported with that version... by adding the instruction below, we can force the packages to be exported with a version:

    wrapInstructions {
        printManifests = true
        manifest( 'aopalliance.*' ) {
            instruction 'Export-Package', '*;version=1.0'
        }
    }
    

    Now, the Export-Package instruction in the manifest is correct:

    Export-Package: org.aopalliance.aop;version="1.0",org.aopalliance.inte
    rcept;version="1.0";uses:="org.aopalliance.aop"
    

    And running the OSGi container, we see that the Spring AOP bundle still does not resolve, but now the only problem is because its (&(osgi.wiring.package=org.apache.commons.logging)(version>=1.1.1)(!(version>=2.0.0))) requirement is not satisfied.

    The commons.logging jar has a known issue (documented in the osgi-run README page)... it declares optional dependencies, making it hard to wrap automatically.

    But additionally, the commons.logging jar has an incorrect Specification-Version in the manifest. It says 1.0 instead of 1.1.1, and that's what osgi-run uses for the bundle version, so Bundle-Version gets the incorrect value.

    By also forcing osgi-run to export the packages with the correct version, the wrapping works correctly and the Spring AOP starts up correctly:

    manifest( /commons-logging.*/ ) {
        instruction 'Import-Package', '!javax.servlet,!org.apache.*,*'
        instruction 'Export-Package', '*;version=1.1.1'
    }
    

    Now, moving on to the next issue, we notice that org.springframework.web does not resolve because of its requirement on (&(osgi.wiring.package=javax.servlet)(version>=2.4.0)(!(version>=4.0.0))).

    This one is easy, add the servlet-api bundle (provided by Felix) to the OSGi runtime and it should work... Just add this dependency in the Gradle file:

    osgiRuntime 'org.apache.felix:org.apache.felix.http.servlet-api:1.1.2'
    

    Now, the OSGi environment starts up without any failures!

    Final solution

    Here's the full set of bundles installed:

       ID|State      |Level|Name
        0|Active     |    0|System Bundle (5.4.0)|5.4.0
        1|Active     |    1|aopalliance (1.0.0)|1.0.0
        2|Active     |    1|Commons Lang (2.5.0)|2.5.0
        3|Active     |    1|Jakarta Commons Logging (1.0.0)|1.0.0
        4|Active     |    1|Apache Felix Gogo Command (0.16.0)|0.16.0
        5|Active     |    1|Apache Felix Gogo Runtime (0.16.2)|0.16.2
        6|Active     |    1|Apache Felix Gogo Shell (0.12.0)|0.12.0
        7|Active     |    1|Apache Felix Servlet API (1.1.2)|1.1.2
        8|Active     |    1|Spring AOP (3.1.2.RELEASE)|3.1.2.RELEASE
        9|Active     |    1|Spring ASM (3.1.2.RELEASE)|3.1.2.RELEASE
       10|Active     |    1|Spring Beans (3.1.2.RELEASE)|3.1.2.RELEASE
       11|Active     |    1|Spring Context (3.1.2.RELEASE)|3.1.2.RELEASE
       12|Active     |    1|Spring Context Support (3.1.2.RELEASE)|3.1.2.RELEASE
       13|Active     |    1|Spring Core (3.1.2.RELEASE)|3.1.2.RELEASE
       14|Active     |    1|Spring Expression Language (3.1.2.RELEASE)|3.1.2.RELEASE
       15|Active     |    1|Spring Object/XML Mapping (3.1.2.RELEASE)|3.1.2.RELEASE
       16|Active     |    1|Spring Web (3.1.2.RELEASE)|3.1.2.RELEASE
       17|Active     |    1|Spring Web Servlet (3.1.2.RELEASE)|3.1.2.RELEASE
       18|Active     |    1|Spring Web Services Core (2.1.1.RELEASE)|2.1.1.RELEASE
       19|Active     |    1|Spring XML (2.1.1.RELEASE)|2.1.1.RELEASE
       20|Active     |    1|tomcat-servlet-api (8.0.0)|8.0.0
       21|Active     |    1|JWSDL (1.2.0)|1.2.0
    

    And in case you want to try osgi-run, this is the Gradle file I used:

    plugins {
        id "com.athaydes.osgi-run" version "1.5.1"
    }
    
    repositories {
        mavenLocal()
        jcenter()
    }
    
    dependencies {
        osgiRuntime 'org.springframework.ws:spring-ws-core:2.1.1.RELEASE'
        osgiRuntime 'org.apache.felix:org.apache.felix.http.servlet-api:1.1.2'
    }
    
    runOsgi {
        wrapInstructions {
            printManifests = true
            manifest( 'aopalliance.*' ) {
                instruction 'Export-Package', '*;version=1.0'
            }
            manifest( /commons-logging.*/ ) {
                instruction 'Import-Package', '!javax.servlet,!org.apache.*,*'
                instruction 'Export-Package', '*;version=1.1.1'
            }
        }
    }
    

    Just save this in a build.gradle file, then run gradle createOsgi to get your starter script at build/osgi/run.sh.

    I will work on osgi-run to try to make things like these get resolved automatically, hopefully in the next version, the first build file shown above will work without further ado.