2

I have the following piece of code:

fun main(args: Array<String>) {
    val urlForCSR: URL = ClassLoader.getSystemClassLoader().getResource("merchant.id")
    // also tried ClassLoader.getSystemResource("merchant.id")
    ...

The following when run from intelliJ works fine and finds the resource. But when run using the bundled jar it gives a NullPointerException.

  • Path for resource: /src/main/resources/merchant.id
  • Path for code: /src/main/java/Route.kt

Following is the Maven config snippet:

    ...
    <!-- Make this jar executable -->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
                <archive>
                    <manifest>
                        <mainClass>RouteKt</mainClass> <!-- Class generated (for above main func - named Route.kt) -->
                    </manifest>
                </archive>
            </configuration>
        </plugin>

        <!-- Includes the runtime dependencies -->
        <plugin>
            <groupId>org.dstovall</groupId>
            <artifactId>onejar-maven-plugin</artifactId>
            <version>1.4.4</version>
            <executions>
                <execution>
                    <goals>
                        <goal>one-jar</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>

Is there any other war to get URL for the above resource which would work with one-jar or other way of making a fat jar.

Jar content:

jar tf target/Route-1.0-SNAPSHOT.jar
META-INF/
META-INF/MANIFEST.MF
merchant.id
RouteKt$main$1.class
RouteKt.class
META-INF/maven/
META-INF/maven/groupId/
META-INF/maven/groupId/TokenGenerator/
META-INF/maven/groupId/TokenGenerator/pom.xml
META-INF/maven/groupId/TokenGenerator/pom.properties

One-jar content:

META-INF/MANIFEST.MF
main/Route-1.0-SNAPSHOT.jar
lib/kotlin-stdlib-0.1-SNAPSHOT.jar
lib/kotlin-runtime-0.1-SNAPSHOT.jar
lib/spark-core-2.3.jar
lib/slf4j-api-1.7.12.jar
lib/slf4j-simple-1.7.12.jar
lib/jetty-server-9.3.2.v20150730.jar
...
Mibac
  • 8,990
  • 5
  • 33
  • 57
nitishagar
  • 9,038
  • 3
  • 28
  • 40
  • Important information is missing from your question. Where is `merchant.id` located in the source? Did you look inside the fat jar and see this file there as well? If it is not there, did you look at how to make sure resources are copied into the fat jar? Did you read other SO articles like http://stackoverflow.com/questions/5688329/onejar-with-maven-wont-copy-resource-into-jar? Or http://stackoverflow.com/questions/23276836/onejar-and-resource-loading? – Jayson Minard Jan 06 '16 at 04:42
  • @JaysonMinard Yes the file is there - as said in the question - this executes from intelliJ perfectly fine. Path for resource: `/src/main/resources/` Path for code: `/src/main/java/Route.kt` – nitishagar Jan 06 '16 at 04:43
  • Is the resource `merchant.id` within a JAR within the onejar `lib/` directory? I.e. your onejar fat JAR is `myApp.jar` and within it are many other JAR files in `lib/dependency1.jar` `lib/dependency2.jar` and within those is your `merchant.id` instead of the top level jar? – Jayson Minard Jan 06 '16 at 04:49
  • (note, it working in intellij isn't related information since OneJar completely changes what and how clases and resources are packaged compared to intellij running from the flat file system) – Jayson Minard Jan 06 '16 at 04:50
  • When the resource is within a JAR within the fat JAR, you have the problem described in: http://stackoverflow.com/questions/21361931/how-to-get-an-url-for-a-resource-directory-when-using-onejar and http://stackoverflow.com/questions/941754/how-to-get-a-path-to-a-resource-in-a-java-jar-file/941785 and ... and onejar uses its own class loader, not the system one you are using: http://stackoverflow.com/a/20636830/3679676 – Jayson Minard Jan 06 '16 at 04:52
  • Flagging as duplicate, see the answer below which points out all the duplicate questions in SO – Jayson Minard Jan 06 '16 at 04:57
  • I added a possible solution in my answer for you that I think works, but I cannot test. – Jayson Minard Jan 06 '16 at 05:03
  • @JaysonMinard I agree with the point on jar being bundled in one-jar. Will including resource in one-jar will help? – nitishagar Jan 06 '16 at 05:04
  • Try the answer I give below under "One way to defeat this" example – Jayson Minard Jan 06 '16 at 05:05
  • @JaysonMinard I only have func main in my code - how do I access MyClass/Class name there – nitishagar Jan 06 '16 at 05:05
  • just add a class to the same file `class MyApp {}`. You can also try using system class loader but with method `getResourceAsStream` instead of `getResource` since the URL may not be a valid URL when nesting into other JAR files. That could ALSO be your issue is that you grab the URL instead of an inputstream. – Jayson Minard Jan 06 '16 at 05:10
  • Read my full answer below, and ask questions there. This is no longer the right place to continue a discussion when all of my notes are in the answer I wrote, and edited for everything discussed here. – Jayson Minard Jan 06 '16 at 05:12
  • in your edited question you have the file name as `merchant.id.soft.applepaydev-v1.csr` instead of `merchant.id` therefore unless you shortened that to make this simpler question, that is ALSO a problem with finding the resource if the name does not match. – Jayson Minard Jan 06 '16 at 05:16
  • Ok, my answer is as complete as it can be, please read the options presented and comment on the answer. – Jayson Minard Jan 06 '16 at 05:22
  • So it worked? please add a comment to the answer saying which part worked. – Jayson Minard Jan 06 '16 at 05:28

1 Answers1

6

OneJar packages all of your code into a JAR file that also contains other JAR files in the lib/ directory or as a reference to them in other dirs outside the JAR (depending on the onejar system you are using). It uses a custom class loader to make this work. Therefore it defeats your use of the system class loader.

One way to defeat this is to use a class from the same JAR as the resource you want to load, and therefore its class loader is probably setup correctly into the JAR or nested JAR or magic location in Narnia of your resource:

val stream = MyClass::class.java.getResourceAsStream("/merchant.id")

Where MyClass is a class from the same JAR as merchant.id, and the path to the resource must be absolute with the leading /

Be sure to get the stream and not the resource URL which might not be usable to you since it isn't an understandable URL to the system. It could produce a URL for a JAR within a JAR which the rest of Java wouldn't understand, for example file:/path/to/jarfile/bot.jar!/config/file.txt (or worse!). It could work, but I'm not sure when it is a JAR within a JAR with a file in it.

A secondary option:

val stream = Thread.currentThread().contextClassLoader.getResourceAsStream("/merchant.id")

Check your resource names:

In your question you say to read resource merchant.id but then show JAR contents having merchant.id.soft.applepaydev-v1.csr ... make sure these names match if you are trying to load this exact resource.


Resource loading in onejar must be customized, which is ugly. In a link to one resource that talks about resource loading with onejar:

There are a number of ways that Java supports finding and opening resources:

  • URL findResource(String resource): returns a resource, or null if not found.
  • Enumeration findResources(String resource): returns an enumeration of resource URL's which match the given string
  • URL.openConnection(): opens a connection to a resource by its URL.

One-Jar supports all of these mechanisms within the context of a One-Jar file, which may contain Jar files that have duplicate resources.

This problem is referenced from these other Stack Overflow posts, therefore this is pretty much a duplicate question:

Googling turns up many more resources.

Community
  • 1
  • 1
Jayson Minard
  • 84,842
  • 38
  • 184
  • 227
  • What I did was to add another class with the function to access the resource. Then used the `MyClass::class.java.getResource` – nitishagar Jan 06 '16 at 05:30
  • `getResource` or `getResourceAsStream` ... interesting if `getResource` works, I should edit my answer then. – Jayson Minard Jan 06 '16 at 05:32
  • It does - I just didn't want the cruft of creating a new class. But `fun` in cotillon didn't allow me to do the same. – nitishagar Jan 06 '16 at 06:07
  • You can put your `main()` within the `companion object` of a class instead of letting it go to the default created class, add the `@JvmStatic` annotation and it can be run directly from your new class. Then you have both. – Jayson Minard Jan 06 '16 at 06:09