0

I have a problem with loading resource bundles in loaded jars. The main program is loading jars from a folder with a plugin manager. When an object of the main class of a jar is initialized by the plugin manager, resource bundles of this jar can be loaded. By this, I mean in a static block or in a constructor. Otherwise, an MissingResourceException is thrown. Like when you call a method on that object, that tries to load an existing resource-bundle

Currently, I use a static block at the beginning of the main class of a jar to load all resource bundles of the plugin with possible locales. Because of this, the resource bundles will be cached for some time. Also, my current way seems to work out for sub-loaded jars the same way as for the loaded jar

public class PluginMain implements PluginInterface {    
     static {
        for (Locale availableLocale : getAvailableLocales()) {
                try {
                    ResourceBundle resourceBundle = ResourceBundle.getBundle(BUNDLE_PATH, availableLocale);
                } catch (MissingResourceException e) {
                    e.printStackTrace();
                }
            }
     }
     ...
}

I think it's about the classLoader that is loading the resource-bundle. Still i cannot find a good solution. I already tried to find some solutions. The best i could find fitting is Loading with ResourceBundle from inside a jar, but that did not work out.

Edit: I load my jars like this

public class PluginManagerImpl implements PluginManager {
    private final List<PluginInterface> loadedPlugins = new ArrayList<>();

    private final String path;

    public PluginManagerImpl(String path) {
        File pluginsDir = new File(path, "plugins");
        this.path = pluginsDir.getPath();

        if (pluginsDir.exists()) {
            //pluginsfolder exists
            File[] files = pluginsDir.listFiles();
            if (files != null) {
                for (File f : files)
                    if (!f.isDirectory()) {
                        loadPlugin(f);
                    }
            }
        } else {
            //pluginsfolder does not exist
            if (pluginsDir.mkdir()) {
                Output.WriteLine("Dictionary created: " + pluginsDir.getPath());
            }
        }
    }

    @Override
    public void loadPlugin(File file) {

        URL urlFile;
        //trying to load file, convert it first to URI and then to URL
        try {
            urlFile = file.toURI().toURL();
        } catch (MalformedURLException e) {
            Output.WriteLineProblem(e.getMessage(), 4);
            return;
        }

        //trying to create JAR-file from file
        try (
                //use JarFIle and URLClassLoader as auto-closable
                JarFile jarFile = new JarFile(file);
                //use classloader of this class as parent classLoader
                URLClassLoader classLoader = new URLClassLoader(new URL[]{urlFile}, this.getClass().getClassLoader())
        ) {

            //load manifest
            Manifest manifest = jarFile.getManifest();
            //read attributes from manifest
            Attributes attributes = manifest.getMainAttributes();
            //get main class from attributes
            String main = attributes.getValue(Attributes.Name.MAIN_CLASS);
            if (main == null) {
                Output.WriteLineProblem(file.getName() + " has no main specified");
                return;
            }

            String title = attributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE);
            if (title == null) {
                //https://maven.apache.org/shared/maven-archiver/index.html
                Output.WriteLineProblem(file.getName() + " has no implementation title specified");
                return;
            }

            //https://javapapers.com/core-java/java-class-loader/

            //load class with classLoader of jarFile
            Class<?> cl = classLoader.loadClass(main);

            //get implemented interfaces of class
            Class<?>[] interfaces = cl.getInterfaces();
            //iterate over interfaces and check for PluginInterface.class
            boolean isPlugin = false;
            for (Class<?> anInterface : interfaces) {
                if (anInterface.equals(PluginInterface.class)) {
                    isPlugin = true;
                    break;
                }
            }

            if (isPlugin) {
                //load all classes in jar file
                loadClassesOfjarFile(jarFile, cl.getClassLoader());

                //add the pluginfile
                PluginInterface plugin = (PluginInterface) cl.getConstructor().newInstance();
                plugin.calledAfterInstancing(new File(path, title).getPath());

                Output.WriteLine("Loaded Plugin " + title);
                loadedPlugins.add(plugin);
            }
        } catch (Exception e) {
            Output.WriteLineProblem("Error on checking " + file.getName() + " for plugin");
            e.printStackTrace();
        }
    }

public static void loadClassesOfjarFile(JarFile jarFile, ClassLoader classLoader) {
        jarFile.entries().asIterator().forEachRemaining(jarEntry -> {
            String jarEntryName = jarEntry.getName();
            if ((jarEntryName.endsWith(".class"))) {
                String className = jarEntry.getName().replaceAll("/", "\\.");
                String myClass = className.substring(0, className.lastIndexOf('.'));

                try {
                    Class<?> clazz = classLoader.loadClass(myClass);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            } else if (jarEntryName.endsWith(".xml")) {
                String resourceName = jarEntry.getName().replaceAll("/", "\\.");
                classLoader.getResourceAsStream(jarEntry.getName());
            } 
        });
    }
}

Edit 2: Here a sample project to test The resource bundles are contained in the the resource folder of the plugin. Hierarchy of the project

Sample for the main program:

package main;
public class Main {
    public static final String DEFAULT_PATH =  FileSystems.getDefault().getPath("").toAbsolutePath().toString();
    public static void main(String[] args) {
        PluginManager plugins = new PluginManager(DEFAULT_PATH);

        List<PluginInterface> loadedPlugins = plugins.getLoadedplugins();

        for (PluginInterface loadedPlugin : loadedPlugins) {
            loadedPlugin.loadResourceBundle(Locale.ENGLISH);
        }
    }
}

Sample for plugin:

package plugin;
public class Main implements PluginInterface {

    static {
        Locale locale = Locale.ENGLISH;
        ResourceBundle main = ResourceBundle.getBundle("mainLoadedInStatic", locale);
        //only uncomment to check, that it would work if loaded in static
//        ResourceBundle mainNotLoadedInStatic = ResourceBundle.getBundle("mainNotLoadedInStatic", locale);
    }
    @Override
    public void loadResourceBundle(Locale locale) {
        ResourceBundle mainLoadedInStatic = ResourceBundle.getBundle("mainLoadedInStatic", locale);
        ResourceBundle mainNotLoadedInStatic = ResourceBundle.getBundle("mainNotLoadedInStatic", locale);
    }
}

The error should be:

Exception in thread "main" java.util.MissingResourceException: Can't find bundle for base name mainNotLoadedInStatic, locale en
    at java.base/java.util.ResourceBundle.throwMissingResourceException(ResourceBundle.java:2045)
    at java.base/java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1683)
    at java.base/java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1586)
    at java.base/java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1549)
    at java.base/java.util.ResourceBundle.getBundle(ResourceBundle.java:932)
    at plugin.Main.loadResourceBundle(Main.java:19)
    at main.Main.main(Main.java:18)
  • Can you explain that: `The main programm is loading plugins from a folder with a pluginmanager` more in detail what you exactly mean by that? Are you talking about a Maven plugin??? – khmarbaise Aug 09 '22 at 21:12
  • I am meaning separate compiled jars that can be used by my main program to add functionality. I added the code of the class I load my plugins with. – unpiixelbar Aug 10 '22 at 18:01
  • What is your main programm? Are we talking about a maven plugin? please elaborate more in detail...best would be a full working example... – khmarbaise Aug 11 '22 at 07:41
  • I have rewritten my Question. Maybe now it is more clear what I am meaning. – unpiixelbar Aug 12 '22 at 10:25

1 Answers1

0

I discovered that closing the URLClassLoader (as autocloseable) in loadPlugin of PluginManagerImpl was causing the Problem. The Resources are tried to be loaded with that URLClassLoader and if it is closed, it will fail. Which effect would occur, if the URLClassLoader doesn't get closed at all? As far as i understand this could have a negativ effect because of an unclosed JarFile.