Spring Boot - Update ResourceHandlerRegistry without restarting app - java

In my Spring Boot app that will be running locally the user needs to be able to drag photos into the web page from the operating system and have them displayed. The path on the OS to the files can be set at startup with:
#SpringBootApplication
public class UploaderApplication extends WebMvcConfigurerAdapter {
private String workingDir = "/Users/example/Desktop/";
public static void main(String[] args) {
SpringApplication.run(UploaderApplication.class, args);
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/dir/**")
.addResourceLocations("file://" + workingDir)
.setCachePeriod(0);
System.out.println(workingDir);
}
But I need to give the user the ability to update the directory where the files are coming from after the app is running, since the absolute path won't always be known when the application starts. If I send a GET request from the browser with a new working directory path entered by the user, how can I update the registry?
Thank you.

This is how I decided to solve it. A user can fill out a form to update a database with the directories they will be uploading from. When the application loads it gets the list of directories and creates identifiers for them that can then be used in the html.
#SpringBootApplication
public class boxApplication extends WebMvcConfigurerAdapter {
#Autowired private TemplateRepository templateRepository;
public static void main(String[] args) {
SpringApplication.run(boxApplication.class, args);
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
Template template = templateRepository.findById(1);
for (UploadDirectory local: template.getLocalDirectories()){
registry.addResourceHandler("/dir" + local.getId() + "/**")
.addResourceLocations("file://" + local.getDirectory())
.setCachePeriod(0);
}
}
}

Related

Spring Boot Context Path Ignored With External Tomcat

I have set the context path for tomcat as follows:
#Component
public class CustomContainer implements
WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Override
public void customize(TomcatServletWebServerFactory factory) {
factory.setContextPath("/capripol");
factory.setPort(8080);
}
}
Navigating to localhost:8080/capripol works fine and I am prompted with my login screen, however after logging in my forms and controllers do not append to the context path, so instead of navigating to /capripol/MainMenu etc. they navigate to /MainMenu. How do I set the context path such that my form actions and controllers will be appended do it - why is the tomcat factory context path not setting?
Edit: My Application class
#SpringBootApplication
public class CapripolApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(CapripolApplication.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(CapripolApplication.class);
}
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/", "classpath:/images/")
.setCachePeriod(0);
}
}
}
A few ways to do it. You can add it to each controller, usefully if you want to change the context path
#Controller
#RequestMapping(value = "/foo")
public class bar{
#GetMapping(value = "/bar")
public void stuff(){
//doing stuff
}
}
Or you can put it in your application.properties / yml
server.servlet.contextPath=/foo/*
There are technically some other more round about ways to do it, especially if you are using an older version of Spring, but I would think the application properties is what you are looking for.

Spring Boot can connect to Cassandra when running as Servlet but not as Command Line

Background: I'm trying to set up a code-based data migration system for our Cassandra database. I don't have a ton of experience with Java, but if this were a .NET project I'd set up the migrations as a different project under the same solution. However, based on guidance from other team members that are more experienced, it was recommended that I include the migrations in the same package as the rest of the application (which I'm fine with). It was also suggested that the easiest method would be to run the migrations via a web API endpoint (which I'm more skeptical of). In the interest of avoiding opening up a potential security vulnerability, I thought I'd take a shot at making a command-line utility to execute the migrations.
I have a Spring Boot web application with an entry point class that looks like this:
#Configuration
#SpringBootApplication
#EnableAutoConfiguration
#EnableCaching
#EnableScheduling
public class MyApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MyApplication.class);
}
public static void main(String[] args) {
new MyApplication().configure(new SpringApplicationBuilder(MyApplication.class)).run(args);
}
}
However, I'm trying to add the functionality to run a couple migration scripts that are packaged with this application via the command line (e.g. java -jar MyApplication.jar migrate), so I added the following class:
#Configuration
#SpringBootApplication
#EnableAutoConfiguration
public class MigrationRunner implements CommandLineRunner {
#Autowired
Session session;
#Override
public void run(String[] args)
{
MigrationResources mr = new MigrationResources();
mr.addMigration(...);
mr.addMigration(...);
MigrationEngine.withSession(session).migrate(mr);
}
}
And then updated my entry point class like this:
// annotations
public class MyApplication extends SpringBootServletInitializer {
private final static String MIGRATE_COMMAND = "migrate";
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MyApplication.class);
}
public static void main(String[] args) {
if (args.length > 0 && args[0].equalsIgnoreCase(MIGRATE_COMMAND)) {
new SpringApplicationBuilder()
.sources(MigrationRunner.class)
.run(Arrays.copyOfRange(args, 1, args.length));
} else {
new MyApplication().configure(new SpringApplicationBuilder(MyApplication.class)).run(args);
}
}
}
The problem is that when I execute this with the migrate arg, Spring throws this error:
Error creating bean with name 'migrationRunner': Unsatisfied dependency expressed through field 'session'
Error creating bean with name 'session' defined in class path resource [org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfiguration.class]: Invocation of init method failed
All host(s) tried for query failed (tried: server022/XX.YY.ZZ.022:9042 (com.datastax.driver.core.exceptions.TransportException: [server022/XX.YY.ZZ.022:9042] Connection has been closed), server022/XX.YY.ZZ.020:9042 (com.datastax.driver.core.exceptions.TransportException: [server020/XX.YY.ZZ.020:9042] Connection has been closed), server020/XX.YY.ZZ.021:9042 (com.datastax.driver.core.exceptions.TransportException: [server020/XX.YY.ZZ.021:9042] Connection has been closed))
Running it without the migrate arg still works fine. I suspect that Spring is simply not picking up the correct certificates for this Cassandra server, even though it appears to be getting all the other configuration properties (server name, keyspace, etc.)
Question: How can I make a Spring Boot servlet that also has a command-line mode and can connect to the configured Cassandra server in both modes?
All you need to do is,
#SpringBootApplication
public class MyApplication
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
You have over complicated the application. If you run the MyApplication.main that will run in port 8080 by default.
Bonus, If you need both to start from same class.
#SpringBootApplication
public class MigrationRunner implements CommandLineRunner {
#Autowired
Session session;
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Override
public void run(String[] args)
{
MigrationResources mr = new MigrationResources();
mr.addMigration(...);
mr.addMigration(...);
MigrationEngine.withSession(session).migrate(mr);
}
}

Finding app base for embedded tomcat in spring boot

I am using spring boot. I am developing a web application and i need to get the app base to generate a link for an email. For that i need to get the base URL. As the tomcat is embedded
request.getContextpath()
returns null. I need to get that localhost:8080 dynamically so that when I deploy this to server I don't have to change the code.
In a spring boot application you can change the server context in the application.properties with the properties server.context-path=your-path
in your code you can refer to this properties like below
#SpringBootApplication
#EnableConfigurationProperties(ServerProperties.class)
public class DemoApplication implements CommandLineRunner{
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Autowired
ServerProperties serverProperties;
#Override
public void run(String... strings) throws Exception {
System.out.println("serverProperties.getContextPath(): " + serverProperties.getContextPath());
}
}
The key point here is use #EnableConfigurationProperties(ServerProperties.class) on your #Configuration class and then use the injected fiels ServerProperties for consume the value.
I hope that this can help you...
Huh, that took me some time to get there but:
#SpringBootApplication
public class TestServiceApplication {
public static void main(String[] args) {
AnnotationConfigEmbeddedWebApplicationContext context = (AnnotationConfigEmbeddedWebApplicationContext)SpringApplication.run(TestServiceApplication.class, args);
TomcatEmbeddedServletContainer tomcatContainer = (TomcatEmbeddedServletContainer)context.getEmbeddedServletContainer();
String host = tomcatContainer.getTomcat().getHost().getName();
int port = tomcatContainer.getPort();
System.out.println("Host:port " + host + ":" + port);
}
}
Output: Host:port localhost:8080
Is that what You wanted?

Embedded Tomcat directory listing for spring-boot application

I have a spring boot application with embedded Tomcat.
I wanted to expose some images files & folders from a different location via tomcat directory listing. So I added the below in my configuration file called
public class AppConfig extends WebMvcConfigurerAdapter
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/images/**").addResourceLocations("file:///xxx/yyy/images/");
}
}
I can now access individual image(s), if I know the name.
Example: localhost:8080/images/file.jpg.
But since the directory listing is false by default, I can't access the images listing through "localhost:8080/images/" to know the all the available images.
I tried the below option to add the listings as well, but did not work.
public class MyApplication implements ServletContextInitializer{
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setInitParameter("listings", "true");
}
}
Updated for Spring 2.1
import org.apache.catalina.Context;
import org.apache.catalina.Wrapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
#Component
public class MyTomcatWebServerCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Value("${tomcat.file.base}") // C:\\some\\parent\\child
String tomcatBaseDir;
#Override
public void customize(TomcatServletWebServerFactory factory) {
// customize the factory here
TomcatContextCustomizer tomcatContextCustomizer = new TomcatContextCustomizer() {
#Override
public void customize(Context context) {
String parentFolder = tomcatBaseDir.substring(0,tomcatBaseDir.lastIndexOf("\\"));
String childFolder = tomcatBaseDir.substring(tomcatBaseDir.lastIndexOf("\\") + 1);
context.setDocBase(parentFolder);
Wrapper defServlet = (Wrapper) context.findChild("default");
defServlet.addInitParameter("listings", "true");
defServlet.addInitParameter("readOnly", "false");
defServlet.addMapping("/"+ childFolder + "/*");
}
};
factory.addContextCustomizers(tomcatContextCustomizer);
}
}
In an identical way to SpringBoot Embedded Tomcat JSPServlet Options you can use an EmbeddedServletContainerCustomizer #Bean to look up the default servlet and configure its init parameters.
#Bean
public EmbeddedServletContainerCustomizer customizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (container instanceof TomcatEmbeddedServletContainerFactory) {
customizeTomcat((TomcatEmbeddedServletContainerFactory) container);
}
}
private void customizeTomcat(TomcatEmbeddedServletContainerFactory tomcat) {
tomcat.addContextCustomizers(new TomcatContextCustomizer() {
#Override
public void customize(Context context) {
Wrapper defServlet = (Wrapper) context.findChild("default");
defServlet.addInitParameter("listings", "true");
}
});
}
};
}
Kudos to Andy Wilkinson.
In springboot /** is mapped to ResourceHttpRequestHandler. The call never gets delegated to DefaultServlet for the listings to take effect. I had to make two more adjustments to Mark's solution get it to work.
Add a different mapping to the DefaultServlet -> "/static/*"
The docbase from where the static contents are served is a tmp folder. I had to set it to the folder where the static contents are present.
public void customize(Context context) {
context.setDocBase("../../../mytest");
Wrapper defServlet = (Wrapper) context.findChild("default");
defServlet.addInitParameter("listings", "true");
defServlet.addInitParameter("readOnly", "false");
defServlet.addMapping("/static/*");
}
Deployment folder structure
/myhome/mytest
----myapp.jar
----/tomcat/webapps
----/static
--------All static files go here
application.yml
server :
tomcat :
basedir : tomcat
Current working dir to run the app /myhome/mytest
url to test : http://localhost:8080/static

Spring Boot CommandLineRunner : filter option argument

Considering a Spring Boot CommandLineRunner Application, I would like to know how to filter the "switch" options passed to Spring Boot as externalized configuration.
For example, with:
#Component
public class FileProcessingCommandLine implements CommandLineRunner {
#Override
public void run(String... strings) throws Exception {
for (String filename: strings) {
File file = new File(filename);
service.doSomething(file);
}
}
}
I can call java -jar myJar.jar /tmp/file1 /tmp/file2 and the service will be called for both files.
But if I add a Spring parameter, like java -jar myJar.jar /tmp/file1 /tmp/file2 --spring.config.name=myproject then the configuration name is updated (right!) but the service is also called for file ./--spring.config.name=myproject which of course doesn't exist.
I know I can filter manually on the filename with something like
if (!filename.startsWith("--")) ...
But as all of this components came from Spring, I wonder if there is not a option somewhere to let it manage it, and to ensure the strings parameter passed to the run method will not contain at all the properties options already parsed at the Application level.
Thanks to #AndyWilkinson enhancement report, ApplicationRunner interface was added in Spring Boot 1.3.0 (still in Milestones at the moment, but will soon be released I hope)
Here the way to use it and solve the issue:
#Component
public class FileProcessingCommandLine implements ApplicationRunner {
#Override
public void run(ApplicationArguments applicationArguments) throws Exception {
for (String filename : applicationArguments.getNonOptionArgs())
File file = new File(filename);
service.doSomething(file);
}
}
}
There's no support for this in Spring Boot at the moment. I've opened an enhancement issue so that we can consider it for a future release.
One option is to use Commons CLI in the run() of your CommandLineRunner impl.
There is a related question that you may be interested.
Here is another solution :
#Component
public class FileProcessingCommandLine implements CommandLineRunner {
#Autowired
private ApplicationConfig config;
#Override
public void run(String... strings) throws Exception {
for (String filename: config.getFiles()) {
File file = new File(filename);
service.doSomething(file);
}
}
}
#Configuration
#EnableConfigurationProperties
public class ApplicationConfig {
private String[] files;
public String[] getFiles() {
return files;
}
public void setFiles(String[] files) {
this.files = files;
}
}
Then run the program :
java -jar myJar.jar --files=/tmp/file1,/tmp/file2 --spring.config.name=myproject

Categories