Spring boot createJarFileFromFileEntry - spring-boot

I have a spring boot app and I start it with -Dloader.path=. One of the jar files is hive-exec.jar. This has a jar file bundled called minlog-1.2.jar. If I specify this file in -Dloader.path, I get an error,
java.lang.IllegalStateException: Unable to open nested entry 'minlog-1.2.jar'. It has been compressed and nested jar files must be stored without compression. Please check the mechanism used to create your executable jar file
at org.springframework.boot.loader.jar.JarFile.createJarFileFromFileEntry(JarFile.java:378)
at org.springframework.boot.loader.jar.JarFile.createJarFileFromEntry(JarFile.java:355)
at org.springframework.boot.loader.jar.JarFile.getNestedJarFile(JarFile.java:341)
at org.springframework.boot.loader.archive.JarFileArchive.getNestedArchive(JarFileArchive.java:108)
at org.springframework.boot.loader.archive.JarFileArchive.getNestedArchives(JarFileArchive.java:92)
at org.springframework.boot.loader.PropertiesLauncher.getClassPathArchives(PropertiesLauncher.java:445)
at org.springframework.boot.loader.Launcher.launch(Launcher.java:60)
at org.springframework.boot.loader.PropertiesLauncher.main(PropertiesLauncher.java:564)
However, if I copy this file into a folder and add that folder in -Dloader.path, I don't get any error.
What am I missing?
Thank You,

I am trying to manage the database driver as external jar instead of a project's maven's dependency. The application use the JPA framework, and we wanted to be able to switch from one SQL-database implementation from one environment to another (for example, H2 in DEV, Oracle in production). I had to manage the h2-database-driver jar as an external dependency. While loading it with the "-Dloader.path" command line option, I came accross he same problem as you described.
Viewing the org.springframework.boot.loader.jar.JarFile source code, the class manage an folder-entry and a jar-entry differently.
The method getNestedArchives seems to invoke the createJarFileFromFileEntry method which throw this exception.
There must be a good reason for it, if someone know about it, any comment is welcomed!
When loading jar dependencies from a directory, the java.util.jar.JarFile constructor is used instead and does not seem to throw any exception...
In the createJarFileFromFileEntry method, instad of throwing an exception, wouldn't it be possible to only have a log.warn and not throw an exception?

Related

How can I tell Spring Boot to place some of the classes at the root of the jar instead of BOOT-INF?

I'm trying to set a custom log Handler in my Spring Boot (version 2.6.3) application. The result is a ClassNotFound as described in this other question
Can't override java.util.logging.LogManager in a Spring Boot web application: Getting java.lang.ClassNotFoundException on already loaded class
Based on the answer to that question, it seems I need my Handler and all its dependencies to be placed into the root of the executable jar.
Is there a direct way to accomplish this during the Maven build, i.e. not by extracting and repackaging the jar myself post-build?
This issue is a result of BOOT-INF fat jar structure introduced by Spring Boot 1.4.
There is currently no straightforward solution, and it appears some of the Spring Boot maintainers do not agree there is a problem, so it could be a long time before the situation changes:
Issue #6626: Make it easier to package certain content in the root of a fat jar
Issue #12659: Starting executable war with -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager produces a ClassNotFoundException
WORKAROUND #1
I had to do two things to get my application working again with a custom log handler. 1) use Maven Shade to package up the log handler with all its dependencies, and 2) launch the app with using the PropertiesLauncher class in the command line instead of using java -jar:
java -cp executable.jar:logger-shaded.jar -Dloader.main=mypackage.myapp org.springframework.boot.loader.PropertiesLauncher
The executable.jar, logger-shaded.jar, and mypackage.myapp are placeholders specific to my project, so adjust accordingly.
WORKAROUND #2
If the handler is loaded from code in a config class or from main() instead of being specified in the file loaded via java.util.logging.config.file, as discussed in the comments to the answer in this other question, then everything works as expected. I actually prefer this over Workaround #1 as it results in a smaller deployment, but it does require writing a few more lines of code.

How to add custom launcher to spring-boot-maven's executable jar

How can I add a custom loader to the executable jar?
At the moment i'm doing it manually by opening the jar file, pasting the class and re-signing it the end. -.-
Thanks in advance,
Pedro Silva
Executable jar restrictions
There are 2 types of restrictions that you need to consider when working with a Spring Boot Loader packaged application.
Zip entry compression
The ZipEntry for a nested jar must be saved using the ZipEntry.STORED method. This is required so that we can seek directly to individual content within the nested jar. The content of the nested jar file itself can still be compressed, as can any other entries in the outer jar.
System ClassLoader
Launched applications should use Thread.getContextClassLoader() when loading classes (most libraries and frameworks will do this by default). Trying to load nested jar classes via ClassLoader.getSystemClassLoader() will fail. Please be aware that java.util.Logging always uses the system classloader, for this reason you should consider a different logging implementation.
Alternative single jar solutions
If the above restrictions mean that you cannot use Spring Boot Loader the following alternatives could be considered:
Maven Shade Plugin
JarClassLoader
OneJar
Resource Link:
The executable jar format
spring boot loading jars (application dependencies and external file system jars)

gradle jettyrun oracle no suitable driver found

This is a kind of driving me nuts, I found some similar questions here on SO but I can't get it to work.
I have a multi project spring (web) project. It starts fine, but as soon as I want to query the db I get a
Nested in org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is org.apache.commons.dbcp.SQLNestedException:
Cannot create JDBC driver of class 'oracle.jdbc.OracleDriver' for connect URL 'jdbc:oracle:thin//server:1521:XE':
I tried many things: I made sure the ojdbc.jar is inside my project (not part of maven repo so I loaded it with gradle like
compile files('lib/websphere_apis.jar','lib/ojdbc14.jar')
in my sub (not web) or in my web project.
Which gets the jars inside war when I generate them with with gradle war
I tried adding the jars to the jettyRun configuration with
jettyRun {
additionalRuntimeJars = files('lib/ojdbc14.jar')
}
I'm a bit in doubt whether the files stmt is correct but still it doesn't work.
I also found on SO that I should put
providedRuntime files("$projectDir/../lib/ojdbc14.jar")
inside my webapp as a standard dependency but that still doesn't work for me. I have used postgres driver jars before which I could just include as a dependency.
With JDBC .jar files, if you are not careful about where you load the .jar on the classpath, you might get an error like what you are seeing. If you want to be sure, pass the .jar on the classpath as an argument to the main JVM that is calling the program. The reason is that you need to make sure the .jar class files are loaded in the "default JVM classloader". If you try to load JDBC .jars dynamically through other means ( perhaps even as OSGI) , or dynamically loaded by custom classloader like Tomcat has, then you could get class loader issues.

cxf webservice in standalone spring application packaged as jar-with-dependencies using maven

When running the packaged app like "java -jar my-app-0.0.1-SNAPSHOT-jar-with-dependencies.jar", I get the following error:
org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 47 in
XML document from URL [jar:file:/.../cxf/javafirst/target/my-app-0.0.1-SNAPSHOT-jar-with-dependencies.jar!/application-context.xml]
is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 47;
columnNumber: 61; cvc-complex-type.3.2.2: Attribute 'sendServerVersion'
is not allowed to appear in element 'httpj:engine'.
This is due to an outdated http-jetty.xsd schema published at apache.org. And that is NOT my problem. My problem is that this does not happen in cases like:
maven jetty:run
or
maven exec:java
where the service runs directly against the unpacked binary directories and obviously finds the updated and correct http-jetty.xsd schema file.
I want to do basically one thing:
put the webservice into one jar including dependencies. That should also include all XML schema files because, obviously, it is not a good idea to make a web service dependent on outside resources.
What's the best way to do that using maven?
My possibly naive solution would be to copy the XML schema files manually into the resources directory and tell CXF to resolve them in the jar file. Therefore I have two more specific questions:
1.) Is it possible to let maven find the XML schema files and copy them into appropriate places?
2.) What's the recommended way to make CXF look up the schema files in the jar file?
3.) Is there any better, best-practice solution to that problem?
My maven configuration regarding the maven assembly plugin is the direct combination of the last two sections at http://maven.apache.org/plugins/maven-assembly-plugin/usage.html.
Spring has a very good mechanism to resolve the schema files that it requires - it typically does not download it from the web at all, instead using locally available files within jar files to get the schema and validate the xml, for eg. consider the context custom namespace schema in Spring, if you look at the META-INF/spring.schemas file in spring-context.jar file, you will see an entry along these lines:
http\://www.springframework.org/schema/context/spring-context-3.1.xsd=org/springframework/context/config/spring-context-3.1.xsd
basically what it is saying is to resolve the spring-context-3.1.xsd files from the classpath org.springframework.context.config.spring-context-3.1.xsd file
This applies for any third party library also, which in your case is http-jetty.xsd.
I think what I would recommend is to simply create a spring.schemas file in your jar file in META-INF/ folder, put an entry for the full path to the schema and replace it with a classpath version of http-jetty.xsd.

Ibatis2 and test context

I'm having a stupid configuration issue with Ibatis in my Spring project. Please don't jump on me about how all this was setup, I'm just following the "in house project structure policy".
So here is the structure, we have the "src/main/resources/META-INF/" folder that contains all of our config files used by the application, and then there is a "src/test/resources/META-INF/" that contains only the config files that have different settings to run unit testing.
Well in our case that's only one file, the src/main/resources/META-INF/spring/application-config.xml became the src/test/resources/META-INF/spring/test-application-config.xml. I'm am not going to outline the small differences between the two, because that part works fine.
The test-application-config.xml imports the src/main/resources/META-INF/spring/data-access-config.xml file just fine, which in turns use the src/main/resources/META-INF/ibatis/sqlmap-config.xml successfully... After that is when it goes to Hell.
See up until now we're using Spring to find the next config files in the classpath, but when we hit sqlmap-config.xml we leave the spring framework for the ibatis framework I believe, which loads the resource files defined inside it relative to the classpath (that's taken from the doc, whatever that means).
Inside the sqlmap-config.xml are defined a few resource files we're using that live inside the src/main/resources/META-INF/ibatis/mapping folder.
They are referenced like this:
<sqlMapConfig><sqlMap resource="/META-INF/ibatis/mapping/MyObject.xml"/></sqlMapConfig>
That works fine when I run the app normally, but when I run my JUnit test cases I get an IO exception stating that it can't find the file /META-INF/ibatis/mapping/MyObject.xml.
I've tried to change the path in the sqlmap-config.xml to "mapping/MyObject.xml" but that didn't help. I've also tried to use the Spring classpath prefix "classpath:META-INF/ibatis/mapping/MyObject.xml", didn't work either.
Anyone would have any idea on how to set that Ibatis properly so it works for both the app and the junit?
Thanks.
To solve this problem, I removed all the the Ibatis files and folders from the src/test/resources/META-INF folder.
The sqlmap-config.xml in src/main/resources/META-INF/ibatis/mapping file now maps like this:
<sqlMapConfig><sqlMap resource="META-INF/ibatis/mapping/MyObject.xml"/></sqlMapConfig>
Please note that compared to my initial post the leading "/" is gone... I think that's what made the difference here.
Hopes this helps anyone running into similar issues.
Just to see whether what you are saying is actually the problem.. you might want to place your mappings (MyObject.xml) in the same folder as sqlmap-config.xml. I say this because I've had my fair share of spring + ibatis + unit testing problems. (see resolved question asked by me)
Also, you might be getting IO exception because the mappings file does not exist outside the container (when you run tests).
You should also post definition for bean created from SqlMapClientFactoryBean. This should have configLocation property that contains path to sqlMapConfig xml
I had the same problem and could not find a (quick) solution that explained what exactly could be going wrong. Hence my answer.
As Spring documentation for Ibatis says:
Remember that iBATIS loads resources from the class path, so be sure
to add the 'Account.xml' file to the class path.
In your case by adding META-INF to your webproject build path i.e. if you used Eclipse, set <classpathentry kind="src" path="META-INF"/> in your projects' .classpath (This will be visible under Navigator view in Eclipse)

Resources