package org.jbake.template; import freemarker.template.Configuration; import freemarker.template.DefaultObjectWrapper; import freemarker.template.SimpleCollection; import freemarker.template.SimpleDate; import freemarker.template.SimpleHash; import freemarker.template.SimpleSequence; import freemarker.template.Template; import freemarker.template.TemplateDateModel; import freemarker.template.TemplateException; import freemarker.template.TemplateHashModel; import freemarker.template.TemplateModel; import freemarker.template.TemplateModelException; import org.apache.commons.configuration.CompositeConfiguration; import org.jbake.app.ConfigUtil.Keys; import org.jbake.app.Crawler; import java.io.File; import java.io.IOException; import java.io.Writer; import java.util.Collection; import java.util.Date; import java.util.Map; import org.jbake.app.ContentStore; /** * Renders pages using the <a href="http://freemarker.org/">Freemarker</a> template engine. * * @author Cédric Champeau */ public class FreemarkerTemplateEngine extends AbstractTemplateEngine { private Configuration templateCfg; public FreemarkerTemplateEngine(final CompositeConfiguration config, final ContentStore db, final File destination, final File templatesPath) { super(config, db, destination, templatesPath); createTemplateConfiguration(config, templatesPath); } private void createTemplateConfiguration(final CompositeConfiguration config, final File templatesPath) { templateCfg = new Configuration(); templateCfg.setDefaultEncoding(config.getString(Keys.RENDER_ENCODING)); try { templateCfg.setDirectoryForTemplateLoading(templatesPath); } catch (IOException e) { e.printStackTrace(); } templateCfg.setObjectWrapper(new DefaultObjectWrapper()); } @Override public void renderDocument(final Map<String, Object> model, final String templateName, final Writer writer) throws RenderingException { try { Template template = templateCfg.getTemplate(templateName); template.process(new LazyLoadingModel(model, db), writer); } catch (IOException e) { throw new RenderingException(e); } catch (TemplateException e) { throw new RenderingException(e); } } /** * A custom Freemarker model that avoids loading the whole documents into memory if not necessary. */ public static class LazyLoadingModel implements TemplateHashModel { private final SimpleHash eagerModel; private final ContentStore db; public LazyLoadingModel(final Map<String, Object> eagerModel, final ContentStore db) { this.eagerModel = new SimpleHash(eagerModel); this.db = db; } @Override public TemplateModel get(final String key) throws TemplateModelException { try { return extractors.extractAndTransform(db, key, eagerModel.toMap(), new TemplateEngineAdapter<TemplateModel>() { @Override public TemplateModel adapt(String key, Object extractedValue) { if(key.equals(Crawler.Attributes.ALLTAGS)) { return new SimpleCollection((Collection) extractedValue); } else if(key.equals(Crawler.Attributes.PUBLISHED_DATE)) { return new SimpleDate((Date) extractedValue, TemplateDateModel.UNKNOWN); } else { // All other cases, as far as I know, are document collections return new SimpleSequence((Collection) extractedValue); } } }); } catch(NoModelExtractorException e) { return eagerModel.get(key); } } @Override public boolean isEmpty() throws TemplateModelException { return false; } } }