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.
Related
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...
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?
I using zuul with many swagger resources (I have different links to specifics api-docs) so my question is how can I configure ng-swagger-gen config to generate all classes from many resources?
This is my ng-swagger-gen config:
{
"$schema": "./node_modules/ng-swagger-gen/ng-swagger-gen-schema.json",
"swagger": "http://localhost:8080/v2/api-docs",
"output": "src/api/",
"apiModule": true
}
And this is my swagger confing in zuul application
#Primary
#Configuration
public class SwaggerConfig implements SwaggerResourcesProvider {
#Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
resources.add(swaggerResource("USER-SERVICE", "/api/user/v2/api-docs"));
resources.add(swaggerResource("STORY-SERVICE", "/api/story/v2/api-docs"));
return resources;
}
private SwaggerResource swaggerResource(String name, String location) {
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setName(name);
swaggerResource.setLocation(location);
swaggerResource.setSwaggerVersion("2.0");
return swaggerResource;
}
}
While calling getTemplate() method it's throwing this error -
"Error resolving template [betreff_product_request], template might not exist or might not be accessible by any of the configured Template Resolvers".
Is it because of the wrong path I've mentioned in templateResolver.setPrefix("D:\\templates\\");?.
How can I solve this?
public class MailerTemplateEngine {
private final TemplateEngine templateEngine;
public MailerTemplateEngine() {
this.templateEngine = new org.thymeleaf.TemplateEngine();
FileTemplateResolver templateResolver = new FileTemplateResolver ();
templateResolver.setPrefix("D:\\templates\\");
templateResolver.setSuffix(".txt");
templateResolver.setTemplateMode(TemplateMode.TEXT);
templateResolver.setOrder(templateEngine.getTemplateResolvers().size());
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setCacheable(false);
templateResolver.setCheckExistence(true);
this.templateEngine.setTemplateResolver(templateResolver);
}
public String getTemplate(String templateName, HashMap<String,String> parameters) {
Context ctx = new Context();
if (parameters != null) {
parameters.forEach((k, v) -> {
ctx.setVariable(k, v);
});
}
return this.templateEngine.process(templateName, ctx).trim();
}
}
I am doing an application in springs with maven. i wrote all properties in app.properties file
file structure is like this
src/main/resource
|_
| templates
| |_mytempaltefile.vm
|_ app.properties
i gave the path(absloute) in app.property
app.properties file
template.base.path=D\:/SVN/trunk/tfbdirect/src/main/resources/templates
utilities-spring.xml
<bean id="velocityEngine"
class="org.springframework.ui.velocity.VelocityEngineFactoryBean">
<property name="velocityProperties">
<props>
<prop key="resource.loader">file</prop>
<prop key="file.resource.loader.class">
org.apache.velocity.runtime.resource.loader.FileResourceLoader
</prop>
<prop key="file.resource.loader.path">${template.base.path}</prop>
<prop key="file.resource.loader.cache">false</prop>
</props>
</property>
</bean>
my class
import java.util.HashMap;
import java.util.Map;
import org.apache.velocity.app.VelocityEngine;
import org.springframework.ui.velocity.VelocityEngineUtils;
import com.providerpay.tfbdirect.service.mail.MailSenderService;
public class LoginServiceImpl implements ILoginService{
/**
* Injected through Spring IOC
*/
ILoginDAO loginDAO;
ClaimRuleProcessServiceImpl claimRuleProcessServiceImpl;
PlatformTransactionManager txmanager;
//IForgotPasswordDAO forgotPasswordDAO;
private VelocityEngine velocityEngine;
private String appURL;
private MailSenderService mailSenderService;
TFBLogger log = TFBLoggerFactory.getLogger(RuleServer.class);
public String getAppURL() {
return appURL;
}
public void setAppURL(String appURL) {
this.appURL = appURL;
}
public MailSenderService getMailSenderService() {
return mailSenderService;
}
public VelocityEngine getVelocityEngine() {
return velocityEngine;
}
public void setVelocityEngine(VelocityEngine velocityEngine) {
this.velocityEngine = velocityEngine;
}
public void setMailSenderService(MailSenderService mailSenderService) {
this.mailSenderService = mailSenderService;
}
public ILoginDAO getLoginDAO() {
return loginDAO;
}
public void setLoginDAO(ILoginDAO loginDAO) {
this.loginDAO = loginDAO;
}
public ClaimRuleProcessServiceImpl getClaimRuleProcessServiceImpl() {
return claimRuleProcessServiceImpl;
}
public void setClaimRuleProcessServiceImpl(
ClaimRuleProcessServiceImpl claimRuleProcessServiceImpl) {
this.claimRuleProcessServiceImpl = claimRuleProcessServiceImpl;
}
public void setTxmanager(PlatformTransactionManager txmanager) {
this.txmanager = txmanager;
}
/**
* Validates Login
* #param loginView
* #return
*/
public boolean isValidLogin(LoginView loginView) {
/* create tx definition object */
DefaultTransactionDefinition paramTransactionDefinition = new DefaultTransactionDefinition();
TransactionStatus status = txmanager.getTransaction(paramTransactionDefinition );
boolean result = false;
try{
LoginEntity loginEntity = BeanMapper.INSTANCE.viewToEntityMapper(loginView);
Feedback feedback = claimRuleProcessServiceImpl.validateClaimEligibility(loginEntity);
log.info( "Rule executed was " +feedback.getAll());
for (FeedbackMessage feedbackmessaage :feedback.getAll())
{
log.info("\n--------------");
log.info(feedbackmessaage.getRuleCd());
log.info(feedbackmessaage.getMessage());
log.info(feedbackmessaage.getSeverity().getName());
log.info("\n--------------");
}
result = loginDAO.isValidLogin(loginEntity);
log.debug("result = {}", result);
txmanager.commit(status);
}catch(Exception e){
txmanager.rollback(status);
throw new TfbException("Error occured while validating login credentials");
}
return result;
}
#Autowired
VelocityEngine velocityengine;
public boolean mailResetLink(LoginView loginView) {
String toEmailAddress;
LoginEntity loginEntity = BeanMapper.INSTANCE.viewToEntityMapper(loginView);
/* getting user Email from DAO*/
toEmailAddress = loginDAO.getEmailByUsername(loginEntity);
if(toEmailAddress != null && toEmailAddress.trim().length() > 0)
{
Map<String, Object> model = new HashMap<String, Object>();
model.put("user", loginEntity);
model.put("appURL", appURL);
String body = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, "emailTemplate.vm","UTF-8", model);
mailSenderService.sendMail("from mail", toEmailAddress, "Password Reset Link",body);
}
else
{
return false;
}
return true;
}
public boolean resetPassword(LoginView loginView)
{
LoginEntity loginEntity = BeanMapper.INSTANCE.viewToEntityMapper(loginView);
return loginDAO.resetPassword(loginEntity);
}
}
every thing fine but i need to change the absolute path to relative path.. i tried many ways.
i tried like following
template.base.path=/templates/
but still getting below error .
ResourceManager : unable to find resource 'emailTemplate.vm' in any resource loader.
can any one help me..
Thanks in advance
You are falling into a common pitfall when using velocity with spring : you place your templates in one location and use a resource loader that searches them in another place. So you have 2 common usages :
put templates in classpath (as you do) and use ClasspathResourceLoader
resource.loader = class
class.resource.loader.class = org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
it is simple with little dependencies, but it forces you to put templates in classpath ...
put templates under WEB-INF (as you would do for JSPs) and use WebappResourceLoader from velocity tools
resource.loader=webapp
webapp.resource.loader.class=org.apache.velocity.tools.view.WebappResourceLoader
webapp.resource.loader.path=/WEB-INF/velocity/
it is more natural for a template location, but you add a dependency on velocity tools.
And let spring manage dependencies but not instanciating via new ...
I had the same problem recently with karaf OSGi framework.
In a CXF resource class (Login in this context) you have to initialize the Velocity Engine like this:
public Login() {
/* first, get and initialize an engine */
ve = new VelocityEngine();
ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
ve.init();
}
Then, in the web method, instantiate the template:
/* create a context and add data */
synchronized (initLock) {
if (loginTemplate == null) {
loginTemplate = ve.getTemplate("templates/login.vm");
}
}
VelocityContext context = new VelocityContext();
Unfortunately, it didn't work out to load the template immediately after the ve.init() call in the constructor.
Your configuration seems correct. If you instantiate the VelocityEngine instance using "new", try following:
#Autowired
VelocityEngine velocityEngine;