2008-12-18 12 views
2

Je développe un plug-in Eclipse qui correspond à un modèle client-serveur. C'est un projet commercial donc nous ne pouvons pas redistribuer les pilotes JDBC pour les différentes bases de données que nous supportons avec le plug-in.Comment remplacer dynamiquement le chargeur de classe pour un plug-in Eclipse?

J'ai donc développé une page de préférences pour permettre à l'utilisateur de localiser les jars et d'avoir un mécanisme de découverte simple qui parcourt les classes dans les fichiers jar, en chargeant chacun pour vérifier qu'il implémente l'interface java.sql.Driver. Tout cela fonctionne très bien.

Mais le hic, c'est que j'utilise Hibernate. Et Hibernate utilise Class.forName() pour instancier le pilote JDBC.

Si j'essaie d'utiliser ce qui suit, j'obtiens ClassNotFoundException. Et si j'essaie de créer le pilote moi-même comme suit, j'obtiens une exception SecurityException.

public Object execute(final IRepositoryCallback callback) 
{ 
    final DatabaseDriverClassLoader loader = new DatabaseDriverClassLoader(
     Activator.getDefault().getDatabaseDriverRegistry()); 
    final ClassLoader oldLoader = Thread.currentThread() 
     .getContextClassLoader(); 
    try 
    { 
     Thread.currentThread().setContextClassLoader(loader); 
     final Class driverClass = loader.loadClass(this.connectionDriverClassName); 
     final Driver driver = (Driver)driverClass.newInstance(); 
     DriverManager.registerDriver(driver); 
     try 
     { 
      final Connection connection = DriverManager.getConnection(
       this.connectionUrl, this.connectionUsername, 
       this.connectionPassword); 
      final SessionFactory sessionFactory = this.configuration 
       .buildSessionFactory(); 
      if (sessionFactory != null) 
      { 
       final Session session = sessionFactory 
        .openSession(connection); 
       if (session != null) 
       { 
        // CHECKSTYLE:OFF 
        try 
        // CHECKSTYLE:ON 
        { 
         return callback.doExecute(session); 
        } 
        finally 
        { 
         session.close(); 
        } 
       } 
      } 
      connection.close(); 
     } 
     finally 
     { 
      DriverManager.deregisterDriver(driver); 
     } 
    } 
    // CHECKSTYLE:OFF 
    catch (Exception e) 
    // CHECKSTYLE:ON 
    { 
     RepositoryTemplate.LOG.error(e.getMessage(), e); 
    } 
    finally 
    { 
     Thread.currentThread().setContextClassLoader(oldLoader); 
    } 
    return null; 
} 

EDIT: Je ne suis pas sûr qu'il est la meilleure option, mais je pris l'approche de la mise en œuvre ma propre ConnectionProvider qui m'a permis instancier le conducteur en utilisant Class.forName() et j'ouvre alors la connexion à l'aide Driver.connect() au lieu de DriverManager.getConnection(). C'est assez basique, mais je n'ai pas besoin d'un pool de connexion dans mon cas d'utilisation spécifique.

La méthode configure() était la suivante:

public void configure(final Properties props) 
{ 
    this.url = props.getProperty(Environment.URL); 
    this.connectionProperties = ConnectionProviderFactory 
     .getConnectionProperties(props); 

    final DatabaseDriverClassLoader classLoader = new DatabaseDriverClassLoader(
     Activator.getDefault().getDatabaseDriverRegistry()); 

    final String driverClassName = props.getProperty(Environment.DRIVER); 
    try 
    { 
     final Class driverClass = Class.forName(driverClassName, true, 
      classLoader); 
     this.driver = (Driver)driverClass.newInstance(); 
    } 
    catch (ClassNotFoundException e) 
    { 
     throw new HibernateException(e); 
    } 
    catch (IllegalAccessException e) 
    { 
     throw new HibernateException(e); 
    } 
    catch (InstantiationException e) 
    { 
     throw new HibernateException(e); 
    } 
} 

Et la méthode getConnection() est la suivante:

public Connection getConnection() 
    throws SQLException 
{ 
    return this.driver.connect(this.url, this.connectionProperties); 
} 

Répondre

4

Class.forName() dans OSGi est une douleur importante. Ce n'est pas vraiment la faute de qui que ce soit, juste que les deux utilisent des chargeurs de classe qui ne fonctionnent pas comme le client l'attend (c'est-à-dire que le chargeur de classe OSGi ne fonctionne pas comme prévu).

Je pense que vous pouvez aller de quelques façons, mais ceux que je peux penser droit sont maintenant:

  • la manière propre, qui consiste à emballer les pilotes JDBC comme bundles OSGi. Contribuer à la classe en tant que service. Vous pouvez le faire avec des services déclaratifs (probablement mieux) ou écrire un activateur dont vous aurez besoin de gérer le démarrage. Lorsque vous êtes prêt à obtenir le pilote, obtenez le service JDBCDriver et recherchez la classe qui vous intéresse.
  • le moins propre, mais fonctionnera pour moins d'effort que le premier - utilisez DynamicImport-Package pour ajouter le les paquets exportés de vos pilotes groupés. De cette façon, le code client peut toujours voir la classe qu'il utilisera, mais il n'a pas besoin de le savoir avant l'exécution. Vous devrez peut-être expérimenter avec le modèle de paquet, cependant, pour couvrir tous les cas (c'est pourquoi il est moins propre).
  • la voie moins OSGi; qui consiste à ajouter vos pilotes au classpath eclipse et à ajouter le chargeur de classe parent de l'application. Vous pouvez ajouter ceci: osgi.parentClassloader=app à votre config.ini. Cela peut ne pas correspondre à votre déploiement, en particulier si vous n'avez pas le contrôle du fichier config.ini.
  • la méthode non-OSGi, au lieu d'utiliser le chargeur de classe de contexte, utilisez le URLClassLoader. Cela ne fonctionnera que si vous avez un répertoire rempli de fichiers jar, ou si l'utilisateur peut spécifier directement ou indirectement l'emplacement du fichier jar du pilote.
+0

Je garderai ce conseil en tête pour d'autres projets. Malheureusement, ces approches consistent à reconditionner et distribuer les pilotes des vendeurs avec mon outil. Je dois éviter cela en raison de problèmes de licence. –