简体   繁体   中英

Read xml from an external jar not included in classpath

I created a javafx project using Netbeans, the project itself works just fine.

I'm now trying to implement a custom light-weight plugin system, the plugins are external JARs located inside the plugins/ directory of the main project. I'm using javax.security package to sandbox the plugins.

Here's the main project's structure:

MainProject
  |
  |---plugins/
  |   |---MyPlugin.jar
  |
  |---src/
  |   |---main.app.plugin
  |       |---Plugin.java
  |       |---PluginSecurityPolicy.java
  |       |---PluginClassLoader.java
  |       |---PluginContainer.java
  ....

And the plugin's one:

Plugin
  |
  |---src/
  |   |---my.plugin
  |   |   |---MyPlugin.java
  |   |--settings.xml
  |
  |---dist/
      |---MyPlugin.jar
          |---META-INF/
          |   |---MANIFEST.MF
          |---my.plugin
          |   |---MyPlugin.class
          |---settings.xml

To load the plugins into the program i've made a PluginContainer class that gets all the jar files from the plugins directory, lists all file inside the jar and lookup for the plugin file and the settings file.

I can load and make an instance of the plugin class, but when it comes to the XML there's no way i can even list it among the jar contents.

Here's the code, maybe someone can see where i did it wrong.

PluginSecurityPolicy.java

import java.security.AllPermission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
import java.security.ProtectionDomain;

public class PluginSecurityPolicy extends Policy {

    @Override
    public PermissionCollection getPermissions(ProtectionDomain domain) {
        if (isPlugin(domain)) {
            return pluginPermissions();
        } else {
            return applicationPermissions();
        }        
    }

    private boolean isPlugin(ProtectionDomain domain) {
        return domain.getClassLoader() instanceof PluginClassLoader;
    }

    private PermissionCollection pluginPermissions() {
        Permissions permissions = new Permissions();
        //
        return permissions;
    }

    private PermissionCollection applicationPermissions() {
        Permissions permissions = new Permissions();
        permissions.add(new AllPermission());
        return permissions;
    }
}

PluginClassLoader.java

import java.net.URL;
import java.net.URLClassLoader;

public class PluginClassLoader extends URLClassLoader {

    public PluginClassLoader(URL jarFileUrl) {
        super(new URL[] {jarFileUrl});
    }
}

PluginContainer.java, the #load method is the one

import main.app.plugin.PluginClassLoader;
import main.app.plugin.PluginSecurityPolicy;
import java.io.File;
import java.net.URL;
import java.security.Policy;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class PluginContainer {
    private ArrayList<Plugin> plugins;
    private ManifestParser parser;

    public PluginContainer() {
        Policy.setPolicy(new PluginSecurityPolicy());
        System.setSecurityManager(new SecurityManager());
        plugins = new ArrayList<>();
        parser = new ManifestParser();
    }

    public void init() {
        File[] dir = new File(System.getProperty("user.dir") + "/plugins").listFiles();
        for (File pluginJarFile : dir) {
            try {
                Plugin plugin = load(pluginJarFile.getCanonicalPath());
                plugins.add(plugin);
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
    }

    public <T extends Plugin> T getPlugin(Class<T> plugin) {
        for (Plugin p : plugins) {
            if (p.getClass().equals(plugin)) {
                return (T)p;
            }
        }
        return null;
    }

    private Plugin load(String pluginJarFile) throws Exception { 
        PluginManifest manifest = null;
        Plugin plugin = null;

        // Load the jar file
        ZipFile jarFile = new ZipFile(pluginJarFile);            

        // Get all jar entries
        Enumeration allEntries = jarFile.entries();
        String pluginClassName = null;

        while (allEntries.hasMoreElements()) {

            // Get single file
            ZipEntry entry = (ZipEntry) allEntries.nextElement();
            String file = entry.getName();

            // Look for classfiles
            if (file.endsWith(".class")) {

                // Set class name
                String classname = file.replace('/', '.').substring(0, file.length() - 6);

                // Look for plugin class
                if (classname.endsWith("Plugin")) {

                    // Set the class name and exit loop
                    pluginClassName = classname;
                    break;
                }
            }
        }

        // Load the class
        ClassLoader pluginLoader = new PluginClassLoader(new URL("file:///" + pluginJarFile));
        Class<?> pluginClass = pluginLoader.loadClass(pluginClassName);

        // Edit as suggested by KDM, still null
        URL settingsUrl = pluginClass.getResource("/settings.xml");
        manifest = parser.load(settingsUrl);

        // Check if manifest has been created
        if (null == manifest) {
            throw new RuntimeException("Manifest file not found in " + pluginJarFile);
        }

        // Create the plugin
        plugin = (Plugin) pluginClass.newInstance();
        plugin.load(manifest);

        return plugin;
    }
}

And the autogenerated MANIFEST.MF

Manifest-Version: 1.0
Ant-Version: Apache Ant 1.9.4
Created-By: 1.8.0_25-b18 (Oracle Corporation)

The Class-Path directive is missing, but if i force it to . or ./settings.xml or settings.xml (by manually editing the MANIFEST.MF file) it won't work either.

This is all I can think of, Thanks in advance for any help

[ EDIT ] I've created an images/monitor-16.png into the plugin jar root, added the #load2 method into the PluginContainer .

Since the method is called within a loop I left the Policy.setPolicy(new PluginSecurityPolicy()); and System.setSecurityManager(new SecurityManager()); inside the constructor.

Here's the new plugn jar structure:

TestPlugin.jar
    |
    |---META-INF/
    |   |---MANIFEST.MF
    |
    |---dev.jimbo
    |   |---TestPlugin.class
    |
    |---images
    |   |---monitor-16.png
    |
    |---settings.xml

The new method code:

private Plugin load2(String pluginJarFile) throws MalformedURLException, ClassNotFoundException {        
    PluginClassLoader urlCL = new PluginClassLoader(new File(pluginJarFile).toURL());
    Class<?> loadClass = urlCL.loadClass("dev.jimbo.TestPlugin");

    System.out.println(loadClass);
    System.out.println("Loading the class using the class loader object. Resource = " + urlCL.getResource("images/monitor-16.png"));
    System.out.println("Loading the class using the class loader object with absolute path. Resource = " + urlCL.getResource("/images/monitor-16.png"));
    System.out.println("Loading the class using the class object. Resource = " + loadClass.getResource("images/monitor-16.png"));
    System.out.println();

    return null;
}

Here's the output

class dev.jimbo.TestPlugin
Loading the class using the class loader object. Resource = null
Loading the class using the class loader object with absolute path. Resource = null
Loading the class using the class object. Resource = null

The following program:

public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
    Policy.setPolicy(new PluginSecurityPolicy());
    System.setSecurityManager(new SecurityManager());
    PluginClassLoader urlCL = new PluginClassLoader(new File(
            "A Jar containing images/load.gif and SampleApp class").toURL());
    Class<?> loadClass = urlCL.loadClass("net.sourceforge.marathon.examples.SampleApp");
    System.out.println(loadClass);
    System.out.println("Loading the class using the class loader object. Resource = " + urlCL.getResource("images/load.gif"));
    System.out.println("Loading the class using the class loader object with absolute path. Resource = " + urlCL.getResource("/images/load.gif"));
    System.out.println("Loading the class using the class object. Resource = " + loadClass.getResource("images/load.gif"));
}

Produces the following output:

class net.sourceforge.marathon.examples.SampleApp
Loading the class using the class loader object. Resource = jar:file:/Users/dakshinamurthykarra/Projects/install/marathon/sampleapp.jar!/images/load.gif
Loading the class using the class loader object with absolute path. Resource = null
Loading the class using the class object. Resource = null

So I do not think any problem with your class loader. Putting this as an answer so that the code can be formatted properly.

Nailed it! Seems that my previous Netbeans (8.0) was deleting the plugin directory from the added Jar/Folder Libraries references on Clean and Build action. I've downloaded and installed Netbeans 8.0.2 and the problem was solved. Couldn't find any related bug for that version on their tracker though..

Anyways Thanks for the help :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM