org.jbake.template.TemplateEngines Maven / Gradle / Ivy
Show all versions of jbake-core Show documentation
package org.jbake.template;
import org.jbake.app.ContentStore;
import org.jbake.app.configuration.JBakeConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
*
* A singleton class giving access to rendering engines. Rendering engines are loaded based on classpath. New
* rendering may be registered either at runtime (not recommanded) or by putting a descriptor file on classpath
* (recommanded).
* The descriptor file must be found in META-INF directory and named
* org.jbake.parser.TemplateEngines.properties. The format of the file is easy:
* org.jbake.parser.FreeMarkerRenderer=ftl
org.jbake.parser.GroovyRenderer=groovy,gsp
* where the key is the class of the engine (must extend {@link AbstractTemplateEngine} and have
* a 4-arg constructor and the value is a comma-separated list of file extensions that this engine is capable
* of proceeding.
* Rendering engines are singletons, so are typically used to initialize the underlying template engines.
*
* This class loads the engines only if they are found on classpath. If not, the engine is not registered. This allows
* JBake to support multiple rendering engines without the explicit need to have them on classpath. This is a better fit
* for embedding.
*
*
* @author Cédric Champeau
*/
public class TemplateEngines {
private static final Logger LOGGER = LoggerFactory.getLogger(TemplateEngines.class);
private final Map engines;
public Set getRecognizedExtensions() {
return Collections.unmodifiableSet(engines.keySet());
}
public TemplateEngines(final JBakeConfiguration config, final ContentStore db) {
engines = new HashMap<>();
loadEngines(config, db);
}
private void registerEngine(String fileExtension, AbstractTemplateEngine templateEngine) {
AbstractTemplateEngine old = engines.put(fileExtension, templateEngine);
if (old != null) {
LOGGER.warn("Registered a template engine for extension [.{}] but another one was already defined: {}", fileExtension, old);
}
}
public AbstractTemplateEngine getEngine(String fileExtension) {
return engines.get(fileExtension);
}
/**
* This method is used to search for a specific class, telling if loading the engine would succeed. This is
* typically used to avoid loading optional modules.
*
*
* @param config the configuration
* @param db database instance
* @param engineClassName engine class, used both as a hint to find it and to create the engine itself. @return null if the engine is not available, an instance of the engine otherwise
*/
private static AbstractTemplateEngine tryLoadEngine(final JBakeConfiguration config, final ContentStore db, String engineClassName) {
try {
@SuppressWarnings("unchecked")
Class extends AbstractTemplateEngine> engineClass = (Class extends AbstractTemplateEngine>) Class.forName(engineClassName, false, TemplateEngines.class.getClassLoader());
Constructor extends AbstractTemplateEngine> ctor = engineClass.getConstructor(JBakeConfiguration.class, ContentStore.class);
return ctor.newInstance(config, db);
} catch (Throwable e) {
// not all engines might be necessary, therefore only emit class loading issue with level warn
LOGGER.debug("Template engine not available: {}", engineClassName);
return null;
}
}
/**
* This method is used internally to load markup engines. Markup engines are found using descriptor files on
* classpath, so adding an engine is as easy as adding a jar on classpath with the descriptor file included.
*/
private void loadEngines(final JBakeConfiguration config, final ContentStore db) {
try {
ClassLoader cl = TemplateEngines.class.getClassLoader();
Enumeration resources = cl.getResources("META-INF/org.jbake.parser.TemplateEngines.properties");
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
Properties props = new Properties();
props.load(url.openStream());
for (Map.Entry