Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OSGi ServiceLoader still not working #3177

Closed
alexgast opened this issue May 20, 2021 · 3 comments
Closed

OSGi ServiceLoader still not working #3177

alexgast opened this issue May 20, 2021 · 3 comments
Labels
Milestone

Comments

@alexgast
Copy link

Problem

When using "Flyway" (7.9.1) in some (OSGi-) Bundle X, the classloader of Bundle X is responsible for loading classes of the Bundle org.flywaydb.core, incl. the static parts of org.flywaydb.core.internal.database.DatabaseTypeRegister, i.e.

public static ClassLoader classLoader = DatabaseTypeRegister.class.getClassLoader()

equals the classloader of Bundle X.

But in OSGi Classloader X has no access to any resource of bundle org.flywaydb.core, i.e. the ServiceLoader folder is not
visible for this classloader and we end up with: #3173.

Solution

We need a classloader explicitly tied to bundle org.flywaydb.core, for instance <...>.getClass().getClassloader of a some internal instanciated class of org.flywaydb.core:

ServiceLoader<DatabaseType> loader = ServiceLoader.load(DatabaseType.class, new DatabaseTypeRegister().getClass().getClassLoader());

or equivalently

public class Clazz {

    public static final ClassLoader LOADER = new Clazz().getClass().getClassLoader();

}

and then

ServiceLoader<DatabaseType> loader = ServiceLoader.load(DatabaseType.class, Clazz.LOADER);
@jamiecounsell
Copy link

@alexgast this is still not working for me as of 7.11.13. Are you able to explain anything you did besides wrap the JAR in an OSGI Plugin to get this working?

@alexgast
Copy link
Author

alexgast commented Jul 27, 2021

OSGi a has strict concept of classloader visibility/isolation. In general a classloader of some OSGi Bundle (=Plugin) A (for instance a client bundle containg migration files: *.sql etc.) has no access to resources of a different Bundle B (for instance the flyway core bundle 'org.flywaydb.core'). As a consequence the last two lines of the Flyway constructor are not correct

   public Flyway(Configuration configuration) {
       this.configuration = new ClassicConfiguration(configuration);

       // Load callbacks from default package
       this.configuration.loadCallbackLocation("db/callback", false);

       // Set ClassLoader for ServiceLoader
       DatabaseTypeRegister.classLoader = configuration.getClassLoader();
       VersionPrinter.classLoader = configuration.getClassLoader();
   }

because "configuration.getClassLoader()" holds a classloader of Bundle A by Flyway.configure(getClass().getClassLoader()) ..., but this classloader only has access to Bundle A (which is good to load migration files from Bundle A, but not sufficient to load Servicloader resources from Bundle B).

Solution/Workaround:

remove the followng lines from Flyway constructor

        // Set ClassLoader for ServiceLoader
        DatabaseTypeRegister.classLoader = configuration.getClassLoader();
        VersionPrinter.classLoader = configuration.getClassLoader();

and optionally make ClassLoader classLoader private to prevent overriding, i.e.:

for class org.flywaydb.core.internal.database.DatabaseTypeRegister

private static ClassLoader classLoader = new DatabaseTypeRegister().getClass().getClassLoader();

and for org.flywaydb.core.internal.license.VersionPrinter:

private static ClassLoader classLoader = new VersionPrinter().getClass().getClassLoader();

A deeper discussion of Serviceloader and OSGi issues (maybe needed to get flyway extensions working) you can find here: https://aries.apache.org/documentation/modules/spi-fly.html.

@jamiecounsell
Copy link

Thanks @alexgast! For my fellow OSGI devs that end up here, 7.11.4 fixed the issue Alex is describing. However, I was still having an issue causing Flyway to not recognize my migrations. This is because the bundle:// protocol is only supported when the system detects an OSGI environment. It does this by attempting to load the class org.osgi.framework.Bundle.

ClassUtils.isPresent("org.osgi.framework.Bundle", classLoader);

However, of course when Flyway does this with its own classLoader, this isn't available. The solution is to ensure there is an Import-Package line in your Flyway bundle's MANIFEST.MF for the OSGI Framework.

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Flyway
Bundle-SymbolicName: org.flywaydb.flyway-core
Bundle-Version: 7.11.4
Automatic-Module-Name: org.flywaydb.flyway-core
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: javax.sql, org.osgi.framework

With 7.11.4 and this Import-Package line, it works!

jamiecounsell added a commit to jamiecounsell/flyway that referenced this issue Jul 30, 2021
As seen in flyway#3177, DatabaseTypeRegister shouldn't really be using the classloader provided in the configuration. In OSGI, this causes Flyway to be unable to find the database drivers, since they are only visible to the Flyway bundle and the user's bundle is only really needed to provide resources like SQL migrations.
jamiecounsell added a commit to jamiecounsell/flyway that referenced this issue Jul 30, 2021
As seen in flyway#3177, DatabaseTypeRegister shouldn't really be using the classloader provided in the configuration. In OSGI, this causes Flyway to be unable to find the database drivers, since they are only visible to the Flyway bundle and the user's bundle is only really needed to provide resources like SQL migrations.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants