Search code examples
javamavenjardependenciesresourcebundle

java.util.MissingResourceException - new File() does not reach into jar - how to provide external library with a path for this File?


The question started as - maven does not reach into jar for a folder bundle correctly, when running tests. It does work currectly when running some main() though.

The stack trace for running test (while building) looked like this:

Caused by: java.util.MissingResourceException: Can't find Not found profile: file:\C:\Users\Simon\.m2\repository\ario\TextProcessing\1.0.4-SNAPSHOT\TextProcessing-1.0.4-SNAPSHOT.jar!\lang bundle
at java.util.logging.Logger.setupResourceInfo(Logger.java:1942)
at java.util.logging.Logger.<init>(Logger.java:380)
at java.util.logging.LogManager.demandLogger(LogManager.java:554)
at java.util.logging.Logger.demandLogger(Logger.java:455)
at java.util.logging.Logger.getLogger(Logger.java:553)
at cz.techniserv.ario.tagger.TagDetect.setLangPath(TagDetect.java:126)
at cz.techniserv.ario.tagger.TagDetect.<init>(TagDetect.java:58)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:408)
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:147)
... 86 more

I triple checked - the lang folder is within the jar.

When running a main() within the module however, it does not try to reach into jar withing m2 repository, but will take the lang folder from within the opened dependency project. So it does not try to reach into a jar, just takes the data from project folder. This runs correctly.

Apparently, the File() constructor is unable to reach for the resource in the jar.

The code, that tries to reach into the jar is within a constructor. It looks like this:

this.path = this.getClass().getResource("/" + aLanguageDirectoryName).getPath();

and than there is a File constructor which takes this path.

In case of running the app it resolves to the project path. When it runs tests it will resolve to the jar within the m2 repository and tries to reach within the path that is in the exception:

file:\C:\Users\Simon\.m2\repository\ario\TextProcessing\1.0.4-SNAPSHOT\TextProcessing-1.0.4-SNAPSHOT.jar!\lang

What would you do? Would you copy the resource "lang" folder on some temporary path and provide the library with this new non-within-jar temporary path, so that it can open with the File() constructor? Or do you see another better way?


Solution

  • Ok, so what we did is that the data are ad hoc extracted to classpath and accessed.

    The problem with this solution is that if you delete the files after usage, than you will always extract and delete data with each run - when this happens with every spring initialization of running tests this can happen extreme amount of times and take a lot of time. Leaving the data there however means that if the path is not absolute and out of the project trunk folder, SCM will pick it up unless you ignore it and commit the data which is not satisfying. Also they get packed into the jar upon build since they are on classpath which is another drawback. Yes, you can ignore with SCM and configure maven to exclude the folder, however some developers will forget (one already did) to ignore with SCM and it was commited.

    We consider extracting to an absolute path which would not be on classpath a bad practice - firstly because you have no control on what system the projects run on so you cannot guess very well the absolute path - also it does not look pretty to throw data around your computer for bad design.

    So I guess the best thing would be to push everyone to place the data on some place on their discs and set an environment variable which would be the same for everyone. This makes the project less portable, requires more configuration, but removes formerly mentioned problems.

    I did not come up with anything better.

    In case anyone would want to do the same thing, here is our code:

        CodeSource src = this.getClass().getProtectionDomain().getCodeSource();
        if (src == null) {
            return null;
        }
        URL jarURL = src.getLocation();
        try (JarFile jar = new JarFile(jarURL.getPath());) {
            Enumeration<JarEntry> enumEntries = jar.entries();
            while (enumEntries.hasMoreElements()) {
                JarEntry fileFromJar = (JarEntry) enumEntries.nextElement();
                File toBeCreatedFileLocally = new File(NAME_FOR_TEMPORARY_FOLDER_TO_HOLD_LANG_DATA_FROM_JAR + File.separator + fileFromJar.getName());
                if (fileFromJar.isDirectory()) {
                    continue;
                }
                if (fileFromJar.getName().contains(aLanguageDirectoryName)) {
                    toBeCreatedFileLocally.getParentFile().mkdirs();
                    try (InputStream is = jar.getInputStream(fileFromJar); // get the input stream
                            FileOutputStream fos = new FileOutputStream(toBeCreatedFileLocally)) {
                        while (is.available() > 0) {  // write contents of 'is' to 'fos'
                            fos.write(is.read());
                        }
                    }
                }
            }
        } catch (IOException ex) {
            Logger.getLogger(TagDetect.class.getName()).log(Level.SEVERE, null, ex);
        }
    

    This code is actually a modification of a different answer here https://stackoverflow.com/a/1529707/1920149 (beware, that answer has a bug, check comments - they rejected my edit)