/*
 * Copyright 2002-2018 the original author or authors.
 *
 * 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
 *
 *      https://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.springframework.ui.freemarker;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import freemarker.cache.FileTemplateLoader;
import freemarker.cache.MultiTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.SimpleHash;
import freemarker.template.TemplateException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;

Factory that configures a FreeMarker Configuration. Can be used standalone, but typically you will either use FreeMarkerConfigurationFactoryBean for preparing a Configuration as bean reference, or FreeMarkerConfigurer for web views.

The optional "configLocation" property sets the location of a FreeMarker properties file, within the current application. FreeMarker properties can be overridden via "freemarkerSettings". All of these properties will be set by calling FreeMarker's Configuration.setSettings() method and are subject to constraints set by FreeMarker.

The "freemarkerVariables" property can be used to specify a Map of shared variables that will be applied to the Configuration via the setAllSharedVariables() method. Like setSettings(), these entries are subject to FreeMarker constraints.

The simplest way to use this class is to specify a "templateLoaderPath"; FreeMarker does not need any further configuration then.

Note: Spring's FreeMarker support requires FreeMarker 2.3 or higher.

Author:Darren Davison, Juergen Hoeller
See Also:
Since:03.03.2004
/** * Factory that configures a FreeMarker Configuration. Can be used standalone, but * typically you will either use FreeMarkerConfigurationFactoryBean for preparing a * Configuration as bean reference, or FreeMarkerConfigurer for web views. * * <p>The optional "configLocation" property sets the location of a FreeMarker * properties file, within the current application. FreeMarker properties can be * overridden via "freemarkerSettings". All of these properties will be set by * calling FreeMarker's {@code Configuration.setSettings()} method and are * subject to constraints set by FreeMarker. * * <p>The "freemarkerVariables" property can be used to specify a Map of * shared variables that will be applied to the Configuration via the * {@code setAllSharedVariables()} method. Like {@code setSettings()}, * these entries are subject to FreeMarker constraints. * * <p>The simplest way to use this class is to specify a "templateLoaderPath"; * FreeMarker does not need any further configuration then. * * <p>Note: Spring's FreeMarker support requires FreeMarker 2.3 or higher. * * @author Darren Davison * @author Juergen Hoeller * @since 03.03.2004 * @see #setConfigLocation * @see #setFreemarkerSettings * @see #setFreemarkerVariables * @see #setTemplateLoaderPath * @see #createConfiguration * @see FreeMarkerConfigurationFactoryBean * @see org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer * @see freemarker.template.Configuration */
public class FreeMarkerConfigurationFactory { protected final Log logger = LogFactory.getLog(getClass()); @Nullable private Resource configLocation; @Nullable private Properties freemarkerSettings; @Nullable private Map<String, Object> freemarkerVariables; @Nullable private String defaultEncoding; private final List<TemplateLoader> templateLoaders = new ArrayList<>(); @Nullable private List<TemplateLoader> preTemplateLoaders; @Nullable private List<TemplateLoader> postTemplateLoaders; @Nullable private String[] templateLoaderPaths; private ResourceLoader resourceLoader = new DefaultResourceLoader(); private boolean preferFileSystemAccess = true;
Set the location of the FreeMarker config file. Alternatively, you can specify all setting locally.
See Also:
/** * Set the location of the FreeMarker config file. * Alternatively, you can specify all setting locally. * @see #setFreemarkerSettings * @see #setTemplateLoaderPath */
public void setConfigLocation(Resource resource) { this.configLocation = resource; }
Set properties that contain well-known FreeMarker keys which will be passed to FreeMarker's Configuration.setSettings method.
See Also:
  • setSettings.setSettings
/** * Set properties that contain well-known FreeMarker keys which will be * passed to FreeMarker's {@code Configuration.setSettings} method. * @see freemarker.template.Configuration#setSettings */
public void setFreemarkerSettings(Properties settings) { this.freemarkerSettings = settings; }
Set a Map that contains well-known FreeMarker objects which will be passed to FreeMarker's Configuration.setAllSharedVariables() method.
See Also:
  • setAllSharedVariables.setAllSharedVariables
/** * Set a Map that contains well-known FreeMarker objects which will be passed * to FreeMarker's {@code Configuration.setAllSharedVariables()} method. * @see freemarker.template.Configuration#setAllSharedVariables */
public void setFreemarkerVariables(Map<String, Object> variables) { this.freemarkerVariables = variables; }
Set the default encoding for the FreeMarker configuration. If not specified, FreeMarker will use the platform file encoding.

Used for template rendering unless there is an explicit encoding specified for the rendering process (for example, on Spring's FreeMarkerView).

See Also:
  • setDefaultEncoding.setDefaultEncoding
  • setEncoding.setEncoding
/** * Set the default encoding for the FreeMarker configuration. * If not specified, FreeMarker will use the platform file encoding. * <p>Used for template rendering unless there is an explicit encoding specified * for the rendering process (for example, on Spring's FreeMarkerView). * @see freemarker.template.Configuration#setDefaultEncoding * @see org.springframework.web.servlet.view.freemarker.FreeMarkerView#setEncoding */
public void setDefaultEncoding(String defaultEncoding) { this.defaultEncoding = defaultEncoding; }
Set a List of TemplateLoaders that will be used to search for templates. For example, one or more custom loaders such as database loaders could be configured and injected here.

The TemplateLoaders specified here will be registered before the default template loaders that this factory registers (such as loaders for specified "templateLoaderPaths" or any loaders registered in postProcessTemplateLoaders).

See Also:
/** * Set a List of {@code TemplateLoader}s that will be used to search * for templates. For example, one or more custom loaders such as database * loaders could be configured and injected here. * <p>The {@link TemplateLoader TemplateLoaders} specified here will be * registered <i>before</i> the default template loaders that this factory * registers (such as loaders for specified "templateLoaderPaths" or any * loaders registered in {@link #postProcessTemplateLoaders}). * @see #setTemplateLoaderPaths * @see #postProcessTemplateLoaders */
public void setPreTemplateLoaders(TemplateLoader... preTemplateLoaders) { this.preTemplateLoaders = Arrays.asList(preTemplateLoaders); }
Set a List of TemplateLoaders that will be used to search for templates. For example, one or more custom loaders such as database loaders can be configured.

The TemplateLoaders specified here will be registered after the default template loaders that this factory registers (such as loaders for specified "templateLoaderPaths" or any loaders registered in postProcessTemplateLoaders).

See Also:
/** * Set a List of {@code TemplateLoader}s that will be used to search * for templates. For example, one or more custom loaders such as database * loaders can be configured. * <p>The {@link TemplateLoader TemplateLoaders} specified here will be * registered <i>after</i> the default template loaders that this factory * registers (such as loaders for specified "templateLoaderPaths" or any * loaders registered in {@link #postProcessTemplateLoaders}). * @see #setTemplateLoaderPaths * @see #postProcessTemplateLoaders */
public void setPostTemplateLoaders(TemplateLoader... postTemplateLoaders) { this.postTemplateLoaders = Arrays.asList(postTemplateLoaders); }
Set the Freemarker template loader path via a Spring resource location. See the "templateLoaderPaths" property for details on path handling.
See Also:
  • setTemplateLoaderPaths
/** * Set the Freemarker template loader path via a Spring resource location. * See the "templateLoaderPaths" property for details on path handling. * @see #setTemplateLoaderPaths */
public void setTemplateLoaderPath(String templateLoaderPath) { this.templateLoaderPaths = new String[] {templateLoaderPath}; }
Set multiple Freemarker template loader paths via Spring resource locations.

When populated via a String, standard URLs like "file:" and "classpath:" pseudo URLs are supported, as understood by ResourceEditor. Allows for relative paths when running in an ApplicationContext.

Will define a path for the default FreeMarker template loader. If a specified resource cannot be resolved to a java.io.File, a generic SpringTemplateLoader will be used, without modification detection.

To enforce the use of SpringTemplateLoader, i.e. to not resolve a path as file system resource in any case, turn off the "preferFileSystemAccess" flag. See the latter's javadoc for details.

If you wish to specify your own list of TemplateLoaders, do not set this property and instead use setTemplateLoaders(List templateLoaders)

See Also:
/** * Set multiple Freemarker template loader paths via Spring resource locations. * <p>When populated via a String, standard URLs like "file:" and "classpath:" * pseudo URLs are supported, as understood by ResourceEditor. Allows for * relative paths when running in an ApplicationContext. * <p>Will define a path for the default FreeMarker template loader. * If a specified resource cannot be resolved to a {@code java.io.File}, * a generic SpringTemplateLoader will be used, without modification detection. * <p>To enforce the use of SpringTemplateLoader, i.e. to not resolve a path * as file system resource in any case, turn off the "preferFileSystemAccess" * flag. See the latter's javadoc for details. * <p>If you wish to specify your own list of TemplateLoaders, do not set this * property and instead use {@code setTemplateLoaders(List templateLoaders)} * @see org.springframework.core.io.ResourceEditor * @see org.springframework.context.ApplicationContext#getResource * @see freemarker.template.Configuration#setDirectoryForTemplateLoading * @see SpringTemplateLoader */
public void setTemplateLoaderPaths(String... templateLoaderPaths) { this.templateLoaderPaths = templateLoaderPaths; }
Set the Spring ResourceLoader to use for loading FreeMarker template files. The default is DefaultResourceLoader. Will get overridden by the ApplicationContext if running in a context.
See Also:
  • DefaultResourceLoader
/** * Set the Spring ResourceLoader to use for loading FreeMarker template files. * The default is DefaultResourceLoader. Will get overridden by the * ApplicationContext if running in a context. * @see org.springframework.core.io.DefaultResourceLoader */
public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; }
Return the Spring ResourceLoader to use for loading FreeMarker template files.
/** * Return the Spring ResourceLoader to use for loading FreeMarker template files. */
protected ResourceLoader getResourceLoader() { return this.resourceLoader; }
Set whether to prefer file system access for template loading. File system access enables hot detection of template changes.

If this is enabled, FreeMarkerConfigurationFactory will try to resolve the specified "templateLoaderPath" as file system resource (which will work for expanded class path resources and ServletContext resources too).

Default is "true". Turn this off to always load via SpringTemplateLoader (i.e. as stream, without hot detection of template changes), which might be necessary if some of your templates reside in an expanded classes directory while others reside in jar files.

See Also:
  • setTemplateLoaderPath
/** * Set whether to prefer file system access for template loading. * File system access enables hot detection of template changes. * <p>If this is enabled, FreeMarkerConfigurationFactory will try to resolve * the specified "templateLoaderPath" as file system resource (which will work * for expanded class path resources and ServletContext resources too). * <p>Default is "true". Turn this off to always load via SpringTemplateLoader * (i.e. as stream, without hot detection of template changes), which might * be necessary if some of your templates reside in an expanded classes * directory while others reside in jar files. * @see #setTemplateLoaderPath */
public void setPreferFileSystemAccess(boolean preferFileSystemAccess) { this.preferFileSystemAccess = preferFileSystemAccess; }
Return whether to prefer file system access for template loading.
/** * Return whether to prefer file system access for template loading. */
protected boolean isPreferFileSystemAccess() { return this.preferFileSystemAccess; }
Prepare the FreeMarker Configuration and return it.
Throws:
  • IOException – if the config file wasn't found
  • TemplateException – on FreeMarker initialization failure
Returns:the FreeMarker Configuration object
/** * Prepare the FreeMarker Configuration and return it. * @return the FreeMarker Configuration object * @throws IOException if the config file wasn't found * @throws TemplateException on FreeMarker initialization failure */
public Configuration createConfiguration() throws IOException, TemplateException { Configuration config = newConfiguration(); Properties props = new Properties(); // Load config file if specified. if (this.configLocation != null) { if (logger.isDebugEnabled()) { logger.debug("Loading FreeMarker configuration from " + this.configLocation); } PropertiesLoaderUtils.fillProperties(props, this.configLocation); } // Merge local properties if specified. if (this.freemarkerSettings != null) { props.putAll(this.freemarkerSettings); } // FreeMarker will only accept known keys in its setSettings and // setAllSharedVariables methods. if (!props.isEmpty()) { config.setSettings(props); } if (!CollectionUtils.isEmpty(this.freemarkerVariables)) { config.setAllSharedVariables(new SimpleHash(this.freemarkerVariables, config.getObjectWrapper())); } if (this.defaultEncoding != null) { config.setDefaultEncoding(this.defaultEncoding); } List<TemplateLoader> templateLoaders = new ArrayList<>(this.templateLoaders); // Register template loaders that are supposed to kick in early. if (this.preTemplateLoaders != null) { templateLoaders.addAll(this.preTemplateLoaders); } // Register default template loaders. if (this.templateLoaderPaths != null) { for (String path : this.templateLoaderPaths) { templateLoaders.add(getTemplateLoaderForPath(path)); } } postProcessTemplateLoaders(templateLoaders); // Register template loaders that are supposed to kick in late. if (this.postTemplateLoaders != null) { templateLoaders.addAll(this.postTemplateLoaders); } TemplateLoader loader = getAggregateTemplateLoader(templateLoaders); if (loader != null) { config.setTemplateLoader(loader); } postProcessConfiguration(config); return config; }
Return a new Configuration object. Subclasses can override this for custom initialization (e.g. specifying a FreeMarker compatibility level which is a new feature in FreeMarker 2.3.21), or for using a mock object for testing.

Called by createConfiguration().

Throws:
  • IOException – if a config file wasn't found
  • TemplateException – on FreeMarker initialization failure
See Also:
  • createConfiguration()
Returns:the Configuration object
/** * Return a new Configuration object. Subclasses can override this for custom * initialization (e.g. specifying a FreeMarker compatibility level which is a * new feature in FreeMarker 2.3.21), or for using a mock object for testing. * <p>Called by {@code createConfiguration()}. * @return the Configuration object * @throws IOException if a config file wasn't found * @throws TemplateException on FreeMarker initialization failure * @see #createConfiguration() */
protected Configuration newConfiguration() throws IOException, TemplateException { return new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); }
Determine a FreeMarker TemplateLoader for the given path.

Default implementation creates either a FileTemplateLoader or a SpringTemplateLoader.

Params:
  • templateLoaderPath – the path to load templates from
See Also:
Returns:an appropriate TemplateLoader
/** * Determine a FreeMarker TemplateLoader for the given path. * <p>Default implementation creates either a FileTemplateLoader or * a SpringTemplateLoader. * @param templateLoaderPath the path to load templates from * @return an appropriate TemplateLoader * @see freemarker.cache.FileTemplateLoader * @see SpringTemplateLoader */
protected TemplateLoader getTemplateLoaderForPath(String templateLoaderPath) { if (isPreferFileSystemAccess()) { // Try to load via the file system, fall back to SpringTemplateLoader // (for hot detection of template changes, if possible). try { Resource path = getResourceLoader().getResource(templateLoaderPath); File file = path.getFile(); // will fail if not resolvable in the file system if (logger.isDebugEnabled()) { logger.debug( "Template loader path [" + path + "] resolved to file path [" + file.getAbsolutePath() + "]"); } return new FileTemplateLoader(file); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Cannot resolve template loader path [" + templateLoaderPath + "] to [java.io.File]: using SpringTemplateLoader as fallback", ex); } return new SpringTemplateLoader(getResourceLoader(), templateLoaderPath); } } else { // Always load via SpringTemplateLoader (without hot detection of template changes). logger.debug("File system access not preferred: using SpringTemplateLoader"); return new SpringTemplateLoader(getResourceLoader(), templateLoaderPath); } }
To be overridden by subclasses that want to register custom TemplateLoader instances after this factory created its default template loaders.

Called by createConfiguration(). Note that specified "postTemplateLoaders" will be registered after any loaders registered by this callback; as a consequence, they are not included in the given List.

Params:
  • templateLoaders – the current List of TemplateLoader instances, to be modified by a subclass
See Also:
  • createConfiguration()
  • setPostTemplateLoaders
/** * To be overridden by subclasses that want to register custom * TemplateLoader instances after this factory created its default * template loaders. * <p>Called by {@code createConfiguration()}. Note that specified * "postTemplateLoaders" will be registered <i>after</i> any loaders * registered by this callback; as a consequence, they are <i>not</i> * included in the given List. * @param templateLoaders the current List of TemplateLoader instances, * to be modified by a subclass * @see #createConfiguration() * @see #setPostTemplateLoaders */
protected void postProcessTemplateLoaders(List<TemplateLoader> templateLoaders) { }
Return a TemplateLoader based on the given TemplateLoader list. If more than one TemplateLoader has been registered, a FreeMarker MultiTemplateLoader needs to be created.
Params:
  • templateLoaders – the final List of TemplateLoader instances
Returns:the aggregate TemplateLoader
/** * Return a TemplateLoader based on the given TemplateLoader list. * If more than one TemplateLoader has been registered, a FreeMarker * MultiTemplateLoader needs to be created. * @param templateLoaders the final List of TemplateLoader instances * @return the aggregate TemplateLoader */
@Nullable protected TemplateLoader getAggregateTemplateLoader(List<TemplateLoader> templateLoaders) { switch (templateLoaders.size()) { case 0: logger.debug("No FreeMarker TemplateLoaders specified"); return null; case 1: return templateLoaders.get(0); default: TemplateLoader[] loaders = templateLoaders.toArray(new TemplateLoader[0]); return new MultiTemplateLoader(loaders); } }
To be overridden by subclasses that want to perform custom post-processing of the Configuration object after this factory performed its default initialization.

Called by createConfiguration().

Params:
  • config – the current Configuration object
Throws:
  • IOException – if a config file wasn't found
  • TemplateException – on FreeMarker initialization failure
See Also:
  • createConfiguration()
/** * To be overridden by subclasses that want to perform custom * post-processing of the Configuration object after this factory * performed its default initialization. * <p>Called by {@code createConfiguration()}. * @param config the current Configuration object * @throws IOException if a config file wasn't found * @throws TemplateException on FreeMarker initialization failure * @see #createConfiguration() */
protected void postProcessConfiguration(Configuration config) throws IOException, TemplateException { } }