Using multiple template resolvers with Spring 3.2 and Thymeleaf 2.1.3 for emails - spring

I have problem defining a ClassLoaderTemplateResolver for emails and one ServletContextTemplateResolver for web views. I getting the following error when trying to send emails:
HTTP Status 500 - Request processing failed; nested exception is
org.thymeleaf.exceptions.TemplateProcessingException: Resource resolution by ServletContext with
org.thymeleaf.resourceresolver.ServletContextResourceResolver can only be performed when context
implements org.thymeleaf.context.IWebContext [current context: org.thymeleaf.context.Context]
My WebMvcConfig looks like this:
private static final String VIEWS_PATH = "/WEB-INF/views/";
private static final String MAIL_PATH = "mail/";
#Bean
public ServletContextTemplateResolver templateResolver() {
final ServletContextTemplateResolver resolver = new ServletContextTemplateResolver();
resolver.setPrefix(VIEWS_PATH);
resolver.setSuffix(".html");
resolver.setTemplateMode("HTML5");
resolver.setCharacterEncoding("UTF-8");
resolver.setOrder(2);
resolver.setCacheable(false);
return resolver;
}
#Bean
public ClassLoaderTemplateResolver emailTemplateResolver() {
final ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
resolver.setPrefix(MAIL_PATH);
resolver.setSuffix(".html");
resolver.setTemplateMode("HTML5");
resolver.setCharacterEncoding("UTF-8");
resolver.setOrder(1);
return resolver;
}
#Bean
public SpringTemplateEngine templateEngine() {
final SpringTemplateEngine engine = new SpringTemplateEngine();
final Set<TemplateResolver> templateResolvers = new HashSet<TemplateResolver>();
templateResolvers.add(templateResolver());
templateResolvers.add(emailTemplateResolver());
engine.setTemplateResolvers(templateResolvers);
engine.addDialect(new SpringSocialDialect());
engine.addDialect(new SpringSecurityDialect());
return engine;
}
And my EmailService like this:
#Service
public class EmailService {
#Autowired
private JavaMailSender mailSender;
#Autowired
private TemplateEngine templateEngine;
/*
* Send HTML mail with inline image
*/
public void sendEmailToBookSeller(
final ContactBookSellerForm form,
final Locale locale) throws MessagingException {
boolean multipart = true;
boolean isHtml = true;
// Prepare the evaluation context
final Context ctx = new Context(locale);
ctx.setVariable("message", form.getMessage());
ctx.setVariable("bookTitle", form.getBookTitle());
ctx.setVariable("email", form.getToEmail());
ctx.setVariable("logo", "logo");
ctx.setVariable("logoOnlyText", "logoOnlyText");
// Prepare message
final MimeMessage mimeMessage = mailSender.createMimeMessage();
final MimeMessageHelper message = new MimeMessageHelper(mimeMessage, multipart, "UTF-8");
message.setSubject("Regarding your book on Mimswell - " + form.getBookTitle());
message.setFrom(form.getFromEmail());
message.setTo(form.getToEmail());
// Create the HTML body using Thymeleaf
final String htmlContent = templateEngine.process("email-buy-book.html", ctx);
message.setText(htmlContent, isHtml);
message.addInline("logo", new ClassPathResource("WEB-INF/views/mail/logo130130red.png"), "image/png");
message.addInline("logoOnlyText", new ClassPathResource("WEB-INF/views/mail/logo_only_text.png"), "image/png");
// Send mail
this.mailSender.send(mimeMessage);
}
}
The error occours on the following line:
final String htmlContent = templateEngine.process("email-buy-book.html", ctx);
Where it is using ServletContextResourceResolver instead of my other resolver. I want it to use ClassLoaderTemplateResolver since it can handle plain Context objects instead of having to use WebContext. However, I could try to use a WebContext instead since it implements the IWebContext and only use one resolver. But then I need a HttpServletRequest, HttpServletResponse and a ServletContext as parameters which seems to messy.
My structure :
Any idea whats wrong in my code?

I gave up this and went for the WebContext approach instead, even though i'm stuck needing the request, response and servletcontext every time sending something. This is how I did it:
1. Get the servlet context:
#Autowired
ServletContext servletContext;
2. Get the request and response as parameters to the sendmail method:
HttpServletRequest request,
HttpServletResponse response
3. Create the WebContext instead:
final WebContext ctx = new WebContext(request, response, servletContext, locale);
It worked from now on.

Since you (correctly) set the ClassLoaderTemplateResolver to have priority over the ServletContextTemplateResolver, Thymeleaf tries to use the correct order but fails to resolve the view with former and then tries latter.
I believe that the problem is with the prefix and suffix parameters you set combined with the view name you pass to templateEngine.process method. Thymeleaf will construct your view name by concatenating suffix + viewname + suffix resulting to "mail/email-buy-book.html.html".
Try to pass only "email-buy-book" and see if it solves the problem.

Since you're using the ClassLoaderTemplateResolver, Spring is going to use the prefix and append it to WEB-INF/classes. So the thing to check is whether Maven (or whatever build tool you're using) copied the html file to WEB-INF/classes/mail/email-buy-book.html. If it didn't, try copying it manually and give it a go. Looking at your screenshot, I don't see the "mail" folder under "classes" so this could be the issue.
Also, only pass "email-buy-book" and leave out the extension as #grid mentioned.
final String htmlContent = templateEngine.process("email-buy-book", ctx);
I have it working with XML config and not Java config, but I don't see why that should matter for you.

Related

Testing file upload in Spring Boot leads to FileUploadException (multipart boundary was not set)

I'm trying to upload files to my Spring Boot application and directly writing them to their destination (not in a temp file first). The application code I have works, but I can't get my unit test to work. My controller looks like this:
#PostMapping("/upload")
#ResponseBody
public String handleFileUpload(final HttpServletRequest request) throws IOException {
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (!isMultipart) {
throw new ResponseStatusException(BAD_REQUEST, "Input was not of type multipart");
}
ServletFileUpload upload = new ServletFileUpload();
FileItemIterator fileIterator = upload.getItemIterator(request);
while (fileIterator.hasNext()) {
FileItemStream item = fileIterator.next();
if (!item.isFormField()) {
// Save the file
try {
return myFileStorageService.store(item.openStream());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
throw new ResponseStatusException(BAD_REQUEST, "Input did not contain a file");
}
This code works great, but my test doesn't:
#MockBean
private MyFileStorageService myFileStorageService;
#Autowired
private MockMvc mockMvc;
#Test
void shouldUploadFile() throws Exception {
final InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("testfile.txt");
final MockMultipartFile testFile = new MockMultipartFile("file", "testfile.txt", null, inputStream);
doReturn("success!").when(myFileStorageService).store(testFile);
mockMvc.perform(multipart("/upload").file(testFile))
.andExpect(status().isOk())
.andExpect(content().string("success!"));
verify(myFileStorageService).store(testFile);
}
This results in the following exception:
org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.init(FileItemIteratorImpl.java:189)
at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.getMultiPartStream(FileItemIteratorImpl.java:205)
at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.findNextItem(FileItemIteratorImpl.java:224)
at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.<init>(FileItemIteratorImpl.java:142)
at org.apache.tomcat.util.http.fileupload.FileUploadBase.getItemIterator(FileUploadBase.java:252)
at org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload.getItemIterator(ServletFileUpload.java:134)
at com.lolmewn.FileUploadController.handleFileUpload(FileUploadController.java:128)
...
And in my config, I have configured the following:
spring:
servlet:
multipart:
enabled: false
max-file-size: -1
max-request-size: -1
I expect Spring would generate the multipart boundaries for me, just like the browser or Postman do, is this not the case? I saw many similar questions, with most of them explicitly setting their content-type as the primary error, but as far as I know I'm not setting a content-type anywhere, so I expect Spring to generate it for me.
If you are using default application.properties, then add #SpringBootTest annotation at top of your class which will instantiate it. If using something like application-test.properties you need to include #ActiveProfiles(test)
as well.
If you are using a config class to represent it
#EnableConfigurationProperties(value = YourConfig.class)
EDIT: Change
final MockMultipartFile testFile = new MockMultipartFile("file", "testfile.txt", null, inputStream);
To
final MockMultipartFile testFile = new MockMultipartFile("file", "testfile.txt",
MediaType.MULTIPART_FORM_DATA_VALUE, inputStream);

Streaming upload via #Bean-provided RestTemplateBuilder buffers full file

I'm building a reverse-proxy for uploading large files (multiple gigabytes), and therefore want to use a streaming model that does not buffer entire files. Large buffers would introduce latency and, more importantly, they could result in out-of-memory errors.
My client class contains
#Autowired private RestTemplate restTemplate;
#Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
int REST_TEMPLATE_MODE = 1; // 1=streams, 2=streams, 3=buffers
return
REST_TEMPLATE_MODE == 1 ? new RestTemplate() :
REST_TEMPLATE_MODE == 2 ? (new RestTemplateBuilder()).build() :
REST_TEMPLATE_MODE == 3 ? restTemplateBuilder.build() : null;
}
and
public void upload_via_streaming(InputStream inputStream, String originalname) {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);
InputStreamResource inputStreamResource = new InputStreamResource(inputStream) {
#Override public String getFilename() { return originalname; }
#Override public long contentLength() { return -1; }
};
MultiValueMap<String, Object> body = new LinkedMultiValueMap<String, Object>();
body.add("myfile", inputStreamResource);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body,headers);
String response = restTemplate.postForObject(UPLOAD_URL, requestEntity, String.class);
System.out.println("response: "+response);
}
This is working, but notice my REST_TEMPLATE_MODE value controls whether or not it meets my streaming requirement.
Question: Why does REST_TEMPLATE_MODE == 3 result in full-file buffering?
References:
How to forward large files with RestTemplate?
How to send Multipart form data with restTemplate Spring-mvc
Spring - How to stream large multipart file uploads to database without storing on local file system -- establishing the InputStream
How to autowire RestTemplate using annotations
Design notes and usage caveats, also: restTemplate does not support streaming downloads
In short, the instance of RestTemplateBuilder provided as an #Bean by Spring Boot includes an interceptor (filter) associated with actuator/metrics -- and the interceptor interface requires buffering of the request body into a simple byte[].
If you instantiate your own RestTemplateBuilder or RestTemplate from scratch, it won't include this by default.
I seem to be the only person visiting this post, but just in case it helps someone before I get around to posting a complete solution, I've found a big clue:
restTemplate.getInterceptors().forEach(item->System.out.println(item));
displays...
org.SF.boot.actuate.metrics.web.client.MetricsClientHttpRequestInterceptor
If I clear the interceptor list via setInterceptors, it solves the problem. Furthermore, I found that any interceptor, even if it only performs a NOP, will introduce full-file buffering.
public class SimpleClientHttpRequestFactory { ...
I have explicitly set bufferRequestBody = false, but apparently this code is bypassed if interceptors are used. This would have been nice to know earlier...
#Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
prepareConnection(connection, httpMethod.name());
if (this.bufferRequestBody) {
return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
}
else {
return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
}
}
public abstract class InterceptingHttpAccessor extends HttpAccessor { ...
This shows that the InterceptingClientHttpRequestFactory is used if the list of interceptors is not empty.
/**
* Overridden to expose an {#link InterceptingClientHttpRequestFactory}
* if necessary.
* #see #getInterceptors()
*/
#Override
public ClientHttpRequestFactory getRequestFactory() {
List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
if (!CollectionUtils.isEmpty(interceptors)) {
ClientHttpRequestFactory factory = this.interceptingRequestFactory;
if (factory == null) {
factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
this.interceptingRequestFactory = factory;
}
return factory;
}
else {
return super.getRequestFactory();
}
}
class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest { ...
The interfaces make it clear that using InterceptingClientHttpRequest requires buffering body to a byte[]. There is not an option to use a streaming interface.
#Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {

Custom json response for internal exception in spring

While implementing a global exception handler in Spring, I noticed that in case of a not recognized Accept header, Spring would throw it's own internal error. What I need is to return a custom JSON error structure instead. Works fine for application specific exceptions and totally fails for Spring HttpMediaTypeNotAcceptableException.
This code tells me "Failed to invoke #ExceptionHandler method: public java.util.Map RestExceptionHandler.springMalformedAcceptHeaderException()" when I try to request a page with incorrect Accept header. Any other way to return custom JSON for spring internal exceptions?
#ControllerAdvice
public class RestExceptionHandler {
#ExceptionHandler(value = HttpMediaTypeNotAcceptableException.class)
#ResponseBody
public Map<String, String> springMalformedAcceptHeaderException() {
Map<String, String> test = new HashMap<String, String>();
test.put("test", "test");
return test;
}
}
Eventually figured that the only way is to do the json mapping manually.
#ExceptionHandler(value = HttpMediaTypeNotAcceptableException.class)
#ResponseBody
public String springMalformedAcceptHeaderException(HttpServletResponse response) {
// populate errorObj, set response headers, etc
ObjectWriter jsonWriter = new ObjectMapper().writer();
try {
return jsonWriter.writeValueAsString(errorObj);
} catch(Exception e){}
return "Whatever";
}

#InitBinder with #RequestBody escaping XSS in Spring 3.2.4

I am having a #RequestBody annotated argument in my method like this:
#RequestMapping(value = "/courses/{courseId}/{name}/comment", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.OK)
public #ResponseBody CommentContainer addComment(#PathVariable Long courseId,
#ActiveAccount Account currentUser,
#Valid #RequestBody AddCommentForm form,
BindingResult formBinding,
HttpServletRequest request) throws RequestValidationException {
.....
}
Then I have a #InitBinder annotated method in the same controller:
#InitBinder
public void initBinder(WebDataBinder dataBinder) {
dataBinder.registerCustomEditor(AddCommentForm.class, new StringEscapeEditor());
}
My StringEscapeEditor is not running. But my initBinder method is. So it does not mapping my form to the escape editor. This seems right after reading this thread (Where it seems like #RequestMapping is not supported by #InitBinder):
spring mvc #InitBinder is not called when processing ajax request
And i tested to map a #PathVariable string and then my editor is working.
This is a big deal in my application since most of my bindings is done with #RequestBody and it would be great if i could apply some custom bindings to it.
What is the most common way to solve this problem? and to escape my input data for script attacks.
To escape XSS I suggest that escaping is done while outputting the data, because correct escaping depends on the output document.
If JSON response generated by #ResponseBody is consumed directly by the client and there is no opportunity to XSS escape the content, then JacksonMessageConverter can be customised to perform XSS escaping on strings.
One can customise JacksonMessageConverter like this:
1) First we create ObjectMapper factory that will create our custom object mapper:
public class HtmlEscapingObjectMapperFactory implements FactoryBean<ObjectMapper> {
private final ObjectMapper objectMapper;
public HtmlEscapingObjectMapperFactory() {
objectMapper = new ObjectMapper();
objectMapper.getJsonFactory().setCharacterEscapes(new HTMLCharacterEscapes());
}
#Override
public ObjectMapper getObject() throws Exception {
return objectMapper;
}
#Override
public Class<?> getObjectType() {
return ObjectMapper.class;
}
#Override
public boolean isSingleton() {
return true;
}
public static class HTMLCharacterEscapes extends CharacterEscapes {
private final int[] asciiEscapes;
public HTMLCharacterEscapes() {
// start with set of characters known to require escaping (double-quote, backslash etc)
asciiEscapes = CharacterEscapes.standardAsciiEscapesForJSON();
// and force escaping of a few others:
asciiEscapes['<'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['>'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['&'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['"'] = CharacterEscapes.ESCAPE_CUSTOM;
asciiEscapes['\''] = CharacterEscapes.ESCAPE_CUSTOM;
}
#Override
public int[] getEscapeCodesForAscii() {
return asciiEscapes;
}
// and this for others; we don't need anything special here
#Override
public SerializableString getEscapeSequence(int ch) {
return new SerializedString(StringEscapeUtils.escapeHtml4(Character.toString((char) ch)));
}
}
}
(inspiration for HtmlCharacterEscapes came from this question: HTML escape with Spring MVC and Jackson Mapper)
2) Then we register the message converter that uses our custom object mapper (example in xml config):
<bean id="htmlEscapingObjectMapper" class="com.example.HtmlEscapingObjectMapperFactory" />
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" p:objectMapper-ref="htmlEscapingObjectMapper" />
</mvc:message-converters>
</mvc:annotation-driven>
Now all the JSON messages created by #ResponseBody should have strings escaped as specified in HTMLCharacterEscapes.
Alternative solutions to the problem:
XSS escape what you need in the controller body after the objects have been deserialised
maybe XSS escape in javascript on the client before outputting the content
In addition to doing output escaping, it may be useful to also do some input validation (using standard Spring validation methods) to block some of the content that you don't want to be entered into the system / database.
EDIT: JavaConfig
I haven't tried this out but in Java config it should work like this (you won't need Factory Bean from above because you can set up everything in config in this case):
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
converters.add(buildHtmlEscapingJsonConverter());
}
private MappingJacksonHttpMessageConverter buildHtmlEscapingJsonConverter() {
MappingJacksonHttpMessageConverter htmlEscapingConverter = new MappingJacksonHttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.getJsonFactory().setCharacterEscapes(new HTMLCharacterEscapes());
htmlEscapingConverter.setObjectMapper(objectMapper);
return htmlEscapingConverter;
}
Please be aware that any other non-json default message converters that would normally be configured will now be lost (e.g. XML converters etc..) and if you need them, you will need to add them manually (you can see what's active by default here in section 2.2: http://www.baeldung.com/spring-httpmessageconverter-rest)

How to get HttpServletRequest object in Spring Schedular (#Schedule)?

I am trying to send a mail using spring scheduler. In that I need HttpServletRequest object to create webContext, so that I can send mail using thyme leaf.
Anyone know the answer of this. Thanks in advance. Code is as follows,
#Async
private void sendNotification(String toField, Users user, int currentMonth)
throws Exception {
// Prepare the evaluation context
#SuppressWarnings("deprecation")
**//here i need request object**
final WebContext ctx = new WebContext(request, request.getSession()
.getServletContext(), request.getLocale());
ctx.setVariable("eagletId", user.getEagletId());
ctx.setVariable("name", user.getFirstName());
ctx.setVariable("setSentDate", new Date());
ctx.setVariable("department", user.getDepartment());
ctx.setVariable("batch", user.getBatch());
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setTo(user.getEmail());
// create html body using thymeleaf
final String htmlContent = this.templateEngine.process("email.html",
ctx);
helper.setText(htmlContent, true);
mailSender.send(message);
}

Resources