How to service external static HTML files in Spring Boot Embedded tomcat? - spring

I'm new to Spring framework and Spring Boot.
I've implemented a very simple RESTful Spring Boot web application.
You can see the nearly full source code in another question: Spring Boot: How to externalize JDBC datasource configuration?
How can the app service external static HTML, css js files?
For example, the directory structure may be as follows:
MyApp\
MyApp.jar (this is the Spring Boot app that services the static files below)
static\
index.htm
images\
logo.jpg
js\
main.js
sub.js
css\
app.css
part\
main.htm
sub.htm
I've read the method to build a .WAR file that contains static HTML files, but since it requires rebuild and redeploy of WAR file even on single HTML file modification, that method is unacceptable.
An exact and concrete answer is preferable since my knowledge of Spring is very limited.

I see from another of your questions that what you actually want is to be able to change the path to static resources in your application from the default values. Leaving aside the question of why you would want to do that, there are several possible answers.
One is that you can provide a normal Spring MVC #Bean of type WebMvcConfigurerAdapter and use the addResourceHandlers() method to add additional paths to static resources (see WebMvcAutoConfiguration for the defaults).
Another approach is to use the ConfigurableEmbeddedServletContainerFactory features to set the servlet context root path.
The full "nuclear option" for that is to provide a #Bean definition of type EmbeddedServletContainerFactory that set up the servlet container in the way you want it. If you use one of the existing concrete implementations they extend the Abstract* class that you already found, so they even have a setter for a property called documentRoot. You can also do a lot of common manipulations using a #Bean of type EmbeddedServletContainerCustomizer.

Is enough if you specify '-cp .' option in command 'java -jar blabla.jar' and in current directory is 'static' directory

Take a look at this Dave Syer's answer implementation.
You can set the document root directory which will be used by the web context to serve static files using ConfigurableEmbeddedServletContainer.setDocumentRoot(File documentRoot).
Working example:
package com.example.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import java.io.File;
import java.nio.file.Paths;
#Configuration
public class WebConfigurer implements ServletContextInitializer, EmbeddedServletContainerCustomizer {
private final Logger log = LoggerFactory.getLogger(WebConfigurer.class);
private final Environment env;
private static final String STATIC_ASSETS_FOLDER_PARAM = "static-assets-folder";
private final String staticAssetsFolderPath;
public WebConfigurer(Environment env, #Value("${" + STATIC_ASSETS_FOLDER_PARAM + ":}") String staticAssetsFolderPath) {
this.env = env;
this.staticAssetsFolderPath = staticAssetsFolderPath;
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
if (env.getActiveProfiles().length > 0) {
log.info("Web application configuration, profiles: {}", (Object[]) env.getActiveProfiles());
}
log.info(STATIC_ASSETS_FOLDER_PARAM + ": '{}'", staticAssetsFolderPath);
}
private void customizeDocumentRoot(ConfigurableEmbeddedServletContainer container) {
if (!StringUtils.isEmpty(staticAssetsFolderPath)) {
File docRoot;
if (staticAssetsFolderPath.startsWith(File.separator)) {
docRoot = new File(staticAssetsFolderPath);
} else {
final String workPath = Paths.get(".").toUri().normalize().getPath();
docRoot = new File(workPath + staticAssetsFolderPath);
}
if (docRoot.exists() && docRoot.isDirectory()) {
log.info("Custom location is used for static assets, document root folder: {}",
docRoot.getAbsolutePath());
container.setDocumentRoot(docRoot);
} else {
log.warn("Custom document root folder {} doesn't exist, custom location for static assets was not used.",
docRoot.getAbsolutePath());
}
}
}
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
customizeDocumentRoot(container);
}
}
Now you can customize your app with command line and profiles (src\main\resources\application-myprofile.yml):
> java -jar demo-0.0.1-SNAPSHOT.jar --static-assets-folder="myfolder"
> java -jar demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=myprofile

Related

Error Messages as Key Value Pairs - from a Properties File in classpath - Spring boot 2.0

We are currently on a Spring Boot Version 1.x
We have Error Messages (Error Key -> Error Code) pairs in our error.properties file (this is in the class path).
We leveraged PropertiesConfigurationFactory to get these Error Key and Error Code pairs in to a POJO, this POJO had a Map
Hence very convenient to be used across our application to get an Error code for a given Error Key.
What is its equivalent in Spring Boot 2.x ?.
Assuming you have error.properties file with the below contents:
errors.error1=101
errors.error2=102
errors.error3=103
A simple spring boot app that demonstrates the injection of these properties :
package snmaddula.remittance;
import java.util.Map;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
#SpringBootApplication
#ConfigurationProperties
#PropertySource("classpath:error.properties")
public class DemoApplication {
private Map<String, Integer> errors;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
public CommandLineRunner cli() {
return (args) -> {
System.out.println(errors); // you can print and see the error properties injected to this map.
};
}
public void setErrors(Map<String, Integer> errors) {
this.errors = errors;
}
}
With the use of #PropertySource and #ConfigurationProperties we can enable property injection provided we have a setter method for our attribute.
When you run this program, you can see the properties getting printed on to the console as I added a CommandLineRunner cli() {..} to show the working of it.
The working sample is available on GitHub.

Spring boot application shows file download option when serving static content

I have a spring boot application in which I have some static files kept inside resources/static directory. One of them is a swagger directory. The file system layout looks as follows:
resources
- static
- swagger
- index.html
Now if I send request to my webapp with URI localhost:8080/swagger/index.html then it serves the file correctly. However, if I send request to localhost:8080/swagger then the webapp shows a file download box with an empty binary file named swagger.
In my opinion second URI should actually serve the index.html file automatically. How do I fix this behavior?
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#Configuration
public class WebConfig {
#Bean
public WebMvcConfigurerAdapter forwardToIndex() {
return new WebMvcConfigurerAdapter() {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/swagger").setViewName(
"forward:/swagger/index.html");
registry.addViewController("/swagger/").setViewName(
"forward:/swagger/index.html");
}
};
}
}

how to use the ApplicationArguments in spring-boot

I am learning the Spring-Boot(I am new to it), reading the Spring Boot Document. In the 23.6 Accessing application arguments, It talk about the ApplicationArguments, and the code is:
package com.example.project;
import org.springframework.boot.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.stereotype.*;
import java.util.*;
#Component
public class MyBean {
#Autowired
public MyBean(ApplicationArguments args) {
boolean debug = args.containsOption("debug");
List<String> files = args.getNonOptionArgs();
System.out.println(debug);
System.out.println(files);
}
}
It says if run with "--debug logfile.txt" debug=true, files=["logfile.txt"].
But in my project, I don't know how to run it. I create the spring-boot using Maven: The Project Structure
In Spring Boot doc ApplicationArguments is autowired in a bean. Here is a more hands on example where it's used in a Main method.
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Application implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(Application.class, args).stop();
}
#Override
public void run(ApplicationArguments args) throws Exception {
boolean debug = args.containsOption("debug");
List<String> files = args.getNonOptionArgs();
System.out.println(debug);
System.out.println(files);
}
}
Assuming that you have an Application class with annotation #SpringBootApplication like in the answer provided by a.b.d.
To be able to provide the arguments within IntelliJ IDEA environment you will need to first Run the main method and then Edit 'Run/Debug Configurations' and under Main Class fill Program arguments field with "--debug logfile.txt":
In one word like a thousand :
the 'Program arguments' in your IDE field prefixed by -- is simply the same name as the 'Option' expected in the 'ApplicationArguments'.
Hence you can match --debug and "args.containsOption("debug")".

Spring boot not loading PropertySourceLoader

With spring boot version 1.2.3 (also tested it with 1.2.5), I am following creating-a-custom-jasypt-propertysource-in-springboot and Spring Boot & Jasypt easy: Keep your sensitive properties encrypted to use jsypt library using custom PropertySourceLoader. My source loader class is in api.jar and this jar file is included in myapplication.war file. This war is deployed in tomcat.
It seems that spring is not loading EncryptedPropertySourceLoader on application startup. Can anyone please help?
Following is my project structure and related code.
Project - api.jar
| src
| | main
| | java
| | EncryptedPropertySourceLoader.java
| | resources
| | META-INF
| | spring.factories
The api.jar is build with api.jar!META-INF/spring.factories.
package com.test.boot.env;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
public class EncryptedPropertySourceLoader implements PropertySourceLoader, PriorityOrdered {
private static final Logger logger = LoggerFactory.getLogger(EncryptedPropertySourceLoader.class);
public EncryptedPropertySourceLoader() {
logger.error("\n\n\n***CREATING properties loader.\n\n\n");
}
#Override
public String[] getFileExtensions() {
return new String[] { "properties" };
}
#Override
public PropertySource<?> load(final String name, final Resource resource, final String profile) throws IOException {
logger.error("\n\n\n***Loading properties files.\n\n\n");
if (true) {
throw new RuntimeException("calling load"); \\intentional to identify the call
}
return null;
}
#Override
public int getOrder() {
return HIGHEST_PRECEDENCE;
}
}
The spring.factories contents are
org.springframework.boot.env.PropertySourceLoader=\
com.test.boot.env.EncryptedPropertySourceLoader
I have also tried without '\' but it does not make any difference.
Following is the path of spring.factories in myapplication.war file
myapplication.war!WEB-INF/lib/api.jar!META-INF/spring.factories
As per my understanding, I should see the RuntimeException on startup but my application is starting successfully. Can anyone please help to find what I am be missing?
For what I can tell the SpringFactoriesLoader mechanism for factory PropertySourceLoader is only used by PropertySourcesLoader which in turn is ONLY used to load the application properties. Those are either application.properties, application.yml, or application.yaml. So for your example just add a file application.properties to your classpath and you'll get the exception you are expecting.
What I don't know is why other property source import mechanisms such as using annotation #PropertySource are not going through PropertySourcesLoader to take advantage of the factories loaders and override mechanism.

how to load additional bean configuration file in spring at run time

I have a spring app, and in the future we will develop more classes, and for these class we will also use additional configuration files (not overwrite the existing ones) to define the beans. Then how to dynamically load them? I know there is an interface of ApplicationContextAware, I could have a bean running checking whether new configuration files are available, if they come, I could run the
setApplicationContext(ApplicationContext applicationContext)
But then how to use ApplicationContext to load the additional configuration file?
update:
If the app is loaded from XML then I could convert ApplicationContext to ClassPathXmlApplicationContext and then use the load method,but what if AnnotationConfigApplicationContext, it only has scan method to scan package, but what if I want to load from xml?
update:
The following is the code I want to use, it used spring integration to monitor a fold, at run time I could put jar file on the class path, and then put the xml configuration in that folder, this will trigger the loadAdditionBeans function to run, and the xml File object will be passed in, what need to do is to add the context in that File to the current context but not create a child context.
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.integration.annotation.MessageEndpoint;
import org.springframework.integration.annotation.ServiceActivator;
import java.io.File;
#MessageEndpoint
public class FolderWatcher implements ApplicationContextAware {
//private ApplicationContext ctx;
private AnnotationConfigApplicationContext ctx; // it's a spring boot,so the ctx is AnnotationConfigApplicationContext
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.ctx=(AnnotationConfigApplicationContext)applicationContext;
}
#ServiceActivator
public void loadAdditionBeans(File file){
/*
file is an xml configuration file, how to load the beans defined it into the currect context,
I don't what to have another hierarchy, since that will make the beans defined in the file not
available in parent.
*/
}
}
PathMatchingResourcePatternResolver pmrl = new PathMatchingResourcePatternResolver(context.getClassLoader());
Resource[] resources = pmrl.getResources(
"classpath*:com/mycompany/**/applicationContext.xml"
);
for (Resource r : resources) {
GenericApplicationContext createdContext = new GenericApplicationContext(context);
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(createdContext);
int i = reader.loadBeanDefinitions(r);
}
Have a look at the above code and let me know if it helps to resolve your problem.
If you're using the classpath scanning but you still want o load additional configurations from XML, you can simply use the #ImportResource annotation on your #Configuration class and import the XML resource you need:
#Configuration
#ImportResource( { "classpath*:/rest_config.xml" } )
public class MyConfig{
...
}
That way it's easy to mix in legacy XML configuration with newer, Java configs and you don't have to - for example - migrate your entire configuration in one go.
Hope that helps.

Resources