/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.jooby.pebble;
import static java.util.Objects.requireNonNull;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import org.jooby.Env;
import org.jooby.Jooby;
import org.jooby.Renderer;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheBuilderSpec;
import com.google.inject.Binder;
import com.google.inject.multibindings.Multibinder;
import com.mitchellbosecke.pebble.PebbleEngine;
import com.mitchellbosecke.pebble.loader.ClasspathLoader;
import com.mitchellbosecke.pebble.loader.Loader;
import com.typesafe.config.Config;
/**
* <h1>pebble</h1>
* <p>
* <a href="http://www.mitchellbosecke.com/pebble">Pebble</a> a lightweight but rock solid Java
* templating engine.
* </p>
*
* <h2>usage</h2>
* <pre>
* {
* use(new Pebble());
*
* get("/", req {@literal ->} Results.html("index").put("model", new MyModel());
*
* // or Pebble API
* get("/pebble-api", req {@literal ->} {
* PebbleEngine pebble = req.require(PebbleEngine.class);
* PebbleTemplate template = pebble.getTemplate("template");
* template.evaluate(...);
* });
* }
* </pre>
*
* <p>
* Templates are loaded from root of classpath: <code>/</code> and must end with: <code>.html</code>
* file extension.
* </p>
*
* <h2>template loader</h2>
* <p>
* Templates are loaded from the root of classpath and must end with <code>.html</code>. You can
* change the default template location and extensions too:
* </p>
*
* <pre>
* {
* use(new Pebble("templates", ".pebble"));
* }
* </pre>
*
* <h2>template cache</h2>
* <p>
* Cache is OFF when <code>env=dev</code> (useful for template reloading), otherwise is ON.
* </p>
* <p>
* Cache is backed by Guava and the default cache will expire after <code>200</code> entries.
* </p>
* <p>
* If <code>200</code> entries is not enough or you need a more advanced cache setting, just set the
* <code>pebble.cache</code> option:
* </p>
*
* <pre>
* pebble.cache = "expireAfterWrite=1h;maximumSize=200"
* </pre>
*
* <p>
* See {@link CacheBuilderSpec}.
* </p>
*
* <h2>tag cache</h2>
* <p>
* It works like template cache, except the cache is controlled by the property:
* <code>pebble.tagCache</code>
* </p>
*
* <h2>advanced configuration</h2>
* <p>
* Advanced configuration if provided by callback:
* </p>
*
* <pre>
* {
* use(new Pebble().doWith(pebble {@literal ->} {
* pebble.extension(...);
* pebble.loader(...);
* }));
* }
* </pre>
*
* <p>
* That's all folks! Enjoy it!!!
* </p>
*
* @author edgar
* @since 0.13.0
*/
public class Pebble implements Jooby.Module {
/** Default cache size on prod . */
private static final int MAX_SIZE = 100;
private BiConsumer<PebbleEngine.Builder, Config> callback;
private PebbleEngine.Builder pebble;
/**
* Creates a new {@link Pebble} module. Add a {@link ClasspathLoader} and set prefix and suffix on
* it.
*
* @param prefix Template prefix location (might be null).
* @param suffix Template extension.
*/
public Pebble(final String prefix, final String suffix) {
this.pebble = new PebbleEngine.Builder().loader(loader(prefix, suffix));
}
/**
* Creates a new {@link Pebble} module. Add a {@link ClasspathLoader} that loads template from
* root of classpath and ends with the given <code>suffix</code>.
*
* @param suffix Template extension.
*/
public Pebble(final String suffix) {
this(null, suffix);
}
/**
* Creates a new {@link Pebble} module. Add a {@link ClasspathLoader} that loads template from
* root of classpath and ends with the given <code>.html</code>.
*/
public Pebble() {
this(null, ".html");
}
/**
* Advanced configuration callback for {@link PebbleEngine.Builder}.
*
* @param callback A callback to finish setup.
* @return This module.
*/
public Pebble doWith(final BiConsumer<PebbleEngine.Builder, Config> callback) {
this.callback = requireNonNull(callback, "Callback is required.");
return this;
}
/**
* Advanced configuration callback for {@link PebbleEngine.Builder}.
*
* @param callback A callback to finish setup.
* @return This module.
*/
public Pebble doWith(final Consumer<PebbleEngine.Builder> callback) {
requireNonNull(callback, "Callback is required.");
return doWith((p, c) -> callback.accept(p));
}
@SuppressWarnings({"unchecked", "rawtypes" })
@Override
public void configure(final Env env, final Config conf, final Binder binder) {
/**
* This control pebble caches: template and tags.
*
* It always on and we always set a template/tags cache, we don't let pebble to apply defaults.
*
* In development <code>env.name=dev</code> we set a Guava cache with maxSize=0. This allow us
* to reload templates without restarting the application.
*/
pebble.cacheActive(true);
/**
* Cache builder:
* 1. if there is a custom cache, then use it (regardless of application env)
* 2. when missing, we set a default cache for dev or prod.
*/
String mode = env.name();
Function<String, Cache> cache = path -> {
if (conf.hasPath(path)) {
return CacheBuilder.from(conf.getString(path)).build();
}
// dev vs prod:
return CacheBuilder.newBuilder().maximumSize("dev".equals(mode) ? 0 : MAX_SIZE).build();
};
/** Template cache. */
pebble.templateCache(cache.apply("pebble.cache"));
/** Tag cache. */
pebble.tagCache(cache.apply("pebble.tagCache"));
/** locale. */
pebble.defaultLocale(env.locale());
// done defaults, allow user to override everything
if (callback != null) {
callback.accept(pebble, conf);
}
this.pebble.extension(new XssExt(env));
PebbleEngine pebble = this.pebble.build();
binder.bind(PebbleEngine.class).toInstance(pebble);
Multibinder.newSetBinder(binder, Renderer.class)
.addBinding()
.toInstance(new PebbleRenderer(pebble));
}
private static Loader<String> loader(final String prefix, final String suffix) {
Loader<String> loader = new ClasspathLoader();
loader.setPrefix(prefix);
loader.setSuffix(suffix);
return loader;
}
}