SpringWebThymeleafContextConverter.java
package fr.sii.ogham.spring.template.thymeleaf;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.ApplicationContext;
import org.springframework.expression.EvaluationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.support.RequestContext;
import org.springframework.web.servlet.view.AbstractTemplateView;
import org.thymeleaf.context.IContext;
import fr.sii.ogham.core.exception.template.ContextException;
import fr.sii.ogham.template.thymeleaf.common.ThymeleafContextConverter;
/**
* Specific context converter for Spring that registers static variables and
* {@link EvaluationContext} for SpEL expressions.
*
* The aim is to provide the same support as if user was using Spring in web
* context (access to Spring beans from templates, be able to use static
* variables, ...).
*
* @author Aurélien Baudet
*
*/
public class SpringWebThymeleafContextConverter implements ThymeleafContextConverter {
private final ThymeleafContextConverter delegate;
private final String springRequestContextVariableName;
private final ApplicationContext applicationContext;
private final WebContextProvider webContextProvider;
private final ThymeleafRequestContextWrapper thymeleafRequestContextWrapper;
private final ThymeleafWebContextProvider thymeleafWebContextProvider;
private final ContextMerger contextMerger;
public SpringWebThymeleafContextConverter(ThymeleafContextConverter delegate, String springRequestContextVariableName, ApplicationContext applicationContext, WebContextProvider webContextProvider,
ThymeleafRequestContextWrapper thymeleafRequestContextWrapper, ThymeleafWebContextProvider thymeleafWebContextProvider, ContextMerger contextMerger) {
super();
this.delegate = delegate;
this.springRequestContextVariableName = springRequestContextVariableName;
this.applicationContext = applicationContext;
this.webContextProvider = webContextProvider;
this.thymeleafRequestContextWrapper = thymeleafRequestContextWrapper;
this.thymeleafWebContextProvider = thymeleafWebContextProvider;
this.contextMerger = contextMerger;
}
/*
* If this is not null, we are using Spring 3.1+ and there is the
* possibility to automatically add @PathVariable's to models. This will be
* computed at class initialization time.
*/
private static final String pathVariablesSelector;
static {
/*
* Compute whether we can obtain @PathVariable's from the request and
* add them automatically to the model (Spring 3.1+)
*/
String pathVariablesSelectorValue = null;
try {
// We are looking for the value of the View.PATH_VARIABLES constant,
// which is a String
final Field pathVariablesField = View.class.getDeclaredField("PATH_VARIABLES");
pathVariablesSelectorValue = (String) pathVariablesField.get(null);
} catch (final NoSuchFieldException | IllegalAccessException ignored) {
pathVariablesSelectorValue = null;
}
pathVariablesSelector = pathVariablesSelectorValue;
}
@Override
public IContext convert(fr.sii.ogham.core.template.context.Context context) throws ContextException {
IContext base = delegate.convert(context);
// the web context may be lost due to @Async method call
if (isAsyncCall()) {
return base;
}
// partially borrowed from org.thymeleaf.spring5.view.ThymeleafView
final Map<String, Object> springModel = new HashMap<>(30);
HttpServletRequest request = webContextProvider.getRequest(context);
HttpServletResponse response = webContextProvider.getResponse(context);
ServletContext servletContext = webContextProvider.getServletContext(context);
if (pathVariablesSelector != null) {
@SuppressWarnings("unchecked")
final Map<String, Object> pathVars = (Map<String, Object>) request.getAttribute(pathVariablesSelector);
if (pathVars != null) {
springModel.putAll(pathVars);
}
}
final RequestContext requestContext = new RequestContext(request, response, servletContext, springModel);
// For compatibility with ThymeleafView
addRequestContextAsVariable(springModel, springRequestContextVariableName, requestContext);
// For compatibility with AbstractTemplateView
addRequestContextAsVariable(springModel, AbstractTemplateView.SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE, requestContext);
thymeleafRequestContextWrapper.wrapAndRegister(requestContext, request, response, servletContext, springModel);
return contextMerger.merge(thymeleafWebContextProvider.getWebContext(context, base, request, response, servletContext, applicationContext, springModel), base);
}
private boolean isAsyncCall() {
try {
RequestContextHolder.currentRequestAttributes();
return false;
} catch(IllegalStateException e) {
return true;
}
}
protected static void addRequestContextAsVariable(final Map<String, Object> model, final String variableName, final RequestContext requestContext) throws ContextException {
if (model.containsKey(variableName)) {
throw new ContextException(new ServletException("Cannot expose request context in model attribute '" + variableName + "' because of an existing model object of the same name"));
}
model.put(variableName, requestContext);
}
}