Thymeleaf String Template - java

I'm having trouble working out what I'm doing wrong here -
#Test
void should_fill_a_string_template() {
String template = "Hello ${name}";
StringTemplateResolver resolver = new StringTemplateResolver();
Context context = new Context();
context.setVariable("name","Test");
TemplateEngine templateEngine = new TemplateEngine();
templateEngine.setTemplateResolver(resolver);
String result = templateEngine.process(template, context);
Assertions.assertEquals("Hello Test",result);
}
I just want to use the Thymeleaf template processor in a plain old java project without other dependencies. I need to pass in a String (not a file to be resolved) and have the template filled with the values provided.
I see the default behavior here indicates that the StringTemplateResolver should just resolve the string passed in as the template itself and not a reference to the template.
What am I missing here?

Related

Thymeleaf pick json template based on input variable such as clientId

I'd like to be able to configure different json templates based on e.g. a clientId with a default template in case a template for the clientId is not defined.
Context context = new Context();
Map<String, Object> templateResolutionAttribute = new HashMap<>();
templateResolutionAttribute.put("clientId", 12345);//Trying to somehow pass a variable to the template selection process...
TemplateSpec templateSpec = new TemplateSpec(
"json/data.json",
templateResolutionAttribute
);
String output = springTemplateEngine.process(templateSpec, context);
The Spring Config class I'm using looks like this:
#Configuration
public class ThymeLeafConfig {
#Bean
public SpringResourceTemplateResolver jsonMessageTemplateResolver() {
SpringResourceTemplateResolver theResourceTemplateResolver =
new SpringResourceTemplateResolver();
theResourceTemplateResolver.setPrefix("classpath:/templates/");
theResourceTemplateResolver.setResolvablePatterns(
Collections.singleton("json/*"));
theResourceTemplateResolver.setSuffix(".json");
theResourceTemplateResolver.setCharacterEncoding("UTF-8");
theResourceTemplateResolver.setCacheable(false);
theResourceTemplateResolver.setOrder(1);
return theResourceTemplateResolver;
}
The json templates could be in different directories or alternatively have different file names (whatever works best/easiest):
json/
data.json
12345/
data.json
or
json/
data.json
data.12345.json
What is the best or easiest way to achieve returning a template that "matches" the clientId and if no match for the clientId is found return a default template?
Note: For new clients, ideally, I'd like to be able to just add new json templates without making any code changes...

How to load quarkus qute template dynamic without inject?

I had the following problem: I have a service where I want to dynamically render templates using qute. Whose names I don't currently know (because they are passed via the endpoint).
Unfortunately Quarkus itself doesn't give the possibility to say "Template t = new Template()".... You always have to define them via inject at the beginning of a class. After a long time of searching and thinking about it, I have the following solution:
The solution is to inject the Quarkus Template Engine instead of a Template. The Engine could render a template directly.... Then we only have to read our template file as a String (Java 11 can read Files.readString(path, encoding)) and render it with our data map.
#Path("/api")
public class YourResource {
public static final String TEMPLATE_DIR = "/templates/";
#Inject
Engine engine;
#POST
public String loadTemplateDynamically(String locale, String templateName, Map<String, String> data) {
File currTemplate = new File(YourResource.class.getResource("/").getPath() + TEMPLATE_DIR + locale + "/" + templateName + ".html"); // this generates a String like <yourResources Folder> + /templates/<locale>/<templateName>.html
try {
Template t = engine.parse(Files.readString(currTemplate.getAbsoluteFile().toPath(), StandardCharsets.UTF_8));
//this render your data to the template... you also could specify it
return t.data(data.render());
} catch (IOException e) {
e.printStackTrace();
}
return "template not exists";
}
}

Spring boot + Thymeleaf - multiple template resolvers with fallback to default

I am trying to add support for emails with Thymeleaf templates in my Spring Boot app. It works fine as long as I am using only templates stored as .html files. What I would like to do is add support for "overriding" these files with user configured templates. So if template exists in DB, use it. Otherwise, try to use the one from file.
My config looks as follows:
#Configuration
public class ThymeleafConfig {
#Bean
public SpringTemplateEngine springTemplateEngine(DatabaseTemplateResolver databaseTemplateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
databaseTemplateResolver.setTemplateMode(TemplateMode.HTML);
templateEngine.addTemplateResolver(databaseTemplateResolver);
return templateEngine;
}
#Bean
public SpringResourceTemplateResolver htmlTemplateResolver() {
SpringResourceTemplateResolver emailTemplateResolver = new SpringResourceTemplateResolver();
emailTemplateResolver.setPrefix("/templates/");
emailTemplateResolver.setSuffix(".html");
emailTemplateResolver.setTemplateMode(TemplateMode.HTML);
emailTemplateResolver.setCharacterEncoding(StandardCharsets.UTF_8.name());
return emailTemplateResolver;
}
}
And database resolver:
#RequiredArgsConstructor
#Component
public class DatabaseTemplateResolver extends StringTemplateResolver {
#Qualifier("htmlTemplateResolver")
private final SpringResourceTemplateResolver htmlTemplateResolver;
#Override
public ITemplateResource computeTemplateResource(IEngineConfiguration configuration, String ownerTemplate, String templateName, Map<String, Object> templateResolutionAttributes) {
model.runInTransaction(tx -> {
Optional<Template> template = // load template from DB;
if (template.isPresent()) {
return super.computeTemplateResource(configuration, ownerTemplate, template.get().getContent(), templateResolutionAttributes);
} else {
return htmlTemplateResolver.resolveTemplate(configuration, ownerTemplate, templateName, templateResolutionAttributes);
}
}, TransactionDescriptor.asSystemUser());
return null;
}
}
The template is found in DB here and gets returned but I get the following error:
[THYMELEAF][task-2] Exception processing template "email-notification": Error resolving template [email-notification], template might not exist or might not be accessible by any of the configured Template Resolvers
org.thymeleaf.exceptions.TemplateInputException: Error resolving template [email-notification], template might not exist or might not be accessible by any of the configured Template Resolvers
at org.thymeleaf.engine.TemplateManager.resolveTemplate(TemplateManager.java:869)
at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:607)
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1098)
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1059)
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1048)
Does anyone know what am I doing wrong?
I hope you are fine, Did you see this answer?
How to read a Thymeleaf template from DB
I think it's because of the prefix or suffix that you set.

Using M2Doc programmatically : Error in the generated .docx document

I'm trying to use M2Doc programmatically, I managed to generate my .docx file without getting errors in the validation part but I'm getting the following Error in the generated document:
{m:self.Name} Couldn't find the 'aqlFeatureAccess(org.eclipse.emf.common.util.URI.Hierarchical,java.lang.String)' service
The "self.Name" part is what I wrote in my template.
I think I'm lacking some kind of reference to a service but I don't know how to fix it.
The self variable is a reference to a model based on a meta-model I created. But I'm not sure I imported it correctly in my code.
I based my code on the code I found on the M2Doc website + some code I found on their GitHub, especially concerning how to add a service in the queryEnvironment.
I searched in the source code of acceleo and M2Doc to see which services they add but it seems that they already import all the services I'm using.
As I said, the validation part is going well and doesn't generate a validation file.
public static void parseDocument(String templateName) throws Exception{
final URI templateURI = URI.createFileURI("Template/"+templateName+"."+M2DocUtils.DOCX_EXTENSION_FILE);
final IQueryEnvironment queryEnvironment =
org.eclipse.acceleo.query.runtime.Query.newEnvironmentWithDefaultServices(null);
final Map<String, String> options = new HashMap<>(); // can be empty
M2DocUtils.prepareEnvironmentServices(queryEnvironment, templateURI, options); // delegate to IServicesConfigurator
prepareEnvironmentServicesCustom(queryEnvironment, options);
final IClassProvider classProvider = new ClassProvider(ClassLoader.getSystemClassLoader()); // use M2DocPlugin.getClassProvider() when running inside Eclipse
try (DocumentTemplate template = M2DocUtils.parse(templateURI, queryEnvironment, classProvider)) {
ValidationMessageLevel validationLevel = validateDocument(template, queryEnvironment, templateName);
if(validationLevel == ValidationMessageLevel.OK){
generateDocument(template, queryEnvironment, templateName, "Model/ComplexKaosModel.kaos");
}
}
}
public static void prepareEnvironmentServicesCustom(IQueryEnvironment queryEnvironment, Map<String, String> options){
Set<IService> services = ServiceUtils.getServices(queryEnvironment, FilterService.class);
ServiceUtils.registerServices(queryEnvironment, services);
M2DocUtils.getConfigurators().forEach((configurator) -> {
ServiceUtils.registerServices(queryEnvironment, configurator.getServices(queryEnvironment, options));
});
}
public static void generateDocument(DocumentTemplate template, IQueryEnvironment queryEnvironment,
String templateName, String modelPath)throws Exception{
final Map<String, Object> variable = new HashMap<>();
variable.put("self", URI.createFileURI(modelPath));
final Monitor monitor = new BasicMonitor.Printing(System.out);
final URI outputURI = URI.createFileURI("Generated/"+templateName+".generated."+M2DocUtils.DOCX_EXTENSION_FILE);
M2DocUtils.generate(template, queryEnvironment, variable, outputURI, monitor);
}
The variable "self" contains an URI:
variable.put("self", URI.createFileURI(modelPath));
You have to load your model and set the value of self to an element from your model using something like:
final ResourceSet rs = new ResourceSetImpl();
final Resource r = rs.getResource(uri, true);
final EObject value = r.getContents()...;
variable.put("self", value);
You can get more details on resource loading in the EMF documentation.

spring boot 2 properties configuration

I have some code that works properly on spring boot prior to 2 and I find it hard to convert it to work with spring boot 2.
Can somebody assist?
public static MutablePropertySources buildPropertySources(String propertyFile, String profile)
{
try
{
Properties properties = new Properties();
YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
// load common properties
PropertySource<?> applicationYamlPropertySource = loader.load("properties", new ClassPathResource(propertyFile), null);
Map<String, Object> source = ((MapPropertySource) applicationYamlPropertySource).getSource();
properties.putAll(source);
// load profile properties
if (null != profile)
{
applicationYamlPropertySource = loader.load("properties", new ClassPathResource(propertyFile), profile);
if (null != applicationYamlPropertySource)
{
source = ((MapPropertySource) applicationYamlPropertySource).getSource();
properties.putAll(source);
}
}
propertySources = new MutablePropertySources();
propertySources.addLast(new PropertiesPropertySource("apis", properties));
}
catch (Exception e)
{
log.error("{} file cannot be found.", propertyFile);
return null;
}
}
public static <T> void handleConfigurationProperties(T bean, MutablePropertySources propertySources) throws BindException
{
ConfigurationProperties configurationProperties = bean.getClass().getAnnotation(ConfigurationProperties.class);
if (null != configurationProperties && null != propertySources)
{
String prefix = configurationProperties.prefix();
String value = configurationProperties.value();
if (null == value || value.isEmpty())
{
value = prefix;
}
PropertiesConfigurationFactory<?> configurationFactory = new PropertiesConfigurationFactory<>(bean);
configurationFactory.setPropertySources(propertySources);
configurationFactory.setTargetName(value);
configurationFactory.bindPropertiesToTarget();
}
}
PropertiesConfigurationFactory doesnt exist anymore and the YamlPropertySourceLoader load method no longer accepts 3 parameters.
(the response is not the same either, when I have tried invoking the new method the response objects were wrapped instead of giving me the direct strings/integers etc...)
The PropertiesConfigurationFactory should be replaced with Binder class.
Binder class
Sample code:-
ConfigurationPropertySource source = new MapConfigurationPropertySource(
loadProperties(resource));
Binder binder = new Binder(source);
return binder.bind("initializr", InitializrProperties.class).get();
We were also using PropertiesConfigurationFactory to bind a POJO to a
prefix of the Environment. In 2.0, a brand new Binder API was
introduced that is more flexible and easier to use. Our binding that
took 10 lines of code could be reduced to 3 simple lines.
YamlPropertySourceLoader:-
Yes, this class has been changed in version 2. It doesn't accept the third parameter profile anymore. The method signature has been changed to return List<PropertySource<?>> rather than PropertySource<?>. If you are expecting single source, please get the first occurrence from the list.
Load the resource into one or more property sources. Implementations
may either return a list containing a single source, or in the case of
a multi-document format such as yaml a source for each document in the
resource.
Since there is no accepted answer yet, i post my full solution, which builds upon the answer from #nationquest:
private ConfigClass loadConfiguration(String path){
MutablePropertySources sources = new MutablePropertySources();
Resource res = new FileSystemResource(path);
PropertiesFactoryBean propFactory = new PropertiesFactoryBean();
propFactory.setLocation(res);
propFactory.setSingleton(false);
// resolve potential references to local environment variables
Properties properties = null;
try {
properties = propFactory.getObject();
for(String p : properties.stringPropertyNames()){
properties.setProperty(p, env.resolvePlaceholders(properties.getProperty(p)));
}
} catch (IOException e) {
e.printStackTrace();
}
sources.addLast(new PropertiesPropertySource("prefix", properties));
ConfigurationPropertySource propertySource = new MapConfigurationPropertySource(properties);
return new Binder(propertySource).bind("prefix", ConfigClass.class).get();
}
The last three lines are the relevant part here.
dirty code but this is how i solved it
https://github.com/mamaorha/easy-wire/tree/master/src/main/java/co/il/nmh/easy/wire/core/utils/properties

Categories