/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.riotfamily.common.freemarker;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.riotfamily.common.util.SpringUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.ClassUtils;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import freemarker.cache.TemplateLoader;
import freemarker.ext.beans.BeansWrapper;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.ObjectWrapper;
import freemarker.template.SimpleHash;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import freemarker.template.TemplateHashModel;
/**
* FreeMarkerConfigurer that supports some additional settings.
* @author Felix Gnass [fgnass at neteye dot de]
* @since 6.4
*/
public class RiotFreeMarkerConfigurer extends FreeMarkerConfigurer
implements ApplicationContextAware {
private TemplateExceptionHandler exceptionHandler =
new ErrorPrintingExceptionHandler();
private Map<String, String> macroLibraries;
private Map<String, ?> sharedVariables;
private Collection<Class<?>> utilityClasses;
private boolean whitespaceStripping = false;
private boolean useTemplateCache = true;
private boolean useComputerNumberFormat = true;
private boolean exposeStaticsModel = true;
private boolean exposeBeanFactoryModel = true;
private int templateUpdateDelay = 0;
private String urlEscapingCharset = "UTF-8";
private ApplicationContext applicationContext;
private ObjectWrapper objectWrapper;
/**
* Sets the macro libraries to be auto-imported, keyed by their namespace.
*/
public void setMacroLibraries(Map<String, String> macroLibraries) {
this.macroLibraries = macroLibraries;
}
/**
* Sets the classes to be exposed as freemarker.ext.beans.StaticModel.
*/
public void setUtilityClasses(Collection<Class<?>> utilityClasses) {
this.utilityClasses = utilityClasses;
}
/**
* Sets the {@link TemplateExceptionHandler} to be used. By default an
* {@link ErrorPrintingExceptionHandler} will be used.
*/
public void setExceptionHandler(TemplateExceptionHandler exceptionHandler) {
this.exceptionHandler = exceptionHandler;
}
/**
* Set a Map that contains well-known FreeMarker objects which will be passed
* to FreeMarker's <code>Configuration.setAllSharedVariables()</code> method.
* <p>
* Riot overrides this setter in order to set the variables in
* {@link #postProcessConfiguration(Configuration)}, after the custom
* {@link ObjectWrapper} has been set.
* </p>
* @see freemarker.template.Configuration#setAllSharedVariables
*/
@Override
@SuppressWarnings("unchecked")
public void setFreemarkerVariables(Map variables) {
sharedVariables = variables;
}
/**
* Sets whether the FTL parser will try to remove superfluous
* white-space around certain FTL tags.
*/
public void setWhitespaceStripping(boolean whitespaceStripping) {
this.whitespaceStripping = whitespaceStripping;
}
/**
* Sets the URL escaping charset. Allows null, which means that the
* output encoding will be used for URL escaping.
* Default is <code>UTF-8</code>.
*/
public void setUrlEscapingCharset(String urlEscapingCharset) {
this.urlEscapingCharset = urlEscapingCharset;
}
/**
* Whether the <code>#0.#</code> should be used as default number format.
* Default is <code>true</code>.
*/
public void setUseComputerNumberFormat(boolean useComputerNumberFormat) {
this.useComputerNumberFormat = useComputerNumberFormat;
}
/**
* Whether {@link BeansWrapper#getStaticModels()} should be exposed as
* <tt>statics</tt>.
*/
public void setExposeStaticsModel(boolean exposeStaticsModel) {
this.exposeStaticsModel = exposeStaticsModel;
}
/**
* Whether a {@link BeanFactoryTemplateModel} should be exposed as
* <tt>beans</tt>.
*/
public void setExposeBeanFactoryModel(boolean exposeBeanFactoryModel) {
this.exposeBeanFactoryModel = exposeBeanFactoryModel;
}
/**
* Sets whether the FreeMarker template cache should be used
* (default is <code>true</code>).
*/
public void setUseTemplateCache(boolean useTemplateCache) {
this.useTemplateCache = useTemplateCache;
}
/**
* Set the time in seconds that must elapse before checking whether there
* is a newer version of a template file. Default is <code>0</code>.
*/
public void setTemplateUpdateDelay(int templateUpdateDelay) {
this.templateUpdateDelay = templateUpdateDelay;
}
public void setObjectWrapper(ObjectWrapper objectWrapper) {
this.objectWrapper = objectWrapper;
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
protected void postProcessTemplateLoaders(List<TemplateLoader> templateLoaders) {
super.postProcessTemplateLoaders(templateLoaders);
templateLoaders.add(new ResourceTemplateLoader(getResourceLoader()));
}
@Override
protected void postProcessConfiguration(Configuration config)
throws IOException, TemplateException {
config.setURLEscapingCharset(urlEscapingCharset);
config.setWhitespaceStripping(whitespaceStripping);
if (useComputerNumberFormat) {
config.setNumberFormat("#0.#");
}
if (macroLibraries != null) {
for(Map.Entry<String, String> entry : macroLibraries.entrySet()) {
config.addAutoImport(entry.getKey(), entry.getValue());
}
}
config.setTemplateExceptionHandler(exceptionHandler);
if (objectWrapper == null) {
objectWrapper = DefaultObjectWrapper.getDefaultInstance();
}
config.setObjectWrapper(objectWrapper);
if (sharedVariables != null) {
config.setAllSharedVariables(
new SimpleHash(sharedVariables, objectWrapper));
}
TemplateHashModel staticsModel = getStaticsModel(objectWrapper);
if (exposeStaticsModel) {
config.setSharedVariable("statics", staticsModel);
}
if (utilityClasses != null) {
for (Class<?> clazz : utilityClasses) {
config.setSharedVariable(
ClassUtils.getShortName(clazz),
staticsModel.get(clazz.getName()));
}
}
if (exposeBeanFactoryModel) {
config.setSharedVariable("beans", new BeanFactoryTemplateModel(
applicationContext, objectWrapper));
}
if (useTemplateCache) {
config.setTemplateUpdateDelay(templateUpdateDelay);
}
else {
config.setCacheStorage(new NoCacheStorage());
}
Collection<ConfigurationPostProcessor> postProcessors =
SpringUtils.orderedBeans(applicationContext,
ConfigurationPostProcessor.class);
for (ConfigurationPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessConfiguration(config);
}
}
private TemplateHashModel getStaticsModel(ObjectWrapper wrapper) {
if (wrapper instanceof BeansWrapper) {
return ((BeansWrapper) wrapper).getStaticModels();
}
return BeansWrapper.getDefaultInstance().getStaticModels();
}
}