Task #2225

Add loaders and/or root paths of all installed bundles in Execute Groovy commands (macro, scripts and import)

Added by Sebastien Jacquot about 2 years ago. Updated 6 months ago.

Status:New Start date:06/26/2017
Priority:Normal Due date:
Assignee:- % Done:

50%

Category:Development Spent time: -
Target version:TXM 0.8.1

Description

Since some code is and will be extracted to plug-ins, the groovy engine needs to know where to look for some classes. This should be done without the use of Activators if possible.
Actual code to define the loader in ExecuteGroovyScript is based on Activators:

    if (gse == null) {
            String[] roots = new String[] { 
                    scriptRootDir+"/user/", //$NON-NLS-1$
                    scriptRootDir+"/macro/", //$NON-NLS-1$
                    scriptRootDir+"/import/", //$NON-NLS-1$
            }; 

            HashMap<String, Bundle> bundleWithActivators = new HashMap<String, Bundle>();
            HashSet<ClassLoader> loaders = new HashSet<ClassLoader>();
            BundleContext bundleContext = InternalPlatform.getDefault().getBundleContext();
            for (Bundle b : bundleContext.getBundles()) {
                String acName = b.getHeaders().get("Bundle-Activator");
                if (acName != null) {
                    if (!bundleWithActivators.containsKey(b.getSymbolicName())) {
                        bundleWithActivators.put(b.getSymbolicName(),b);
                    } else {
                        Bundle b2 = bundleWithActivators.get(b.getSymbolicName());
                        if (b2.getVersion().compareTo(b.getVersion()) < 0) {
                            bundleWithActivators.put(b.getSymbolicName(),b);
                        }
                    }
                }
            }

            for (Bundle b : bundleWithActivators.values()) {
                try {
                    String acName = b.getHeaders().get("Bundle-Activator");
                    Class<?> ac = b.loadClass(acName);
                    loaders.add(ac.getClassLoader());
                    //FIXME: debug
                    //System.out.println("ExecuteGroovyScript.executeScript(): bundle with activators: " + ac.getClassLoader());
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
            Log.info("Initialized TXMClassLoader with "+loaders.size()+ " bundles.");
            TXMClassLoader scl = new TXMClassLoader(loaders);

            try {
                gse = new GSERunner(roots, scl);
            } catch (IOException e) {
                e.printStackTrace();
            }

But the behavior is quite strange, if for example I put this in a macro:

// tests class loader des extensions depuis groovy
import org.txm.textsbalance.core.preferences.*

println "**********************************************" 
println TextsBalancePreferences.PREFERENCES_PREFIX;
println "**********************************************" 

Running the macro displays well the TextsBalancePreferences.PREFERENCES_PREFIX value.
But the plug-in org.txm.textsbalance.core has no Activator and its class loader is not set in the TXMClassLoader so I don't understand how the Groovy Engine can find the class.

Also, import groovy scripts like org.txm.importer.quick.quickLoader, etc., needs now to know the paths or loaders of some other plug-ins, especially the new plug-in org.txm.utils.

Solution

  • find a way to retrieve class loaders and/or root paths of all installed bundle even if they have no Activators
  • mutualize the code of the Groovy Engine initialization of commands: /org.txm.rcp/src/main/java/org/txm/rcpapplication/commands/ExecuteImportScript.java and /org.txm.rcp/src/main/java/org/txm/rcpapplication/commands/ExecuteGroovyScript.java
  • => plus, actual ExecuteImportScript command behavior is to check if the current import script in TXM install directory is newer than the one in user scripts directory and replaces the second if it's the case. Is it really the wanted behavior? Since each TXM update involving groovy modifications erases the user scripts?
  • => I suggest to not use the user scripts directory directly for import anymore but to use the scripts in the future import plug-ins themselves, the ones in the TXM install directory. Behavior could be, if a user script for the specified import exists => use it, otherwise use the one in the TXM install directory (default behavior). It also should be more simple to maintain (knowing the version of the scripts, reversing install, etc.) and avoid some useless copy and check at install and update. Since users that modify the included import groovy scripts are pretty rare and surely advanced users, it should not be a problem for them to get the original import scripts in TXM install directory and manually copy them in the user directory (but it also could be automatized within the preferences interface or an advanced command)
  • Tell me what do you think about this and I'll may do an new issue.

Validations tests

[WIP]

  • check that all works well with lazy bundles loading
  • import some corpora with some different import types
  • execute some user Groovy scripts that uses some classes outside the host plug-ins
  • execute some Macro Groovy scripts
  • execute some text

Improvements

[WIP]

  • add only the class loaders based on some values, eg. Vendor = Textometrie.org, etc.

History

#1 Updated by Sebastien Jacquot about 2 years ago

  • % Done changed from 0 to 50

New method based on BundleWiring has been implemented in /org.txm.rcp/src/main/java/org/txm/rcpapplication/utils/GSERunner.java. The method adds the class loaders of all the installed bundles of the platform so the script can access all plug-ins (Activator or w/o Activator) exposed classes. It is now in use in /org.txm.rcp/src/main/java/org/txm/rcpapplication/commands/ExecuteImportScript.java and /org.txm.rcp/src/main/java/org/txm/rcpapplication/commands/ExecuteGroovyScript.java:

    /**
     * Creates a new GSERunner and adds to it the class loaders of all installed bundles of the platform. 
     * @return a new GSERunner
     */
    public static GSERunner createGSERunner(String[] urls)    {

        GSERunner gse = null;

        HashSet<ClassLoader> loaders = new HashSet<ClassLoader>();
        BundleContext bundleContext = InternalPlatform.getDefault().getBundleContext();

        for (Bundle b : bundleContext.getBundles()) {
            BundleWiring bundleWiring = b.adapt(BundleWiring.class);
            loaders.add(bundleWiring.getClassLoader());

            //FIXME: debug
            System.out.println("GSERunner.createGSERunner(): class loader added: " + bundleWiring.getClassLoader());
        }

        Log.info("Initializing TXMClassLoader with " + loaders.size() + " bundles.");
        TXMClassLoader scl = new TXMClassLoader(loaders);

        try {
            gse = new GSERunner(urls, scl);
        }
        catch (IOException e) {
            e.printStackTrace();
        }

        return gse;
    }

#2 Updated by Sebastien Jacquot about 2 years ago

  • Description updated (diff)

#3 Updated by Sebastien Jacquot about 2 years ago

  • Description updated (diff)

#4 Updated by Sebastien Jacquot 6 months ago

  • Target version changed from TXM 0.8.0a (split/restructuration) to TXM 0.8.1

Also available in: Atom PDF