Spring Boot/Thymeleaf Executable Jar, Cannot find template location: classpath:/templates/ - spring

I've built an Restful API using Spring Boot and I'm deploying it as an executable Jar file using Maven. Build section of my POM file as follows;
<build>
<finalName>project-name</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
I've added Thymeleaf to my project as I need to display a HTML file. I have not added any additional configuration options to my application.properties file. I have created a simple HTML file in the folder '/src/main/resources/templates/' and I've created a controller to display the HTML file.
If I run the project within IntelliJ, I am able to view the HTML file via the controller.
If I run the project using 'mvn spring-boot:run', I am able to view the HTML file. via the controller.
However I am not able to view the HTML file when the project is ran from the executable Jar file that maven creates after I've ran 'mvn package'.
When the project is ran from the executable jar, I see the following output upon startup;
18:18:38.234 [main] WARN o.s.b.a.t.AbstractTemplateResolverConfiguration - Cannot find template location: classpath:/templates/ (please add some templates or check your Thymeleaf configuration)
I'm assuming from this that it's unable to locate the /templates/ folder. In an effort to debug the issue I extracted the executable jar and I see the following folder structure;
- BOOT-INF
- classes
- templates
- mails
reset.html
password-reset.html
- lib
- META-INF
- org
I can clearly see that the templates folder is in the jar along with the HTML file 'password-reset.html'.
I seem to be having a similar issue with another part of the project too, which loads the contents of a file into a string. The code below again works fine ran from IntelliJ and when running via 'mvn spring-boot:run', but it's not able to load the file when ran from the executable jar.
Resource resource = new ClassPathResource("templates/mails/reset.html");
BufferedReader br = new BufferedReader(new InputStreamReader(resource.getInputStream()), 1024);
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
stringBuilder.append(line).append('\n');
}
br.close();
body = stringBuilder.toString();
I'm guessing there is some issue with the classpath(?) in the project when it's ran from an executable jar. I wonder if anyone has any idea why I'm experiencing this issue and what I can potentially do to resolve it?

Related

With the spring boot Gradle plugin, is there a task for writing the manifest file without packaging it

I am writing a plug-in using legacy code (on which I don't have any writing rights) for a spring-boot project, and I need to have access to the manifest file in his final form, as produced by the bootJar task. Sadly, this file seems too be directly written to the jar file, without any intermediate file.
Do you know a way to generate the Manifest file in its final form without unzipping the final jar ?
Not sure it helps, and couldn't write it as a comment. I had the same challenge with git, to log the commit id.
build.gradle:
plugins {id 'com.gorylenko.gradle-git-properties'}
gitProperties
project structure:
build
-classes
-generated
-libs
--x.jar
---com
---META-INF
----MANIFEST.MF
---git.properties
src
...
java:
Properties props = new Properties();
InputStream is = getClass().getClassLoader().getResourceAsStream("git.properties");
props.load(is);
log.info("git props: " + props);
so maybe you can try ..getResourceAsStream("META-INF/MANIFEST.MF");
Now I understood you need the access not from the app, but from the plug-in, but still..
Please let us know if you solved the challenge.

spring-boot-starter-freemarker does not find templates

Using spring-boot-starter-freemarker without further config I would expect to be able to load templates from the default template path(src/resources/templates) (note it's src/... not build/...).
Having this file here:
src/resources/templates/emails/welcome.ftl
Trying to load it as a template:
// some service class
#Autowired
private Configuration freemarkerConfig;
public void doStuff() {
Template t = freemarkerConfig.getTemplate("emails/welcome.ftl");
// ...
}
Fails with this error message:
freemarker.template.TemplateNotFoundException: Template not found for name "emails/welcome.text.ftl".
The name was interpreted by this TemplateLoader: MultiTemplateLoader(loader1 = FileTemplateLoader(baseDir="/some/path/backend/build/resources/main/templates", canonicalBasePath="/some/path/backend/build/resources/main/templates/"), loader2 = ClassTemplateLoader(resourceLoaderClass=org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer, basePackagePath="" /* relatively to resourceLoaderClass pkg */)).
So the configuration seems kind of ok-ish, but instead of the src folder it is using the build folder. When running via./gradlew bootRun we see the error. Doing a ./gradlew buildand then ./gradlew bootRun the templates are found - because they are now in the build folder. But for development it would be much appreciated to not require a full re-build.
So, I know we now could configure freemarker manually to load from the src folder, but that feels hacky.
Am I doing something wrong or is this expected behavior?
You can configure bootRun so that sources are loaded from their source location. Assuming that the templates are part of the main source set, i.e. they're in src/main/resources, the configuration would be the following:
bootRun {
sourceResources sourceSets.main
}
This is described in the reference documentation for Spring Boot's Gradle plugin.

How do I locate resource files in a Spring Boot project deployed to Elastic Beanstalk?

My Spring Boot project contains an XML file within the src/main/resources folder, which is the common location for such a file.
Running locally and also on Pivotal CloudFoundary, I am able to locate the file and read it in, but on Beanstalk the process results in an empty file.
Code to locate and read file:
URL url = getClass().getResource("/myFile.xml");
LOG.info("File location: " + url.toString());
Resulting log entry:
File location: jar:file:/var/app/current/application.jar!/WEB-INF/classes!/myFile.xml
When I SSH into EC2 instance, I can find the jar in the specified directory.
Do I need to configure Maven to move this file somewhere?
UPDATE
I've since realized that I need to treat this file as in InputStream as it's packaged within the jar.
I'm now using the following code which results in the follow errors:
FileUtils.copyInputStreamToFile(new ClassPathResource("myFile.xml").getInputStream(), myFile);
java.lang.NullPointerException: null
at org.apache.commons.io.FileUtils.openOutputStream(FileUtils.java:345) ~[commons-io-2.5.jar:2.5]
and
FileUtils.copyInputStreamToFile(new ClassPathResource("classpath:myFile.xml").getInputStream(), myFile);
java.io.FileNotFoundException: class path resource [classpath:myFile.xml] cannot be opened because it does not exist
Thanks!
How does your pom.xml look like? Maybe there is a resource-filter active?
In a spring application you could use File file = ResourceUtils.getFile("classpath:myFile.xml"); to read a resource file.
Could you check the result by using ResourceUtils?
Here the link to the api-documentation:
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/ResourceUtils.html#getFile-java.lang.String-

Spring Tools Suite and Gradle - Setup to use correct resources from inside STS

I have a Spring Boot Gradle project setup in Spring Tools Suite (3.7.2 RELEASE) with the following source folders:
- src/integration-test/java
- src/integration-test/resources
- src/main/java
- src/main/resources
- src/test/java
- src/test/resources`
Whenever I run the application or unit tests from within STS, I see that STS is using the resources found under src/integration-test/resources.
I see a duplicate resource warning in STS for files which exist in all 3 resource source folders. For example, I have an application.properties in all 3 source folders and I see following:
The resource is a duplicate of src/integration-test/resources/application.properties and was not copied to the output folder
If I run the application as a JAR or unit tests/integration tests from the command line (via gradle build), everything seems to use the correct resources. This makes me believe it is a problem with how STS/Eclipse is handling gradle.
Does anybody know of how I can configure STS to use the correct resource source folders when using gradle?
I think my problem may be related to (or the same as?) Spring Boot incorrectly loads test configuration when running from eclipse+gradle, https://issuetracker.springsource.com/browse/STS-3882, https://issues.gradle.org/browse/GRADLE-1777
I also tried the solution found here, but that seems to only fix Maven builds:
Spring Tool Suite finds spring-boot integration test configuration and does not start main application
I think my problem may be related to...
Yes, it is related but in my opinion not the same. That problem is caused by the runtime classpath being incorrect. This problem is an error coming from the eclipse project builder so it is a compile-time issue.
The problems are closely related though. Depending on your point of view, you could say they are the same (incorrect mixing of test and compile-time classpaths).
Here, specifically, the problem is that the eclipse builder tries to copy all the resources it finds in source folders to the project's single output folder. Each source folder has a 'application.properties'. The builder warns that it could not copy some of them because one would overwrite the other.
I think there may be a solution for this problem. But it is a solution that really should come from Gradle + ( BuildShip | STS Gradle Tooling) than from you.
It is possible in Eclipse to configure each source-folder individually to target a specific outputfolder. Maven + M2E are doing this correcty, but Gradle + (BuildsShip | STS Gradle Tooling) combdos do not.
For example this is what maven puts into the eclipse .classpath file when it configures a test resources folder:
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
Notice how it explicitly sets the output folder for that entry (to something different from the project's default output folder).
You may be able to address the problem yourself by modifying the .classpath for a gradle project in a similar way. Either by doing it manually or from your build.gradle.
I'm not sure this is worth it however as you will then likely still get hit by the runtime classpath issue (since these folders will still be added to your runtime classpath, your runtime classpath will end-up with two appication.properties resources, one which will 'shadow' the other. See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=482315)
I would say, the right thing to do is add a comment to the issue I linked, and hope they fix it soon as there is only so much you can do yourself by hacking the build.gradle file to modify the .classpath (this can not solve the runtime classpath issue, but in order to solve the runtime classpath issue, they would have to configure source folders to target individual output folder similar to what m2e does).
I would add this as a comment to #Kris's answer but it's too long.
I have solved the runtime classpath issue by adding the code below to my build.gradle file. The code generates an Eclipse launch configuration for the Spring Boot application class and includes only the runtime classpath (i.e. no test JARs).
My project uses the Gradle 'eclipse' plugin to generate the Eclipse project files (which I then import into Eclipse). Running the eclipseClasspath Gradle target will generate the launch file in the project's root directory.
def mainClassName = "com.example.MyApplication"
task eclipseApplicationLaunch {
group "IDE"
description "Generate an Eclipse launch configuration file for the Spring Boot application class"
}
eclipseApplicationLaunch << {
def writer = new FileWriter("${mainClassName.substring(mainClassName.lastIndexOf(".")+1)}.launch")
def xml = new groovy.xml.MarkupBuilder(writer)
xml.doubleQuotes = true
xml.launchConfiguration(type: "org.eclipse.jdt.launching.localJavaApplication") {
listAttribute(key:"org.eclipse.debug.core.MAPPED_RESOURCE_PATHS") {
listEntry(value:"/${project.name}/src/main/java/${mainClassName.replace(".","/")}.java")
}
listAttribute(key:"org.eclipse.debug.core.MAPPED_RESOURCE_TYPES") {
listEntry(value:"1")
}
listAttribute(key:"org.eclipse.jdt.launching.CLASSPATH") {
listEntry(value:"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\r\n<runtimeClasspathEntry containerPath=\"org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/\" javaProject=\"${project.name}\" path=\"1\" type=\"4\"/>\r\n")
listEntry(value:"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\r\n<runtimeClasspathEntry path=\"3\" projectName=\"${project.name}\" type=\"1\"/>\r\n")
configurations.runtime.resolvedConfiguration.resolvedArtifacts.each { artifact ->
def filePath = artifact.file.canonicalPath.replace("\\","/")
listEntry(value:"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\r\n<runtimeClasspathEntry externalArchive=\"${filePath}\" path=\"3\" type=\"2\"/>\r\n")
}
}
booleanAttribute(key:"org.eclipse.jdt.launching.DEFAULT_CLASSPATH", value:"false")
stringAttribute(key:"org.eclipse.jdt.launching.MAIN_TYPE", value:"${mainClassName}")
stringAttribute(key:"org.eclipse.jdt.launching.PROGRAM_ARGUMENTS", value:"--spring.profiles.active=local --spring.config.location=conf/")
stringAttribute(key:"org.eclipse.jdt.launching.PROJECT_ATTR", value:"${project.name}")
stringAttribute(key:"org.eclipse.jdt.launching.VM_ARGUMENTS", value:"-Djava.net.preferIPv4Stack=true")
}
writer.close()
}
eclipseClasspath.dependsOn eclipseApplicationLaunch
I haven't modified the Eclipse .classpath file as per Kris' suggestion. Instead, I have added #Profile("test") to my test application class and #ActiveProfiles("test") to my test classes.

Can I start a Spring Boot WAR with PropertiesLauncher?

I have a Spring Boot 1.2 app packaged as a WAR because I need to be able to deploy the app in an app server.
I also want to configure an external path which will contain jars to be added to the classpath. After reading the Launcher documentation, I configured the build to use PropertiesLauncher to this end :
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
...
<layout>ZIP</layout>
</configuration>
</plugin>
I tried to start the app with various combinations of this additional system property : -Dloader.path=lib/,lib-provided/,WEB-INF/classes,<my additional path>
But I always end up with this error :
java.lang.IllegalArgumentException: Invalid source folder C:\<path to my war>\<my war>.war
at org.springframework.boot.loader.archive.ExplodedArchive.<init> ExplodedArchive.java:78)
at org.springframework.boot.loader.archive.ExplodedArchive.<init>(ExplodedArchive.java:66)
at org.springframework.boot.loader.PropertiesLauncher.addParentClassLoaderEntries(PropertiesLauncher.java:530)
at org.springframework.boot.loader.PropertiesLauncher.getClassPathArchives(PropertiesLauncher.java:451)
at org.springframework.boot.loader.Launcher.launch(Launcher.java:60)
at org.springframework.boot.loader.PropertiesLauncher.main(PropertiesLauncher.java:609)
I looked at the source code and it seems that PropertiesLauncher can only handle jar archives (ending with ".jar" or ".zip") and "exploded archives" (not ending with the former)
Is it possible to do achieve what I want ? Am I doing it wrong ?
If it's not possible, which alternative is there ?
If somebody end up here this might be useful:
java -cp yourSpringBootWebApp.war -Dloader.path=yourSpringBootWebApp.war!/WEB-INF/classes/,yourSpringBootWebApp.war!/WEB-INF/,externalLib.jar org.springframework.boot.loader.PropertiesLauncher
(Spring-Boot 1.5.9)
https://docs.spring.io/spring-boot/docs/1.5.x/reference/html/executable-jar.html#executable-jar-launching
In Spring Boot 1.2, PropertiesLauncher handles .jar and .zip files as "jar archives" and everything else as "exploded archives" (unzipped jars). It does not properly handles .war
Here's the alternative I found :
I eventually switched back to the regular war launcher and I managed to configure a folder which jar contents are added to the classpath using a SpringApplicationRunListener such as this (pseudo-code for concision) :
public class ClasspathExtender implements SpringApplicationRunListener {
public void contextPrepared(ConfigurableApplicationContext context) {
// read jars folder path from environment
String path = context.getEnvironment().getProperty("my.jars-folder");
// enumerate jars in the folder
File[] files = new File(path).listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) { return name.endsWith(".jar"); }
});
URL[] urls = // convert files array to urls array
// create a new classloader which contains the jars...
ClassLoader extendedClassloader = new URLClassLoader(urls, context.getClassLoader());
// and replace the context's classloader
((DefaultResourceLoader) context).setClassLoader(extendedClassloader);
}
// other methods are empty
}
This listener is instanciated by declaring it in a META-INF/spring.factories file :
org.springframework.boot.SpringApplicationRunListener=my.ClasspathExtender
This worked for me (Spring Boot 1.3.2)
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
...
<layout>WAR</layout>
</configuration>
</plugin>

Resources