Issue
I'm trying to create a proof of concept Spring Boot application that leverages Thymeleaf to populate a string structure based upon properties and request parameters. I had it working using the Model implementation, but that only works if the populated template is the response. In this case, I want the populated template to be used within the project.
@Controller
@RequestMapping("/poc")
public class InfoController {
@Autowired
ThymeleafService thymeleafService;
@Value("${reportType}")
private String reportType;
// This function works - the reportType variable is populated based upon application.properties
@GetMapping("/map")
public String testMap(@RequestParam(name = "id", required = false, defaultValue = "2")
String id, Model model){
model.addAttribute("reportType", "\"" + reportType + "\"");
return id + ".json";
}
// this function does not work - the reportType variable is null
@RequestMapping(value = "/map2",method= RequestMethod.POST)
public String testMap2(@RequestBody MapRequest request, @RequestParam(name = "id", required = false, defaultValue = "2")
String id, Model model) {
String output = thymeleafService.processTemplate(request, id);
return output;
}
}
The ThymeleafService is pretty simple.
@Component
public class ThymeleafService {
@Value("${reportType}")
private String reportType;
private TemplateEngine templateEngine;
public String processTemplate(MapRequest request, String templateId){
templateEngine = new TemplateEngine();
StringTemplateResolver resolver = new StringTemplateResolver();
resolver.setTemplateMode(TemplateMode.TEXT);
templateEngine.addTemplateResolver(resolver);
// Map<String, Object> attributes = new HashMap<>();
final Context ctxt = new Context(Locale.US);
ctxt.setVariable("reportType", "\"" + reportType + "\"");
// for (String name : ctxt.getVariableNames()){
//// attributes.put(name, ctxt.getVariable(name));
// }
TemplateSpec templateSpec = new TemplateSpec(templateId + ".json", null, TemplateMode.TEXT, attributes);
StringWriter writer = new StringWriter();
templateEngine.process(templateSpec, ctxt, writer);
return writer.toString();
}
}
POM.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.poc</groupId>
<artifactId>map</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mappoc</name>
<description>POC</description>
<properties>
<java.version>11</java.version>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
<version>3.1.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Here is the template:
ReportType: [(${reportType})]
What I end up getting back from map is:
ReportType: "configReportType"
But what I end up getting back from map2 is:
ReportType:
I've tried setting the attributes in the Context
, with and without an AttributeMap
in the TemplateEngine.process
function. I've tried using a TemplateSpec
and tried just a string for the template name.
I've debugged the code and confirmed that the reportType variable is getting set and passed into the process
function. But for some reason beyond me, it's not actually injecting the data into the response. I feel like I'm missing something really obvious, but I can't figure it. Any help would be greatly appreciated.
NOTE: The reason I can't just use the Model paradigm is because while right now, it returns the data to the caller, the end goal is to USE that data.
Solution
I finally managed to get this working - the trick was that I had to:
- Use
ClassLoaderTemplateResolver
instead ofStringTemplateResolver
- Change the response in the Controller from
String
toResponseEntity
- Remove
Model
from the Controller signature
Answered By - Jeremy Smith
Answer Checked By - David Marino (JavaFixing Volunteer)