How to map Spring MVC controllers to a specific folder? - java

I'm working on a project using Spring MVC, Spring Boot, Gradle and Thymeleaf. I was wondering if there's a way to avoid controllers returning lengthy String paths to a specific view in a subfolder?
My views are currently in resources/templates/views/home/
public String index( Model model ) {
return "views/home/index";
}
I'm looking to only have to return something like home/index or home/someotherpage for the HomeController.
Thanks!

Yes, it is possible by defining templateResolver bean in your configuration:
#Configuration
public class TemplateConfig {
#Bean
public SpringResourceTemplateResolver templateResolver() {
final SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setCacheable(false);
templateResolver.setPrefix("classpath:/templates/views/");
templateResolver.setSuffix(".html");
return templateResolver;
}
}
Now you can use your return index page in your controller as:
public String index( Model model ) {
return "home/index";
}

Related

Spring Boot: How to access locale in Services or Repositories

I followed some tutorials for internationalization in Spring Boot.
(https://www.youtube.com/watch?v=Y7pHhui0cD4 and https://www.baeldung.com/spring-boot-internationalization)
I came so far to configure it like this:
#Configuration
public class InternationalizationConfiguration {
#Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
sessionLocaleResolver.setDefaultLocale(Locale.GERMAN);
return sessionLocaleResolver;
}
#Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
messageSource.setUseCodeAsDefaultMessage(true);
return messageSource;
}
}
and to use it in the RestConroller like this:
#RestController
public class ExperimentalControllerImpl implements ExperimentalController{
#Autowired
ResourceBundleMessageSource messageSource;
#Override
public String testI18n(#RequestHeader("Accept-Language") String locale) {
StringBuilder builder = new StringBuilder();
builder.append(messageSource.getMessage(Messages.HELLO, null, new Locale(locale)));
builder.append("\n");
builder.append(messageSource.getMessage(Messages.HOW_ARE_YOU, null, new Locale(locale)));
return builder.toString();
}
}
It works fine.....
But how do I get the locale information from the request header to my service layer or my repository layers?
Is there a best practise to store the information which locale is sent by the request, so that I can access it in my services and repositories? I think the value must be request/thread specific. Giving it as a method parameter down to the other layer seems pretty ugly.
Thank you
Regards form Germany

Spring Boot: How to access set Locale in Service or Repository layer

I followed some tutorials for internationalization in Spring Boot.
(https://www.youtube.com/watch?v=Y7pHhui0cD4 and https://www.baeldung.com/spring-boot-internationalization)
I came so far to configure it like this:
#Configuration
public class InternationalizationConfiguration {
#Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
sessionLocaleResolver.setDefaultLocale(Locale.GERMAN);
return sessionLocaleResolver;
}
#Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
messageSource.setUseCodeAsDefaultMessage(true);
return messageSource;
}
}
and to use it in the RestConroller like this:
#RestController
public class ExperimentalControllerImpl implements ExperimentalController{
#Autowired
ResourceBundleMessageSource messageSource;
#Override
public String testI18n(#RequestHeader("Accept-Language") String locale) {
StringBuilder builder = new StringBuilder();
builder.append(messageSource.getMessage(Messages.HELLO, null, new Locale(locale)));
builder.append("\n");
builder.append(messageSource.getMessage(Messages.HOW_ARE_YOU, null, new Locale(locale)));
return builder.toString();
}
}
It works fine.....
But how do I get the locale information from the request header to my service layer or my repository layers?
Is there a best practise to store the information which locale is sent by the request, so that I can access it in my services and repositories? I think the value must be request/thread specific. Giving it as a method parameter down to the other layer seems pretty ugly.
Thank you
Regards form Germany

Thymeleaf cannot detect templates inside spring-boot project

I have the following project structure in my Spring boot app, in which I want to use Thymeleaf
projectName
-Gradle-Module1(Spring boot module)
-build
-src
-main
-resources
-templates
index.html
build.gradle
-Gradle-Module2
...
build.gradle
...
but the spring-boot cannot find my template directory and is showing warning
Cannot find template location: classpath:/templates/ (please add some templates or check your Thymeleaf configuration)
PS: I am using #EnableAutoConfiguration
In my controller code I am doing something like:
#Controller
#EnableAutoConfiguration
public class BaseController {
#RequestMapping(value = "/")
public String index() {
return "index.html";
}
}
and index.html file just prints hello world.
So typically it should look inside src/resources/templates/(of same Gradle module I suppose), but somehow it is not able to find it.
When I try to access localhost:8080 I am getting below error
Error resolving template "index.html", template might not exist or might not be accessible by any of the configured Template Resolvers`
Is there anything I am missing?
Any pointers appreciated. Thanks.
You have to configure Thymeleaf as follows:
#Configuration
public class ThymeleafConfig {
#Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setCacheable(false);
templateResolver.setPrefix("classpath:/templates/");
templateResolver.setSuffix(".html");
return templateResolver;
}
#Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine springTemplateEngine = new SpringTemplateEngine();
springTemplateEngine.addTemplateResolver(templateResolver());
return springTemplateEngine;
}
#Bean
public ThymeleafViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setOrder(1);
return viewResolver;
}
}
Spring doc recommends to add #EnableAutoConfiguration annotation to your primary #Configuration class.
Also it seems you have wrong project structure, the typical package hierarchy is:
src
|- main
|- java
|- resources
|- static
|- templates
|- test
In this case your templates will be in src/main/resources/templates, not in src/resources/templates/.
You should only return the file name. E.g without the .hmtl
#RequestMapping(value = "/")
public String index() {
return "index";
}
#GetMapping("/")
public String index() {
return "index";
}

Upgrading to Thymeleaf 3 and Re-Implementing Rich HTML Email in Spring

I am migrating my project from Thymeleaf 2 to 3 and I'm having an issue with the email template resolver interfering with my web template resolver.
When we first implemented Thymeleaf we followed this document to allow for email templates and web templates and had no issues (section 4.2):
http://www.thymeleaf.org/doc/articles/springmail.html
During conversion to Thymeleaf 3, I worked with ONLY the one template resolver for HTML templates (using the SpringResourceTemplateResolver) and the conversion was fine. I was able to run my application without any problems and the view templates all rendered without problems.
However, when including the email template resolver (using the ClassLoaderTemplateResolver), the following error occurs when going to any controller endpoint that returns a view:
java.io.FileNotFoundException: ClassLoader resource "thymeleaf/thymeleaf/login.html" does not exist
Which indicates it is trying to load the template from the ClassLoaderTemplateResolver and not the SpringResourceTemplateResolver. In Thymeleaf 2 setting the Order attribute solved this problem but this doesn't appear to work in Thymeleaf 3.
I couldn't find any help in the Thymeleaf 3 documentation for having 2 Template resolvers like there was in Thymeleaf 2 docs.
Any suggestions on how to get this working or documentation that I may have overlooked?
I have this same setup (a ClassLoaderTemplateResolver for emails and a SpringResourceTemplateResolver for html page templates). I think you need to set this property:
resolver.setCheckExistence(true);
On whichever of your template resolvers happens first, otherwise spring will assume it exists and you will see the error your seeing.
You can use the following setup..
AppConfig.java
#Configuration
#EnableWebMvc
#ComponentScan("myapp")
public class AppConfig extends WebMvcConfigurerAdapter implements ApplicationContextAware {
private ApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(webTemplateEngine());
resolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
return resolver;
}
#Bean
public TemplateEngine webTemplateEngine() {
//this method must be defined as a bean otherwise i18n messages are not found
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setEnableSpringELCompiler(true);
engine.addTemplateResolver(webTemplateResolver());
return engine;
}
private ITemplateResolver webTemplateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(applicationContext);
resolver.setPrefix("/WEB-INF/thymeleaf/");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setSuffix(".html");
resolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
return resolver;
}
#Bean
public TemplateEngine emailTemplateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(emailTemplateResolver());
return templateEngine;
}
private ITemplateResolver emailTemplateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(applicationContext);
resolver.setPrefix("/WEB-INF/thymeleaf/email/");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setSuffix(".html");
resolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
return resolver;
}
}
And then where you need to use the email template engine, just autowire the emailTemplateEngine and use it
#Autowired
private TemplateEngine emailTemplateEngine;

How do I configure multiple JPA data sources using Spring #Configuration class?

I am trying to configure multiple JPA entity/transaction managers within the same application context using Spring's #Configuration class.
When the context loads, Spring is having difficulties auto-wiring the beans because they implement the same interfaces.
Unfortunately, I'm using legacy code so I can't auto-wire the beans directly and use the #Qualifier annotations, which is why I'm trying to do it using the configuration class.
Within a #Bean declaration, is there any way to qualify which bean should be injected? I thought that using a direct method call would be enough, but it typically results in errors such as
NoUniqueBeanDefinitionException: No qualifying bean of type
[javax.sql.DataSource] is defined: expected single matching bean but
found 4
Here's an example of what I'm trying to do below:
#Configuration
public class ApplicationConfig {
#Bean(name = "transactionManager1")
public PlatformTransactionManager transactionManager1() {
return new JpaTransactionManager(entityManagerFactory1());
}
#Bean(name = "entityManagerFactory1")
public EntityManagerFactory entityManagerFactory1() {
...
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource1());
...
}
#Bean(destroyMethod = "")
#ConfigurationProperties(prefix = "datasource.test1")
public JndiObjectFactoryBean jndiObjectFactoryBean1() {
return new JndiObjectFactoryBean();
}
#Bean(name = "dataSource1")
public DataSource dataSource1() {
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
return lookup.getDataSource(jndiObjectFactoryBean1().getJndiName());
}
#Bean(name = "transactionManager2")
public PlatformTransactionManager transactionManager2() {
return new JpaTransactionManager(entityManagerFactory2());
}
#Bean(name = "entityManagerFactory2")
public EntityManagerFactory entityManagerFactory2() {
...
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource2());
...
}
#Bean(destroyMethod = "")
#ConfigurationProperties(prefix = "datasource.test2")
public JndiObjectFactoryBean jndiObjectFactoryBean2() {
return new JndiObjectFactoryBean();
}
#Bean(name = "dataSource2")
public DataSource dataSource2() {
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
return lookup.getDataSource(jndiObjectFactoryBean2().getJndiName());
}
I suppose I could try to inject the beans directly via the Spring context's getBean() method, but is there a cleaner way of doing this?
I'm not too familiar with the #Primary annotation, but based on what I've read I don't know how spring would autowire the secondary data source in this case since it looks like it would always pick the beans with #Primary first.
If you cannot change the injection sites to add qualifiers, then you're going to have to create a delegating DataSource based on some logic (which you haven't detailed in the question).
Something like this.
#Primary #Bean
public DelegatingDataSource delegatingDataSource(List<DataSource> sources) {
return new DelegatingDataSource() {
#Override
public DataSource getTargetDataSource() {
// decide which dataSource to delegate to
return sources.get(0);
}
}
}
I've used DelegatingDataSource, but that may not be able to provide what you need. You may need to get more advanced with some kind of interceptor/aspect to get details of the caller on which to base the DataSource selection.
However it's implemented, you need to specify a #Primary bean and use it as a proxy.

Categories