Can I start a Spring Boot WAR with PropertiesLauncher? - spring-boot

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>

Related

How to set active profile on Spring Boot while building bootJar?

how to set profile just for the executable (jar) built by calling bootJar task? Ideally, it would override (or add) a property in application.properties inside the jar or something like that.
I expect he is trying to do exactly what we are. In maven one can do something along these lines:
<profile>
<id>openshift</id>
<properties>
<activatedProperties>openshift</activatedProperties>
</properties>
</profile>
Which can be used to replace #activatedProperties# in application.properties to, e.g., openshift. If this is the case, we solved it by modifying build.gradle as follows:
import org.apache.tools.ant.filters.ReplaceTokens
# whatever you have here
def springProfile = project.hasProperty("profile") ? profile : ""
bootRun {
systemProperties["spring.profiles.active"] = springProfile
}
processResources {
with copySpec {
from "src/main/resources/"
filter(ReplaceTokens, tokens: ["activatedProperties": springProfile])
}
}
and in the application.properties have:
spring.profiles.active=#activatedProperties#
However, I would like to note that we consider this a temporary hack. Build scripts should not be modifying files, even if it's limited to resource files. A more correct approach would be to use templates in a separate location and output those to the resources. Or even better (which is what we will do in due time), fix the deployment setup, so it's part of the deployment configuration, as it should be.
You can provide spring.profiles.active=dev,mysql in your application/bootstrap.properties file of your spring boot application.
Or while running the jar you can provide the active profile as argument,
java -jar fatJar.jar --spring.profiles.active=dev,mysql

Reading file inside WAR

I am working on a small RESTFul-project with Maven and Tomcat. Inside my implementation I have to read a txt file.
Currently I am testing the implementation in Eclipse. When using the absolute path I can execute my code without any problem. Since I have to generate a war file, I need to encapsulate the txt file in it. Using Maven and
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<warSourceDirectory>WebContent</warSourceDirectory>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
my war file contains the txt file stored in the highest level. What is your recommendation to read from that txt file inside the war file? I have found several threads here, but all tries failed. I need a specification where I can read the file when testing in Eclipse and when executing it directly in Tomcat.
This code doesn't work when executing the implementation in Eclipse as the text file is not found. The file is in the WebContent folder and after genereting the snapshot/war, you find the file in the war-file.
InputStreamFactory isf = new InputStreamFactory() {
public InputStream createInputStream() throws IOException {
return new FileInputStream("/text_de.txt");
}
};
I also tried the following when executing the implementation in Eclipse without access (I know, that would lead to an error when executing the war file):
return new FileInputStream("/WebContent/text_de.txt");
UPDATE:
Now I can execute the implementation within Eclipse with success. I first tried it with the annotation #Resource, but changed to #Context
#Context
ServletContext servletContext;
Then, I added this row:
String fileLocation = System.getProperty("com.example.resourceLocation",servletContext.getRealPath("/text_de.txt"));
Thanks
I am not sure if this helps in your case, but could you, using the HttpServletRequest request from your doGet() (doPost(), or one of the others), try:
return new FileInputStream(request.getServletContext().getRealPath("/text_de.txt"));
If you extract your .war file using an unzip program, you can determine the path needed, if "/text_de.txt" might not be correct, but a lot of times the contents of /WebContent/ ends up in the root of the .war file...
Hopes this helps,
note: The HttpServletRequest it self has a method with the same name, but that one is deprecated, so you'll know it the wrong one.
after discussion in the comments:
You need a different location during development,
find the file: tomcat/conf/catalina.properties of the instance of tomcat that is actually running. I am not sure if Eclipse is running this, or you point Eclipse to a tomcat-home folder
add a line like com.example.resourceLocation=/full/path/to/eclipse/project/text_de.txt
than use something like:
String fileLocation = System.getProperty(
"com.example.resourceLocation",
request.getServletContext().getRealPath("/text_de.txt"));
//this second parameter is a default value you can provide yourself
//if the property does not exists it chooses the second as return value
in response to the non-servlet question:
I am using Jersey link, here I use something like this
#Context
private HttpServletRequest httpRequest;
#GET
#Path("file")
#Produces(MediaType.APPLICATION_JSON)
public Response getFile () {
String fileLocation = System.getProperty(
"com.example.resourceLocation",
this.httpRequest.getServletContext().getRealPath("/text_de.txt"));
//this second parameter is a default value you can provide yourself
//if the property does not exists it chooses the second as return value
// do something with fileLocation
FileInputStream fi = new FileInputStream(fileLocation);
return Response.status(Response.Status.OK)
.entity("body")
.build();
}
within the class where the responding #POST or #GET etc. functions are located
a few years back in a SOAP service I used a servlet filter and retrieved the request object from the doFilter() function,
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
this.context = request.getServletContext( );
Eclipse can create Filters using the context-menu of your project...
in both cases you have the servletRequest object or servletContext object, so you can fit that into the file reader location solution
kind regards

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

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?

Spring Boot: Thymeleaf not resolving fragments after packaging

im using fragments like this:
#RequestMapping(value="/fragment/nodeListWithStatus", method= RequestMethod.GET)
public String nodeListWithStatus(Model model) {
// status der nodes
model.addAttribute("nodeList", nodeService.getNodeListWithOnlineStatus());
return "/fragments :: nodeList";
}
The templates are in /src/main/resources/templates. This works fine when starting the application from IntelliJ.
As soon as i create an .jar and start it, above code no longer works. Error:
[2014-10-21 20:37:09.191] log4j - 7941 ERROR [http-nio-666-exec-2] --- TemplateEngine: [THYMELEAF][http-nio-666-exec-2] Exception processing template "/fragments": Error resolving template "/fragments", template might not exist or might not be accessible by any of the configured Template Resolvers
When i open the .jar with winrar, i see /templates/fragments.html - so it seems to be there.
My pom.xml has this part for building the jar (Maven clean, install) :
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>de.filth.Application</mainClass>
<layout>JAR</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
Can anyone tell me what im doing wrong here?
Thanks!
You don't need the leading / on the view name, i.e. you should return fragments :: nodeList rather than /fragments :: nodeList. Having made this change Thymeleaf should be able to find the template when run from your IDE or from a jar file.
If you're interested, here's what's happening under the hood:
The view name is used to search for a resource on the classpath. fragments :: nodeList means that the resource name is /templates/fragments.html and /fragments :: nodeList means that the resource name is /templates//fragments.html (note the double slash). When you're running in your IDE the resource is available straight off the filesystem and the double slash doesn't cause a problem. When you're running from a jar file the resource is nested within that jar and the double slash prevents it from being found. I don't fully understand why there's this difference in behaviour and it is rather unfortunate. I've opened an issue so that we (the Spring Boot team) can see if there's anything we can do to make the behaviour consistent.
It's an old topic, but I stumbled upon it while having problem with similar symptoms and different root cause. Wanted to share solution which helped me in case it could help somebody else...
Apparently name of the messages.properties file is case sensitive, but not everywhere. I had mine called "Messages.properties" (with capital M) and it worked just fine from inside IDE (IntelliJ), but once I tried to run app from jar, all messages were replaced with ??parameter.name??. Replacing M with lowercase m resolved the problem.

Import spring context with specific profile

In my java-spring-maven web-application I'm using multiple spring contexts (single context for each project).
For the sake of simplicity let's say I have 2 spring contexts: a.xml and b.xml, each belong to a project: project A and project B where A depends upon B.
a.xml is importing b.xml like this:
<import resource="classpath*:/b.xml" />
So when loading a.xml, b.xml is loaded as well.
Now, I have 2 spring profiles in my system: test and production that are used by my contexts (loading different property place holders for each profile).
So in a.xml I got:
<!-- Production profile -->
<beans profile="production">
<context:property-placeholder
location="classpath:props-for-a.properties"
ignore-unresolvable="true" ignore-resource-not-found="true" />
</beans>
<!-- Test profile -->
<beans profile="test">
<context:property-placeholder
location="classpath*:test-props-for-a.properties"
ignore-unresolvable="true" />
</beans>
And the same convention in b.xml (only loading different properties files).
My properties file are under src/main/resources (production files) and src/test/resources (test files).
Now I have this unit test that looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:/a.xml" })
#ActiveProfiles(profiles = "test")
public class SomeTestClass {
#Test
public void testMethod() {
//Some Test
}
}
In this case, a.xml is loaded in "test" profile and as expected the desired files are loaded (test-props-for-a). b.xml is imported by a.xml but now I experience a strange experience, my properties values from the property file loaded from b.xml are not injected.
For example if I have this property in my property file:
connection-ip=1.2.3.4
and in my class I got:
#Value("${connection-ip}")
private String connectionIP;
The value of connectionIP will be "${connection-ip}" instead of "1.2.3.4".
Notice that the file location starts with classpath*, without the * I get a FileNotFoundException:
Caused by: org.springframework.beans.factory.BeanInitializationException: Could not load properties; nested exception is java.io.FileNotFoundException: class path resource [test-props-for-b.properties] cannot be opened because it does not exist
To be clear, the file test-props-for-b.properties resides under src/test/resources of project B. Why do I get this?
If I run a test that loads b.xml directly (not imported via a.xml) it works just fine, the test properties in the file test-props-for-b.properties are loaded as expected.
Am I doing something wrong? Could this be a bug? If so, can you suggest a workaround?
UPDATE
I removed the star (*) from the path to my properties file (test-props-for-b.properties) and debugged spring code. In class ClassPathResource this method throws the exception:
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else {
is = this.classLoader.getResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
Here are the variables values:
this.clazz is null.
this.path holds the file name test-props-for-b.properties
And this command returns null:
this.classLoader.getResourceAsStream(this.path);
Why is that? I can clearly see the file under src/test/resources of project B.
After investigating the issue this is my conclusion:
The file could easily be loaded if I could use the suitable class-loader, meaning that in order to load a file from project A, any class-loader of a class from project A could do the trick, same for project B. But when using the property-placeholder of spring the class-loader of ClassPathResource.java is used, and when it tries to load a resource from a different project it fails to do so (a ClassPathResource gets initialized per property-placeholder, hence per project in my case).
I don't really know how to fix it, currently I have implemented an ugly workaround by duplicating my test properties files (added file test-props-for-b.properties to src/test/resources folder of project A, this way I have it duplicated in src/test/resource of project B and A as well).
I'm still looking for a cleaner way.
Have you tried to define the spring profiles outside instead of #ActiveProfiles(profiles = "test")?
One example is to define it in maven surefire plugin in this way:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire.version}</version>
<configuration>
<systemPropertyVariables>
<spring.profiles.active>test</spring.profiles.active>
</systemPropertyVariables>
<!-- THE REST OF CONFIGURATIONS -->
</configuration>
</plugin>

Resources