Programmatically resolve Thymeleaf templates - spring

I'm trying to render XML/JSON using Thymeleaf templates. I don't want to render a view using the template name, just want to resolve the template as shown below. Trouble is all I get back is the template name, not it's content.
Set up:
#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.name());
templateResolver.setCacheable(false);
return templateResolver;
}
#Bean
SpringTemplateEngine templateEngine(ApplicationContext appCtx) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.addTemplateResolver(xmlTemplateResolver(appCtx));
return templateEngine;
}
Template (src/main/resources/templates/breakfast-menu.xml):
<?xml version="1.0" encoding="UTF-8"?>
<breakfast_menu>
<food>
<name>${item['name']}</name>
<price>${item['price']}</price>
<description>${item['description']}</description>
<calories>${item['calories']}</calories>
</food>
</breakfast_menu>
Usage:
#Autowired
SpringTemplateEngine templateEngine;
someMethod() {
Context context = new Context();
context.setVariable("item", item);
item.put("name", "Waffle");
String content = templateEngine.process("breakfast-menu", context);
// content == "breakfast-menu". WTH?
}
Using Thymeleaf 3.0.0.BETA01.

I solved this issue with help from the Thymeleaf user forum. For reasons unbeknownst to me, templateEngine.addTemplateResolver doesn't work but templateEngine.setTemplateResolver does. The templates for XML and JSON output are shown below:
XML:
<?xml version="1.0" encoding="UTF-8"?>
<breakfast_menu>
<food>
<name th:text="${item['name']}"></name>
<price th:text="${item['price']}"></price>
<description th:text="${item['description']}"></description>
<calories th:text="${item['calories']}"></calories>
</food>
</breakfast_menu>
JSON:
{
"food": {
"name": "[[${item['name']}]]",
"price": "[[${item['price']}]]",
"description": "[[${item['description']}]]",
"calories": "[[${item['calories']}]]"
}
}

Just for the sake of completeness is here some code to resolve a Thymeleaf template with plain-Thymeleaf without Spring.
This is for HTML but you can adapt it to some other template mode by setting another parameter for the setTemplateMode call:
Maven dependency:
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
Java class:
import java.util.Map;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.StringTemplateResolver;
public class ThymeleafResolver {
public static String resolveHTMLWithThymeleaf(String html, Map<String, Object> variables) {
TemplateEngine templateEngine = new TemplateEngine();
StringTemplateResolver templateResolver = new StringTemplateResolver();
templateResolver.setTemplateMode(TemplateMode.HTML);
templateEngine.setTemplateResolver(templateResolver);
Context context = new Context();
context.setVariables(variables);
return templateEngine.process(html, context);
}
}
Testcase Java code:
Map<String, Object> variables = new HashMap<>();
variables.put("mytext", "Hello World");
String result = ThymeleafInterpreter.interpretHTMLWithThymeleaf("<html><body><span th:text=\"${mytext}\"></span></body></html>", variables);
Assert.assertEquals("<html><body><span>Hello World</span></body></html>", result);

Related

Spring Boot, Thymeleaf and strings

Hello I'm using spring boot 2.4.x and I want to use thymeleaf as my template engine.
I've added the thymeleaf starter and I've the following test
#Test
void name() {
StringTemplateResolver stringTemplateResolver = new StringTemplateResolver();
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.addTemplateResolver(stringTemplateResolver);
Context ctx = new Context();
ctx.setVariable("franco", "prova");
String processedTemplate = templateEngine.process("<html>${franco}</html>", ctx);
Assertions.assertThat(processedTemplate).isEqualTo("<html>prova</html>");
}
but the templateEngine is not substituting the variable and the value of the processedTemplate variable is the same as the input string.
What's my fault?
Thank you
Thymeleaf won't process variables directly in HTML. You either have to use the text inlining syntax:
<html>[[${franco}]]</html>
or do it the standard way (which is to use attributes prefixed with th:) like this:
<html th:text="${franco}" />
or
<html><span th:text="${franco}" /></html>
(Also, as a side note StringTemplateResolver is the default resolver, so you don't have to configure it. Your complete code can look like this.)
#Test
void name() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
Context ctx = new Context();
ctx.setVariable("franco", "prova");
String processedTemplate = templateEngine.process("<html>[[${franco}]]</html>", ctx);
Assertions.assertThat(processedTemplate).isEqualTo("<html>prova</html>");
}

How to Load templates from external folder using Thymeleaf

I'm working with spring boot and thymeleaf to generate Documents from html templates.
As the templates continuously changes, i want ti to load templates from an external just to add or remove templates from there instead of redeploy the application.
As a POC, when using /resources folder works fine.
This is the error:
Error resolving template "voucher", the template might not exist or might
not be accessible by any of the configured Template Resolvers
This is the context:
applycation.yml
spring:
thymeleaf:
prefix: file:///${PARAMETERS_DIRECTORY_TEMPLATES:/home/app/templates/}
check-template-location: true
suffix=: .html
mode: HTML
encoding: UTF-8
This is my Method:
Where templateName is the template filename and parameters is just a map who has the values to be replaced by the engine.
#Override
public String buildHtmlFromTemplate(String templateName, Map<String, String> parameters) {
TemplateEngine templateEngine = new TemplateEngine();
FileTemplateResolver templateResolver = new FileTemplateResolver ();
templateResolver.setOrder(templateEngine.getTemplateResolvers().size());
templateResolver.setCacheable(false);
templateResolver.setCheckExistence(true);
templateEngine.setTemplateResolver(templateResolver);
return templateEngine.process(templateName, this.resolveHtmlTemplateAttributesContext(parameters));
}
NOTE:
I removed tha applycation yml thymeleaf configs and implemented next code but the error persists.
#Override
public String buildHtmlFromTemplate(String templateName, Map<String, String> parameters) {
TemplateEngine templateEngine = new TemplateEngine();
FileTemplateResolver templateResolver = new FileTemplateResolver ();
templateResolver.setPrefix("/home/skeeter/templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML");
templateResolver.setOrder(templateEngine.getTemplateResolvers().size());
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setCacheable(false);
templateResolver.setCheckExistence(true);
templateEngine.setTemplateResolver(templateResolver);
return templateEngine.process(templateName, this.resolveHtmlTemplateAttributesContext(parameters));
}
0
Curiously, the issue was solved using this code and the /usr/app/templates created with sudo.
I think it was only a permissions issue
.....
#Value("${parameters.directory.templates}")
private String templatesDirectory;
.....
#Override
public String buildHtmlFromTemplate(String templateName, Map<String, String> parameters) {
TemplateEngine templateEngine = new TemplateEngine();
FileTemplateResolver templateResolver = new FileTemplateResolver ();
templateResolver.setPrefix(templatesDirectory);
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML");
templateResolver.setOrder(templateEngine.getTemplateResolvers().size());
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setCacheable(false);
templateResolver.setCheckExistence(true);
templateEngine.setTemplateResolver(templateResolver);
return templateEngine.process(templateName, this.resolveHtmlTemplateAttributesContext(parameters));
}
In my case I was using the ClassLoaderTemplateResolver, but after I changed it to FileTemplateResolver I can use the absolute path and it works fine.
Double check that you have read permissions in the directory where you have the templates.

Spring-Boot Elasticseach EntityMapper can not be autowired

Based on this answer and the comments I implemented the code to receive the scores of an elastic search query.
public class CustomizedHotelRepositoryImpl implements CustomizedHotelRepository {
private final ElasticsearchTemplate elasticsearchTemplate;
#Autowired
public CustomizedHotelRepositoryImpl(ElasticsearchTemplate elasticsearchTemplate) {
super();
this.elasticsearchTemplate = elasticsearchTemplate;
}
#Override
public Page<Hotel> findHotelsAndScoreByName(String name) {
QueryBuilder queryBuilder = QueryBuilders.boolQuery()
.should(QueryBuilders.queryStringQuery(name).lenient(true).defaultOperator(Operator.OR).field("name"));
NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder().withQuery(queryBuilder)
.withPageable(PageRequest.of(0, 100)).build();
DefaultEntityMapper mapper = new DefaultEntityMapper();
ResultsExtractor<Page<Hotel>> rs = new ResultsExtractor<Page<Hotel>>() {
#Override
public Page<Hotel> extract(SearchResponse response) {
ArrayList<Hotel> hotels = new ArrayList<>();
SearchHit[] hits = response.getHits().getHits();
for (SearchHit hit : hits) {
try {
Hotel hotel = mapper.mapToObject(hit.getSourceAsString(), Hotel.class);
hotel.setScore(hit.getScore());
hotels.add(hotel);
} catch (IOException e) {
e.printStackTrace();
}
}
return new PageImpl<>(hotels, PageRequest.of(0, 100), response.getHits().getTotalHits());
}
};
return elasticsearchTemplate.query(nativeSearchQuery, rs);
}
}
As you can see I needed to create a new instance of DefaultEntityMapper mapper = new DefaultEntityMapper(); which should not be the case because it should be possible to #Autowire EntityMapper. If I do so, I get the exception that there is no bean.
Description:
Field entityMapper in com.example.elasticsearch5.es.cluster.repository.impl.CustomizedCluserRepositoryImpl required a bean of type 'org.springframework.data.elasticsearch.core.EntityMapper' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.data.elasticsearch.core.EntityMapper' in your configuration.
So does anybody know if its possible to autowire EntityMapper directly or does it needs to create the bean manually using #Bean annotation.
I use spring-data-elasticsearch-3.0.2.RELEASE.jar where the core package is inside.
My pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-elasticsearch</artifactId>
</dependency>
I checked out the source code of spring-data-elasticsearch. There is no bean/comoponent definition for EntityMapper. It seems this answer is wrong. I test it on my project and get the same error.
Consider defining a bean of type 'org.springframework.data.elasticsearch.core.EntityMapper' in your configuration.
I couldn't find any other option by except defining a #Bean

spring boot calling thymeleaf template failed

im getting this error i searched on the same issues like mine on stack and i have foudn that i shouldn t put .html when calling it but im getting the same error :
`Caused by: org.thymeleaf.exceptions.TemplateInputException: Error resolving template "orderConfirmationEmailTemplate", template might not exist or might not be accessible by any of the configured Template Resolver`s
my mail constructor :
#Component
public class MailConstructor {
#Autowired
private Environment env;
#Autowired
private TemplateEngine templateEngine;
public SimpleMailMessage constructNewUserEmail(User user, String password) {
String message="\nPlease use the following credentials to log in and edit your personal information including your own password."
+ "\nUsername:"+user.getUsername()+"\nPassword:"+password;
SimpleMailMessage email = new SimpleMailMessage();
email.setTo(user.getEmail());
email.setSubject("Le's Bookstore - New User");
email.setText(message);
email.setFrom(env.getProperty("support.email"));
return email;
}
public MimeMessagePreparator constructOrderConfirmationEmail (User user, Order order, Locale locale) {
Context context = new Context();
context.setVariable("order", order);
context.setVariable("user", user);
context.setVariable("cartItemList", order.getCartItemList());
String text = templateEngine.process("orderConfirmationEmailTemplate.html", context);
MimeMessagePreparator messagePreparator = new MimeMessagePreparator() {
#Override
public void prepare(MimeMessage mimeMessage) throws Exception {
MimeMessageHelper email = new MimeMessageHelper(mimeMessage);
email.setTo(user.getEmail());
email.setSubject("Order Confirmation - "+order.getId());
email.setText(text,true);
email.setFrom(new InternetAddress("alaaeddinezammel1993#gmail.com"));
}
};
return messagePreparator;
}
and im calling it from rest service:
mailSender.send(mailConstructor.constructOrderConfirmationEmail(user, order, Locale.ENGLISH));
shoppingCartService.clearShoppingCart(shoppingCart);
and im putting the file .html under package in the project
In your question, the TemplateEngine is auto wired so I cannot see how it is configured but it in order to discover your template from the location com.bookstore.domain.security.templates the configuration should look something like this:
#Bean
public TemplateEngine templateEngine() {
final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.addTemplateResolver(templateResolver());
return templateEngine;
}
private ITemplateResolver templateResolver() {
final ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
templateResolver.setPrefix(“/com/bookstore/domain/security/templates“);
templateResolver.setSuffix(".html");
…
return templateResolver;
}
In this code I am configuring the TemplateEngine in code, perhaps you are using XML. Regardless of how you are configuring the TemplateEngine, you are clearly using Spring to do so (since you inject it into your MailConstructor), the key point here is that regardless of how you configure it you need to tell it where to find your template and the way to do that is to invoke the ITemplateResolver's setPrefix() method.
Plenty more details in the article titled Sending email in Spring with Thymeleaf in the Thymeleaf docs.
actually with my configuration putted in my question i just put the .html file under file called templates under resources and it works the mail is sent , spring boot is apparently auto configured with this path without configuring the
templateResolver

EclipseLink Moxy unmarshall and getValueByXPath gives null

I use the below code to get unmarshall and query the unmarshelled object by Xpath.
I am able to get the object after unmarshalling, but while querying by XPath, the value is coming as null.
Do I need to specify any NameSpaceResolver?
Please let me know if you are looking for any further information.
My code:
JAXBContext jaxbContext = (JAXBContext) JAXBContextFactory.createContext(new Class[] {Transaction.class}, null);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
StreamSource streamSource= new StreamSource(new StringReader(transactionXML));
transaction = unmarshaller.unmarshal(streamSource, Transaction.class).getValue();
String displayValue = jaxbContext.getValueByXPath(transaction, xPath, null, String.class);
My XML:
<Transaction xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" >
<SendingCustomer firstName="test">
</SendingCustomer>
</Transaction>
Since there are no namespaces in your example you do not need to worry about leveraging a NamespaceResolver. You didn't provide the XPath that you were having trouble with, so I have just picked one in the example below.
JAVA MODEL
Transaction
import javax.xml.bind.annotation.*;
#XmlRootElement(name="Transaction")
public class Transaction {
#XmlElement(name="SendingCustomer")
private Customer sendingCustomer;
}
Customer
import javax.xml.bind.annotation.XmlAttribute;
public class Customer {
#XmlAttribute
private String firstName;
#XmlAttribute
private String lastNameDecrypted;
#XmlAttribute(name="OnWUTrustList")
private boolean onWUTrustList;
#XmlAttribute(name="WUTrustListType")
private String wuTrustListType;
}
DEMO CODE
input.xml
<Transaction xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SendingCustomer firstName="test" lastNameDecrypted="SMITH"
OnWUTrustList="false" WUTrustListType="NONE">
</SendingCustomer>
</Transaction>
Demo
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import org.eclipse.persistence.jaxb.JAXBContext;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jaxbContext = (JAXBContext) JAXBContextFactory.createContext(new Class[] {Transaction.class}, null);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
StreamSource streamSource= new StreamSource("src/forum17687460/input.xml");
Transaction transaction = unmarshaller.unmarshal(streamSource, Transaction.class).getValue();
String displayValue = jaxbContext.getValueByXPath(transaction, "SendingCustomer/#firstName", null, String.class);
System.out.println(displayValue);
}
}
Output
test

Resources