All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.wl4g.infra.common.view.Freemarkers Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017 ~ 2025 the original author or authors. James Wong 
 *
 * 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 com.wl4g.infra.common.view;

import static com.wl4g.infra.common.collection.CollectionUtils2.safeList;
import static com.wl4g.infra.common.lang.Assert2.hasTextOf;
import static com.wl4g.infra.common.lang.Assert2.notEmptyOf;
import static com.wl4g.infra.common.lang.Assert2.notNullOf;
import static com.wl4g.infra.common.log.SmartLoggerFactory.getLogger;
import static java.util.Arrays.asList;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.annotation.Nullable;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

import com.wl4g.infra.common.collection.CollectionUtils2;
import com.wl4g.infra.common.log.SmartLogger;
import com.wl4g.infra.common.resource.StreamResource;
import com.wl4g.infra.common.resource.resolver.DefaultResourceLoader;
import com.wl4g.infra.common.resource.resolver.ResourceLoader;

import freemarker.cache.ClassTemplateLoader;
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.MultiTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.SimpleHash;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.Version;

/**
 * {@link Freemarkers}
 * 
 * @author James Wong <[email protected]>
 * @version 2020-09-12
 * @since v2.0.0
 * @see {@link org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer}
 */
public abstract class Freemarkers {
    protected final SmartLogger log = getLogger(getClass());

    @Nullable
    private final Properties freemarkerSettings;
    @Nullable
    private final Map freemarkerVariables;
    @Nullable
    private final List preTemplateLoaders;
    @Nullable
    private final List templateLoaders;
    @Nullable
    private final List postTemplateLoaders;
    @Nullable
    private final List templateLoaderPaths;

    @Nullable
    private StreamResource configLocation;
    @Nullable
    private Version version = Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS;

    private Freemarkers() {
        this.freemarkerSettings = new Properties() {
            private static final long serialVersionUID = 270891168780776271L;
            {
                setProperty("template_update_delay", "0");
                setProperty("default_encoding", "UTF-8");
                setProperty("number_format", "0.####");
                setProperty("datetime_format", "yyyy-MM-dd HH:mm:ss");
                setProperty("classic_compatible", "true");
                setProperty("template_exception_handler", "ignore");
            }
        };
        this.freemarkerVariables = new HashMap<>(4);
        this.preTemplateLoaders = new ArrayList<>(4);
        this.templateLoaders = new ArrayList<>(4);
        this.postTemplateLoaders = new ArrayList<>(4);
        this.templateLoaderPaths = new ArrayList<>(4);
    }

    public static Freemarkers createDefault() {
        Freemarkers instance = new Freemarkers() {
        };
        instance.templateLoaderPaths.addAll(asList("/"));
        return instance;
    }

    public static Freemarkers create(String... templateLoaderPaths) {
        notEmptyOf(templateLoaderPaths, "templateLoaderPaths");
        Freemarkers instance = createDefault();
        instance.templateLoaderPaths.clear();
        instance.templateLoaderPaths.addAll(asList(templateLoaderPaths));
        return instance;
    }

    public Freemarkers withFreemarkerSettings(@NotEmpty Properties freemarkerSettings) {
        this.freemarkerSettings.putAll(notEmptyOf(freemarkerSettings, "freemarkerSettings"));
        return this;
    }

    public Freemarkers withFreemarkerVariables(@NotEmpty Map freemarkerVariables) {
        this.freemarkerVariables.putAll(notEmptyOf(freemarkerVariables, "freemarkerVariables"));
        return this;
    }

    public Freemarkers withPreTemplateLoaders(@NotEmpty List preTemplateLoaders) {
        this.preTemplateLoaders.addAll(notEmptyOf(preTemplateLoaders, "preTemplateLoaders"));
        return this;
    }

    public Freemarkers withTemplateLoaders(@NotEmpty List templateLoaders) {
        this.templateLoaders.addAll(notEmptyOf(templateLoaders, "templateLoaders"));
        return this;
    }

    public Freemarkers withPostTemplateLoaders(@NotEmpty List postTemplateLoaders) {
        this.postTemplateLoaders.addAll(notEmptyOf(postTemplateLoaders, "postTemplateLoaders"));
        return this;
    }

    public Freemarkers withConfigLocation(@NotNull StreamResource configLocation) {
        this.configLocation = notNullOf(configLocation, "configLocation");
        return this;
    }

    public Freemarkers withVersion(@NotNull Version version) {
        this.version = notNullOf(version, "version");
        return this;
    }

    /**
     * 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 build() {
        Configuration config = new Configuration(version);
        Properties props = new Properties();

        try {
            // Load config file if specified.
            if (configLocation != null) {
                log.debug("Loading FreeMarker configuration from " + configLocation);
                fillProperties(props, configLocation);
            }
            // Merge local properties if specified.
            if (freemarkerSettings != null) {
                props.putAll(freemarkerSettings);
            }
            // FreeMarker will only accept known keys in its setSettings and
            // setAllSharedVariables methods.
            if (!props.isEmpty()) {
                config.setSettings(props);
            }
            if (!CollectionUtils2.isEmpty(freemarkerVariables)) {
                config.setAllSharedVariables(new SimpleHash(freemarkerVariables, config.getObjectWrapper()));
            }
            config.setDefaultEncoding("UTF-8");
            List tplLoaders = new ArrayList<>(safeList(templateLoaders));
            // Register default template loaders.
            if (templateLoaderPaths != null) {
                for (String path : templateLoaderPaths) {
                    tplLoaders.add(getTemplateLoaderForPath(path));
                }
            }
            tplLoaders.add(new ClassTemplateLoader(Freemarkers.class, ""));
            // Register template loaders that are supposed to kick in late.
            if (postTemplateLoaders != null) {
                tplLoaders.addAll(postTemplateLoaders);
            }
            TemplateLoader loader = getAggregateTemplateLoader(tplLoaders);
            if (loader != null) {
                config.setTemplateLoader(loader);
            }
        } catch (IOException | TemplateException e) {
            throw new IllegalStateException(e);
        }

        return config;
    }

    /**
     * Process the specified FreeMarker template with the given model and write
     * the result to the given Writer.
     * 
     * @param model
     *            the model object, typically a Map that contains model names as
     *            keys and model objects as values
     * @return the result as String
     * @throws IOException
     *             if the template wasn't found or couldn't be read
     * @throws freemarker.template.TemplateException
     *             if rendering failed
     */
    public static String renderingTemplateToString(@NotNull Template template, @Nullable Object model)
            throws IOException, TemplateException {
        notNullOf(template, "template");

        StringWriter result = new StringWriter();
        template.process(model, result);
        return result.toString();
    }

    /**
     * Process the specified FreeMarker template with the given model and write
     * the result to the given Writer.
     * 
     * @param templateName
     *            template name.
     * @param templateString
     *            template string content.
     * @param model
     *            the model object, typically a Map that contains model names as
     *            keys and model objects as values
     * @return the result as String
     * @throws IOException
     *             if the template wasn't found or couldn't be read
     * @throws freemarker.template.TemplateException
     *             if rendering failed
     */
    public static String renderingTemplateToString(
            @NotBlank String templateName,
            @NotBlank String templateString,
            @Nullable Object model) throws IOException, TemplateException {
        hasTextOf(templateName, "templateName");
        hasTextOf(templateString, "templateString");

        Template template = new Template(templateName, new StringReader(templateString), defaultConfigurer);
        return renderingTemplateToString(template, model);
    }

    /**
     * Determine a FreeMarker TemplateLoader for the given path.
     * 

* Default implementation creates either a FileTemplateLoader or a * ResourceTemplateLoader. * * @param templateLoaderPath * the path to load templates from * @return an appropriate TemplateLoader * @see freemarker.cache.FileTemplateLoader * @see ResourceTemplateLoader */ private TemplateLoader getTemplateLoaderForPath(String templateLoaderPath) { // Try to load via the file system, fall back to // ResourceTemplateLoader // (for hot detection of template changes, if possible). try { final StreamResource resource = defaultResourceLoader.getResource(templateLoaderPath); // will fail if not resolvable in the file system File file = resource.getFile(); log.debug("Template loader path [" + file + "] resolved to file path [" + file.getAbsolutePath() + "]"); return new FileTemplateLoader(file); } catch (Exception ex) { log.debug( "Cannot resolve template loader path [{}] to [java.io.File]: using ResourceTemplateLoader as fallback. reason: {}", templateLoaderPath, ex.getMessage()); return new ResourceTemplateLoader(defaultResourceLoader, templateLoaderPath); } } /** * Agergate multi template loaders. * * @param templateLoaders * @return */ private TemplateLoader getAggregateTemplateLoader(List templateLoaders) { switch (templateLoaders.size()) { case 0: log.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); } } /** * Fill the given properties from the given resource (in ISO-8859-1 * encoding). * * @param props * the Properties instance to fill * @param resource * the resource to load from * @throws IOException * if loading failed */ private static void fillProperties(Properties props, StreamResource resource) throws IOException { InputStream is = resource.getInputStream(); try { String filename = resource.getFilename(); if (filename != null && filename.endsWith(".xml")) { props.loadFromXML(is); } else { props.load(is); } } finally { is.close(); } } /** * FreeMarker {@link TemplateLoader} adapter that loads via a * {@link ResourceLoader}. for any resource loader path that cannot be * resolved to a {@link java.io.File}. */ private static class ResourceTemplateLoader implements TemplateLoader { protected static final SmartLogger log = getLogger(ResourceTemplateLoader.class); private final ResourceLoader resourceLoader; private final String templateLoaderPath; /** * Create a new ResourceTemplateLoader. * * @param resourceLoader * the ResourceLoader to use * @param templateLoaderPath * the template loader path to use */ public ResourceTemplateLoader(ResourceLoader resourceLoader, String templateLoaderPath) { this.resourceLoader = resourceLoader; if (!templateLoaderPath.endsWith("/")) { templateLoaderPath += "/"; } this.templateLoaderPath = templateLoaderPath; log.debug("ResourceTemplateLoader for FreeMarker: using resource loader [" + this.resourceLoader + "] and template loader path [" + this.templateLoaderPath + "]"); } @Override @Nullable public Object findTemplateSource(String name) throws IOException { log.debug("Looking for FreeMarker template with name [" + name + "]"); StreamResource resource = this.resourceLoader.getResource(this.templateLoaderPath + name); return (resource.exists() ? resource : null); } @Override public Reader getReader(Object templateSource, String encoding) throws IOException { StreamResource resource = (StreamResource) templateSource; try { return new InputStreamReader(resource.getInputStream(), encoding); } catch (IOException ex) { log.debug("Could not find FreeMarker template: " + resource); throw ex; } } @Override public long getLastModified(Object templateSource) { StreamResource resource = (StreamResource) templateSource; try { return resource.lastModified(); } catch (IOException ex) { log.debug("Could not obtain last-modified timestamp for FreeMarker template in " + resource + ": " + ex); return -1; } } @Override public void closeTemplateSource(Object templateSource) throws IOException { } } /** {@link ResourceLoader} */ private static final ResourceLoader defaultResourceLoader = new DefaultResourceLoader(); /** {@link Configuration} */ private static final Configuration defaultConfigurer = Freemarkers.createDefault().build(); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy