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

io.helidon.build.maven.sitegen.freemarker.FreemarkerEngine Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2018, 2025 Oracle and/or its affiliates.
 *
 * 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 io.helidon.build.maven.sitegen.freemarker;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import io.helidon.build.common.logging.Log;
import io.helidon.build.maven.sitegen.Config;
import io.helidon.build.maven.sitegen.Context;
import io.helidon.build.maven.sitegen.RenderingException;

import freemarker.core.Environment;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import freemarker.template.TemplateNotFoundException;
import freemarker.template.Version;

import static io.helidon.build.common.Strings.requireValid;

/**
 * A facade over freemarker.
 */
@SuppressWarnings("unused")
public class FreemarkerEngine {

    private static final Version FREEMARKER_VERSION = Configuration.VERSION_2_3_23;
    private static final ObjectWrapper OBJECT_WRAPPER = new ObjectWrapper(FREEMARKER_VERSION);

    private final String backend;
    private final Map directives;
    private final Map model;
    private final Configuration freemarker;

    private FreemarkerEngine(Builder builder) {
        backend = requireValid(builder.backend, "backend is invalid!");
        directives = builder.directives;
        model = builder.model;
        freemarker = new Configuration(FREEMARKER_VERSION);
        freemarker.setTemplateLoader(new TemplateLoader());
        freemarker.setDefaultEncoding("UTF-8");
        freemarker.setObjectWrapper(OBJECT_WRAPPER);
        freemarker.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
        freemarker.setLogTemplateExceptions(false);
    }

    /**
     * Get the custom directives in-use.
     *
     * @return map, never {@code null}
     */
    public Map directives() {
        return directives;
    }

    /**
     * Get the custom model in-use.
     *
     * @return map, never {@code null}
     */
    public Map model() {
        return model;
    }

    /**
     * Render a template to a file.
     *
     * @param template   the relative path of the template to render
     * @param targetPath the relative target path of the file to create
     * @param model      the model for the template to use
     * @param outputDir  the output directory
     * @throws RenderingException if an error occurred
     */
    public void renderFile(String template, String targetPath, Map model, Path outputDir)
            throws RenderingException {

        try {
            String rendered = renderString(template, model);
            Path target = outputDir.resolve(targetPath);
            Files.createDirectories(target.getParent());
            Files.writeString(target, rendered);
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    /**
     * Render a template to a string.
     *
     * @param template the relative path of the template to render
     * @param model    the model for the template to use
     * @return the rendered output
     * @throws RenderingException if an error occurred
     */
    public String renderString(String template, Object model) throws RenderingException {
        Context ctx = Context.get();
        String path = backend + "/" + template;
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            Template tpl = freemarker.getTemplate(path);
            OutputStreamWriter writer = new OutputStreamWriter(baos);
            Log.debug("Applying template: %s", path);
            Environment env = tpl.createProcessingEnvironment(model, writer);
            for (Entry directive : ctx.templateSession().directives().entrySet()) {
                env.setVariable(directive.getKey(), directive.getValue());
            }
            env.setVariable("helper", new HelperHashModel(OBJECT_WRAPPER));
            env.setVariable("passthroughfix", new PassthroughFixDirective());
            env.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
            env.setLogTemplateExceptions(false);
            env.process();
            return baos.toString(StandardCharsets.UTF_8);
        } catch (TemplateNotFoundException ex) {
            boolean strict = ctx.strictTemplates();
            String msg = String.format("Unable to find template: %s", path);
            if (strict) {
                throw new RenderingException(msg);
            }
            Log.warn(msg);
            return "";
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        } catch (TemplateException ex) {
            throw new FreemarkerRenderingException(path, ex);
        }
    }

    /**
     * Freemarker rendering exception.
     */
    public static final class FreemarkerRenderingException extends RenderingException {

        FreemarkerRenderingException(String template, Throwable cause) {
            super(String.format("An error occurred while rendering '%s'", template),
                    RenderingException.cause(cause));
        }
    }

    /**
     * A builder of {@link FreemarkerEngine}.
     */
    @SuppressWarnings("unused")
    public static final class Builder {

        private final Map directives = new HashMap<>();
        private final Map model = new HashMap<>();
        private String backend;

        /**
         * Set the backend name.
         *
         * @param backend backend name
         * @return this builder
         */
        public Builder backend(String backend) {
            this.backend = backend;
            return this;
        }

        /**
         * Add custom directives.
         *
         * @param directives the directives to set
         * @return this builder
         */
        public Builder directives(Map directives) {
            if (directives != null) {
                this.directives.putAll(directives);
            }
            return this;
        }

        /**
         * Add some custom model.
         *
         * @param model the model to set
         * @return this builder
         */
        public Builder model(Map model) {
            if (model != null) {
                this.model.putAll(model);
            }
            return this;
        }

        /**
         * Apply the specified configuration.
         *
         * @param config config
         * @return this builder
         */
        public Builder config(Config config) {
            directives.putAll(config.get("directives").asMap().orElseGet(Map::of));
            model.putAll(config.get("model").asMap().orElseGet(Map::of));
            return this;
        }

        /**
         * Build the instance.
         *
         * @return new instance
         */
        public FreemarkerEngine build() {
            return new FreemarkerEngine(this);
        }
    }

    /**
     * Create a new instance from configuration.
     *
     * @param backend backend name
     * @param config  config
     * @return new instance
     */
    public static FreemarkerEngine create(String backend, Config config) {
        return builder()
                .backend(backend)
                .config(config)
                .build();
    }

    /**
     * Create a new instance.
     *
     * @param backend backend name
     * @return new instance
     */
    public static FreemarkerEngine create(String backend) {
        return builder()
                .backend(backend)
                .build();
    }

    /**
     * Create a new builder.
     *
     * @return new builder
     */
    public static Builder builder() {
        return new Builder();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy