package pl.matisoft.soy;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.Integer.MAX_VALUE;
import static org.slf4j.LoggerFactory.getLogger;
import static org.springframework.util.CollectionUtils.isEmpty;
import static org.springframework.util.StringUtils.isEmpty;
import static pl.matisoft.soy.config.SoyViewConfigDefaults.DEFAULT_ENCODING;
import static pl.matisoft.soy.config.SoyViewConfigDefaults.DEFAULT_SOY_PREFIX;
import java.util.List;
import java.util.Locale;
import org.slf4j.Logger;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.AbstractCachingViewResolver;
import org.springframework.web.servlet.view.AbstractView;
import org.springframework.web.servlet.view.InternalResourceView;
import org.springframework.web.servlet.view.RedirectView;
import pl.matisoft.soy.bundle.EmptySoyMsgBundleResolver;
import pl.matisoft.soy.bundle.SoyMsgBundleResolver;
import pl.matisoft.soy.data.adjust.EmptyModelAdjuster;
import pl.matisoft.soy.data.adjust.ModelAdjuster;
import pl.matisoft.soy.global.runtime.EmptyGlobalRuntimeModelResolver;
import pl.matisoft.soy.global.runtime.GlobalRuntimeModelResolver;
import pl.matisoft.soy.holder.CompiledTemplatesHolder;
import pl.matisoft.soy.holder.EmptyCompiledTemplatesHolder;
import pl.matisoft.soy.locale.EmptyLocaleProvider;
import pl.matisoft.soy.locale.LocaleProvider;
import pl.matisoft.soy.render.EmptyTemplateRenderer;
import pl.matisoft.soy.render.TemplateRenderer;
/**
* Created with IntelliJ IDEA. User: mati Date: 20/06/2013 Time: 19:51
*
* An Soy implementation of Spring's TemplateViewResolver.
*
* Warning: please read carefully JavaDocs from AbstractTemplateViewResolver and it the classes from which it inherits
* as it may be necessary to set some configuration options for this resolver to work properly for your use case.
*/
public class SoyTemplateViewResolver extends AbstractCachingViewResolver implements Ordered {
/**
* Prefix for special view names that specify a redirect URL (usually to a controller after a form has been
* submitted and processed). Such view names will not be resolved in the configured default way but rather be
* treated as special shortcut.
*/
public static final String REDIRECT_URL_PREFIX = "redirect:";
/**
* Prefix for special view names that specify a forward URL (usually to a controller after a form has been submitted
* and processed). Such view names will not be resolved in the configured default way but rather be treated as
* special shortcut.
*/
public static final String FORWARD_URL_PREFIX = "forward:";
private static final Logger logger = getLogger(SoyTemplateViewResolver.class);
protected TemplateRenderer templateRenderer = new EmptyTemplateRenderer();
protected ModelAdjuster modelAdjuster = new EmptyModelAdjuster();
protected GlobalRuntimeModelResolver globalRuntimeModelResolver = new EmptyGlobalRuntimeModelResolver();
protected LocaleProvider localeProvider = new EmptyLocaleProvider();
protected SoyMsgBundleResolver soyMsgBundleResolver = new EmptySoyMsgBundleResolver();
private CompiledTemplatesHolder compiledTemplatesHolder = new EmptyCompiledTemplatesHolder();
private ContentNegotiator contentNegotiator = new DefaultContentNegotiator();
/** an encoding to use */
private String encoding = DEFAULT_ENCODING;
/** a default view */
private String indexView = "index";
private boolean redirectContextRelative = true;
private boolean redirectHttp10Compatible = true;
private int order = MAX_VALUE;
private String prefix = DEFAULT_SOY_PREFIX;
protected View loadView(final String viewName, final Locale locale) throws Exception {
checkNotNull(viewName, "viewName cannot be null!");
checkNotNull(templateRenderer, "templateRenderer cannot be null!");
logger.debug("loadView:{}, locale:{}", viewName, locale);
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
final String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
final RedirectView view = new RedirectView(redirectUrl, redirectContextRelative, redirectHttp10Compatible);
return applyLifecycleMethods(viewName, view);
}
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
final String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}
final String newViewName = orIndexView(normalize(viewName));
final List<String> contentTypes = contentNegotiator.contentTypes();
if (!canHandle(newViewName) || !contentNegotiator.isSupportedContentTypes(contentTypes)) {
logger.debug("Unable to handle view: {}.", newViewName);
return null;
}
final SoyView view = new SoyView();
view.setTemplateName(stripPrefix(newViewName));
view.setContentType(resolveContentType(contentTypes));
view.setTemplateRenderer(templateRenderer);
view.setModelAdjuster(modelAdjuster);
view.setGlobalRuntimeModelResolver(globalRuntimeModelResolver);
view.setLocaleProvider(localeProvider);
view.setSoyMsgBundleResolver(soyMsgBundleResolver);
view.setCompiledTemplates(compiledTemplatesHolder.compiledTemplates());
return view;
}
private boolean canHandle(final String templateName) {
return templateName.startsWith(prefix);
}
private String stripPrefix(final String templateName) {
if (canHandle(templateName)) {
return templateName.replace(prefix, "");
}
return templateName;
}
/**
* Map / to a default view name.
*
* @param viewName
* The view name.
* @return The defaulted view name.
*/
private String orIndexView(final String viewName) {
return isEmpty(viewName) ? indexView : viewName;
}
/**
* Remove beginning and ending slashes, then replace all occurrences of / with .
*
* @param viewName
* The Spring viewName
* @return The name of the view, dot separated.
*/
private String normalize(final String viewName) {
String normalized = viewName;
if (normalized.startsWith("/")) {
normalized = normalized.substring(0);
}
if (normalized.endsWith("/")) {
normalized = normalized.substring(0, normalized.length() - 1);
}
return normalized.replaceAll("/", ".");
}
private View applyLifecycleMethods(String viewName, AbstractView view) {
return (View) getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);
}
protected String resolveContentType(final List<String> contentTypes) {
checkArgument(!isEmpty(contentTypes), "Content types must not be null or empty.");
// TODO: This should be better handled.
if (contentTypes.size() > 1) {
logger.warn("More than one content type is not currently supported; "
+ "only first one ({}) will be used.", contentTypes.get(0));
}
return contentTypes.get(0) + "; charset=" + encoding;
}
public void setTemplateRenderer(final TemplateRenderer templateRenderer) {
this.templateRenderer = templateRenderer;
}
public void setModelAdjuster(final ModelAdjuster modelAdjuster) {
this.modelAdjuster = modelAdjuster;
}
public void setGlobalRuntimeModelResolver(final GlobalRuntimeModelResolver globalRuntimeModelResolver) {
this.globalRuntimeModelResolver = globalRuntimeModelResolver;
}
public void setLocaleProvider(final LocaleProvider localeProvider) {
this.localeProvider = localeProvider;
}
public void setSoyMsgBundleResolver(final SoyMsgBundleResolver soyMsgBundleResolver) {
this.soyMsgBundleResolver = soyMsgBundleResolver;
}
public void setIndexView(String indexView) {
this.indexView = indexView;
}
public void setRedirectContextRelative(boolean redirectContextRelative) {
this.redirectContextRelative = redirectContextRelative;
}
public void setRedirectHttp10Compatible(boolean redirectHttp10Compatible) {
this.redirectHttp10Compatible = redirectHttp10Compatible;
}
public boolean isRedirectContextRelative() {
return redirectContextRelative;
}
public boolean isRedirectHttp10Compatible() {
return redirectHttp10Compatible;
}
public void setCompiledTemplatesHolder(CompiledTemplatesHolder compiledTemplatesHolder) {
this.compiledTemplatesHolder = compiledTemplatesHolder;
}
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getEncoding() {
return encoding;
}
public void setEncoding(String encoding) {
this.encoding = encoding;
}
public String getIndexView() {
return indexView;
}
public void setHotReloadMode(final boolean hotReloadMode) {
setCache(!hotReloadMode);
}
public void setContentNegotiator(ContentNegotiator contentNegotiator) {
this.contentNegotiator = contentNegotiator;
}
}