Spring Boot & Thymeleaf with XML Templates - spring-mvc

I have a Spring Boot application with a controller that returns a ModelAndView and Thymeleaf to render templates, where the templates live in /src/main/resources/templates/*.html
This works fine, but How can I configure Spring and/or Thymeleaf to look for xml files instead of html?
If it helps, I'm using Gradle with the org.springframework.boot:spring-boot-starter-web dependency to set things up. I am currently running the server using a class with a main method.

After trying and failing at various bean defs for viewResolver and related things, I finally got this working with a change to my application.yaml file:
spring:
thymeleaf:
suffix: .xml
content-type: text/xml
For those reading this later, you can do similar with your application.properties file (with dot notation in place of the yaml indentation).

This works too :
#Configuration
public class MyConfig
{
#Bean
SpringResourceTemplateResolver xmlTemplateResolver(ApplicationContext appCtx) {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(appCtx);
templateResolver.setPrefix("classpath:/templates/");
templateResolver.setSuffix(".xml");
templateResolver.setTemplateMode("XML");
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setCacheable(false);
return templateResolver;
}
#Bean(name="springTemplateEngine")
SpringTemplateEngine templateEngine(ApplicationContext appCtx) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(xmlTemplateResolver(appCtx));
return templateEngine;
}
}
And for the usage
#RestController
#RequestMapping("/v2/")
public class MenuV2Controller {
#Autowired
SpringTemplateEngine springTemplateEngine;
#GetMapping(value ="test",produces = {MediaType.APPLICATION_XML_VALUE})
#ResponseBody
public String test(){
Map<String, String> pinfo = new HashMap<>();
Context context = new Context();
context.setVariable("pinfo", pinfo);
pinfo.put("lastname", "Jordan");
pinfo.put("firstname", "Michael");
pinfo.put("country", "USA");
String content = springTemplateEngine.process("person-details",context);
return content;
}
}
Don't forget the template in resources/templates folder
<?xml version="1.0" encoding="UTF-8"?>
<persons >
<person>
<fname th:text="${pinfo['lastname']}"></fname>
<lname th:text="${pinfo['firstname']}"></lname>
<country th:text="${pinfo['country']}"></country>
</person>
</persons>

Related

Thymeleaf View Resolver with SpringMVC webapp

How if one can use ThymeleafViewResolver as a defalt view resolver.
Currentely my WebMvcConfig looks like this
#Configuration
#ComponentScan(basePackages="com.greatLearning.ticketAPI.controller")
public class WebMvcConfig implements WebMvcConfigurer {
#Autowired
ApplicationContext applicationContext;
// #Override
// public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// configurer.enable();
// }
public InternalResourceViewResolver resolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
//Thymeleaf ViewResolver
#Bean
public SpringResourceTemplateResolver templateResolver(){
// SpringResourceTemplateResolver automatically integrates with Spring's own
// resource resolution infrastructure, which is highly recommended.
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(this.applicationContext);
templateResolver.setPrefix("/WEB-INF/views/");
templateResolver.setSuffix(".html");
// HTML is the default value, added here for the sake of clarity.
templateResolver.setTemplateMode(TemplateMode.HTML);
// Template cache is true by default. Set to false if you want
// templates to be automatically updated when modified.
templateResolver.setCacheable(true);
return templateResolver;
}
#Bean
public SpringTemplateEngine templateEngine(){
// SpringTemplateEngine automatically applies SpringStandardDialect and
// enables Spring's own MessageSource message resolution mechanisms.
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
// Enabling the SpringEL compiler with Spring 4.2.4 or newer can
// speed up execution in most scenarios, but might be incompatible
// with specific cases when expressions in one template are reused
// across different data types, so this flag is "false" by default
// for safer backwards compatibility.
templateEngine.setEnableSpringELCompiler(true);
return templateEngine;
}
#Bean
public ThymeleafViewResolver viewResolver(){
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
// NOTE 'order' and 'viewNames' are optional
viewResolver.setOrder(1);
viewResolver.setViewNames(new String[] {".html", ".xhtml"});
return viewResolver;
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/resources/**")
.addResourceLocations("/resources/");
}
}
for *.jsp view it is working just fine, but I want to use Thymeleaf to resolve my .html templates becasue I don't want to use *.jsp .
One thing to note is that in my controller
TestController.java
#RequestMapping("/hello")
public String method1() {
return "hello";
}
#RequestMapping("/hello2")
#ResponseBody
public String method2() {
return "hello"; //This works fine
}
when I hit /hello api this is what I get
How do I configre viewResolver so that spring picks up ThymeleafViewResolver to resolve html rather than jsp
Problem Solved!!!!!
Here is what I did after an hour of debugging the libraries
in WebMvcConfig.java I revomed the public InternalResourceViewResolver resolver() method as it is sed to configure jsp.
Now in ThymeleafViewResolver viewResolver() remove these two:
viewResolver.setOrder(1);
viewResolver.setViewNames(new String[] {".html", ".xhtml"});
got help through debgging and [this post](Problem Solved!!!!!
Here is what I did after an hour of debugging the libraries
in WebMvcConfig.java I revomed the public InternalResourceViewResolver resolver() method as it is sed to configure jsp.
Now in ThymeleafViewResolver viewResolver() remove these two:
viewResolver.setOrder(1);
viewResolver.setViewNames(new String[] {".html", ".xhtml"});
got help through debgging and this post.
As to why I will answer if anyone is interested. :)

RedisSentinelConfiguration using spring.redis.sentinel.nodes with spring boot

Trying to configure spring boot application with spring-session and redis but having below issue. Not able to resolve it.
Constructor threw exception; nested exception is java.lang.IllegalStateException: BeanFactory has not been injected into #Configuration class
This code works fine for me
#Configuration
#EnableRedisHttpSession
public class HttpSessionConfig {
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.readFrom(SLAVE_PREFERRED)
.build();
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration()
.master("mymaster")
.sentinel("192.168.56.50", 26379)
.sentinel("192.168.56.50", 26380)
.sentinel("192.168.56.50", 26381);
#Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory(sentinelConfig, clientConfig);
}
}
but not this code using PropertySource.
Spring document says:-
**RedisSentinelConfiguration can also be defined with a PropertySource, which lets you set the following properties:
Configuration Properties
spring.redis.sentinel.master: name of the master node.
spring.redis.sentinel.nodes: Comma delimited list of host:port pairs.**
#Configuration
#EnableRedisHttpSession
#PropertySource(name="application", value="classpath:application.properties")
public class HttpSessionConfig {
#Resource
ConfigurableEnvironment environment;
#Bean
public PropertiesPropertySource propertySource() {
return (PropertiesPropertySource) environment.getPropertySources().get("defaultProperties");
}
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.readFrom(SLAVE_PREFERRED)
.build();
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration(propertySource());
#Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory(sentinelConfig, clientConfig);
}
}
application.properties
server.port=8090
spring.security.user.name=admin
spring.security.user.password=admin
spring.redis.sentinel.master=mymaster
spring.redis.sentinel.nodes=192.168.56.50:26379,192.168.56.50:26380,192.168.56.50:26381
spring.application.name=spring-session-demo
The format of sentinel nodes property is comma separated key:value pairs. So you can extract host and port by java split() function.
#Autowired
private Environment env;
#Bean
public LettuceConnectionFactory connectionFactory() {
RedisSentinelConfiguration sentinelConfig = new RedisSentinelConfiguration();
String master = env.getProperty("spring.redis.sentinel.master");
String nodes = env.getProperty("spring.redis.sentinel.nodes");
sentinelConfig.master(master);
for (String node : nodes.split(",")) {
String split[] = node.split(":");
sentinelConfig.sentinel(split[0], Integer.parseInt(split[1]));
}
...
}

Embedded Jetty + Spring MVC main method

I'm using Embedded Jetty and Spring MVC and i'm starting my WebApplication through the main function.
i don't like the way it's looks, it's not very clean and i have the feeling i'm doing something wrong even though it's working just fine.
my problem is that i want to initialize ServerHolder,ServletContextHandler and Server objects outside the main method, but i cannot do it because DispatcherServlet requires and ApplicationContext.
Any ideas?
public static void main(String[] args) throws Exception {
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(EPConfiguration.class);
ServletHolder servletHolder = new ServletHolder(new DispatcherServlet(applicationContext));
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
context.addServlet(servletHolder, "/*");
BasicConfigurator.configure();
Server server = new Server();
server.setHandler(context);
//HTTP
ServerConnector connector = new ServerConnector(server);
connector.setPort(9999);
server.setConnectors(new Connector[]{connector});
server.start();
server.join();
}
I'm not familier with Jetty, but you can setup and initialize DispatcherServlet in the following way:
Declare a java class which implements the abstract class AbstractAnnotationConfigDispatcherServletInitializer (it usually will be called WebAppInitializer).
Implement its abstract methods - getRootConfigClasses() (which return the classes which configure Application Context), getServletConfigClasses() (which returns the classes which configure the ServletContext) and getServletMappings() (which sets the DispatcherServletmapping).
I usually call the class that configures the ServletContext as WebConfig. You should annotate it with the #Configuration and #EnableWebMvc. In addition you should use it to define a ViewResolver bean:
```java
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/view"):
resolver.setSuffix(".jsp");
resolver.setExposeContextBeansAsAttributes(true);
return resolver;
}
```

springfox does not return the api doc

I'm trying to integrate springfox in to my existing sprint web application I configured the springfox the web app is starting correctly but the api doc is not getting generated
here is the springfox configuration class
#EnableSwagger2 //Loads the spring beans required by the framework
public class SwaggerConfig {
#Bean
public Docket swaggerSpringMvcPlugin() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.paths(PathSelectors.any())
.build()
.apiInfo(apiInfo());
}
}
here is the bean creation
<bean id="config" class="com.myApp.general.SwaggerConfig"/>
following is the controller
#Controller
public class MyController {
#ApiOperation(value = "Gets architecture services",
notes = "",
produces = "application/json")
#RequestMapping(value = "v1/users", method = RequestMethod.GET)
#ResponseBody
public Object users(HttpServletResponse response) {
//method implementation
}
}
when i try to get the api doc it just returns a 404. can someone help me on this
it may be a late answer..but should help people still looking/searching
anyway the below answer should work .
for html the ui.html handler is needed
and for others webjars/** is needed
uri.startsWith("/swagger")|| uri.startsWith("/webjars")||uri.startsWith("/v2/api-docs");
if you have filter-chain to access-specific url's ,be sure to omit any filter cheking similar to above code
#Component
public class SwaggerConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}....
adding above code makes your spring application to look into the folders for swagger-ui files.
if you already have a resource handler..donot forget to include these there.In this case no need to write a special resource handler

Spring Boot sending empty json for custom FreeMarker login page

In Spring Boot I added #Configuration annotated with #EnableWebMvc for custom MVC. It uses Freemarker templates under src/main/resources/templates. Problem is the login page is getting send back to browser as empty json . Do I need to add extra content negotiation or something else? Thx
#Configuration
#EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public ContentNegotiatingViewResolver contentViewResolver() throws Exception {
ContentNegotiationManagerFactoryBean contentNegotiationManager = new ContentNegotiationManagerFactoryBean();
contentNegotiationManager.addMediaType("json", MediaType.APPLICATION_JSON);
FreeMarkerViewResolver viewResolver = new FreeMarkerViewResolver();
viewResolver.setPrefix("/templates/");
viewResolver.setSuffix(".ftl");
MappingJackson2JsonView defaultView = new MappingJackson2JsonView();
defaultView.setExtractValueFromSingleKeyModel(true);
ContentNegotiatingViewResolver contentViewResolver = new ContentNegotiatingViewResolver();
contentViewResolver.setContentNegotiationManager(contentNegotiationManager.getObject());
contentViewResolver.setViewResolvers(Arrays.<ViewResolver> asList(viewResolver));
contentViewResolver.setDefaultViews(Arrays.<View> asList(defaultView));
return contentViewResolver;
}
....
}
#Controller
#SessionAttributes
public class LoginController {
#RequestMapping("/login")
public String login() {
return "login";
}
}
The login controller was missing the appropriate content type setting for the client HTTP GET, so I added this produces attribute.
#RequestMapping(value = "/login", produces="application/xml")

Resources