nitishagar
nitishagar

Reputation: 9403

Using resource in kotlin func - does not work with fat jar (one jar)

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.

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
...

Upvotes: 2

Views: 4495

Answers (1)

Jayson Minard
Jayson Minard

Reputation: 85946

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.

Upvotes: 6

Related Questions