FreemarkerTemplateService.java

/**************************************************************************
 *
 * Copyright (c) 2016-2020 Yawg project contributors.
 *
 **************************************************************************/

package com.varmateo.yawg.freemarker;

import java.io.IOException;
import java.io.Writer;
import java.nio.file.Path;
import java.util.Optional;
import java.util.regex.Pattern;

import freemarker.template.Configuration;
import freemarker.template.TemplateExceptionHandler;
import io.vavr.control.Option;
import io.vavr.control.Try;

import com.varmateo.yawg.api.Result;
import com.varmateo.yawg.api.YawgException;
import com.varmateo.yawg.spi.Template;
import com.varmateo.yawg.spi.TemplateContext;
import com.varmateo.yawg.spi.TemplateService;
import com.varmateo.yawg.util.Results;


/**
 * Creates templates based on the <a
 * href="http://freemarker.org/">Freemarker</a> template engine.
 */
public final class FreemarkerTemplateService
        implements TemplateService {


    private static final Pattern RE_FTLH = Pattern.compile(".*\\.ftlh$");

    private final Configuration _fmConfig;


    /**
     * @param templatesDir The directory containing the Freemarker
     * template files.
     */
    private FreemarkerTemplateService(final Configuration fmConfig) {

        _fmConfig = fmConfig;
    }


    /**
     *
     */
    public static TemplateService create(final Path templatesDir) {

        final Configuration fmConfig;

        try {
            fmConfig = buildFreemarkerConfig(templatesDir);
        } catch ( IOException e ) {
            throw FreemarkerTemplateServiceException.initializationFailure(templatesDir, e);
        }

        return new FreemarkerTemplateService(fmConfig);
    }


    /**
     *
     */
    private static Configuration buildFreemarkerConfig(final Path templatesDir)
            throws IOException {

        final Configuration fmConfig = new Configuration(Configuration.VERSION_2_3_24);

        fmConfig.setDirectoryForTemplateLoading(templatesDir.toFile());
        fmConfig.setDefaultEncoding("UTF-8");
        fmConfig.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
        fmConfig.setLogTemplateExceptions(false);

        return fmConfig;
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public Optional<Template> prepareTemplate(final String name) {

        return Option.of(name)
                .filter(x -> RE_FTLH.matcher(x).matches())
                .map(this::prepareFreemarkerTemplate)
                .map(FreemarkerTemplate::new)
                .map(FreemarkerTemplate::asTemplate) // HACK
                .toJavaOptional();
    }


    /**
     *
     */
    private freemarker.template.Template prepareFreemarkerTemplate(final String name) {

        freemarker.template.Template fmTemplate = null;

        try {
            fmTemplate = _fmConfig.getTemplate(name);
        } catch ( IOException e ) {
            throw FreemarkerTemplateServiceException.fetchFailure(name, e);
        }

        return fmTemplate;
    }


    /**
     *
     */
    private static final class FreemarkerTemplate
            implements Template {


        private final freemarker.template.Template _fmTemplate;


        /**
         *
         */
        /* default */ FreemarkerTemplate(final freemarker.template.Template fmTemplate) {

            _fmTemplate = fmTemplate;
        }


        /**
         *
         */
        @Override
        public Result<Void> process(
                final TemplateContext context,
                final Writer writer) {

            final FreemarkerDataModel fmDataModel = new FreemarkerDataModel(context);
            final Try<Void> result = Try.run(() -> _fmTemplate.process(fmDataModel, writer))
                    .recoverWith(this::processingFailure);

            return Results.fromTry(result);
        }


        /**
         * HACK.
         */
        public Template asTemplate() {

            return this;
        }


        private <T> Try<T> processingFailure(final Throwable rootCause) {

            final YawgException cause = FreemarkerTemplateServiceException.processingFailure(
                    _fmTemplate.getName(), rootCause);

            return Try.failure(cause);
        }


    }

}