Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.greenlaw110.rythm.RythmEngine Maven / Gradle / Ivy
Go to download
A strong typed high performance Java Template engine with .Net Razor like syntax
/*
* Copyright (C) 2013 The Rythm Engine project
* Gelin Luo
*
* 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 com.greenlaw110.rythm;
import com.greenlaw110.rythm.extension.ICacheService;
import com.greenlaw110.rythm.conf.RythmConfiguration;
import com.greenlaw110.rythm.conf.RythmConfigurationKey;
import com.greenlaw110.rythm.exception.RythmException;
import com.greenlaw110.rythm.exception.TagLoadException;
import com.greenlaw110.rythm.extension.ICodeType;
import com.greenlaw110.rythm.extension.IDurationParser;
import com.greenlaw110.rythm.extension.Transformer;
import com.greenlaw110.rythm.internal.*;
import com.greenlaw110.rythm.internal.compiler.*;
import com.greenlaw110.rythm.internal.dialect.AutoToString;
import com.greenlaw110.rythm.internal.dialect.BasicRythm;
import com.greenlaw110.rythm.internal.dialect.DialectManager;
import com.greenlaw110.rythm.internal.dialect.ToString;
import com.greenlaw110.rythm.logger.ILogger;
import com.greenlaw110.rythm.extension.ILoggerFactory;
import com.greenlaw110.rythm.logger.Logger;
import com.greenlaw110.rythm.logger.NullLogger;
import com.greenlaw110.rythm.resource.ITemplateResource;
import com.greenlaw110.rythm.resource.StringTemplateResource;
import com.greenlaw110.rythm.resource.TemplateResourceManager;
import com.greenlaw110.rythm.resource.ToStringTemplateResource;
import com.greenlaw110.rythm.sandbox.SandboxExecutingService;
import com.greenlaw110.rythm.sandbox.SandboxThreadFactory;
import com.greenlaw110.rythm.template.ITag;
import com.greenlaw110.rythm.template.ITemplate;
import com.greenlaw110.rythm.template.JavaTagBase;
import com.greenlaw110.rythm.template.TemplateBase;
import com.greenlaw110.rythm.toString.ToStringOption;
import com.greenlaw110.rythm.toString.ToStringStyle;
import com.greenlaw110.rythm.utils.F;
import com.greenlaw110.rythm.utils.IO;
import com.greenlaw110.rythm.utils.JSONWrapper;
import com.greenlaw110.rythm.utils.S;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Not Thread Safe
*
* A Rythm Template Engine is the entry to the Rythm templating system. It provides a set of
* APIs to render template. Each JVM allows multiple RythmEngine
instance, with each
* one represent a set of configurations.
*
* The {@link Rythm} facade contains a default RythmEngine
instance to make it
* easy to use for most cases
*/
public class RythmEngine implements IEventDispatcher {
private static final ILogger logger = Logger.get(RythmEngine.class);
/**
* Rythm Engine Version. Used along with
* {@link com.greenlaw110.rythm.conf.RythmConfigurationKey#ENGINE_PLUGIN_VERSION plugin version} to
* check if the cached template bytecode need to be refreshed or not
*
*/
private static final String version;
static {
version = IO.readContentAsString(RythmEngine.class.getClassLoader().getResourceAsStream("rythm-engine-version"));
}
private static final InheritableThreadLocal _engine = new InheritableThreadLocal();
/**
* Set the engine instance to a {@link ThreadLocal} variable, thus it is easy to
* {@link #get() get} the current
* RythmEngine
dominating the rendering process.
*
* Note , this method is NOT an API to be called by user application
*
* @param engine
*/
public static void set(RythmEngine engine) {
_engine.set(engine);
}
/**
* Get the current engine instance from a {@link ThreadLocal} variable which is
* {@link #set(RythmEngine) set} previously.
*
* Note , this method is NOT an API to be called by user application
*
* @return the engine
*/
public static RythmEngine get() {
return _engine.get();
}
/**
* Check if the current rendering is dominated by a {@link Sandbox}
*
* Note , this method is NOT an API to be called by user application
*
* @return true if the current thread is running in Sandbox mode
*/
public static boolean insideSandbox() {
return Sandbox.sandboxMode();
}
@Override
public String toString() {
return null == _conf ? "rythm-engine-uninitialized" : id();
}
/* -----------------------------------------------------------------------------
Fields and Accessors
-------------------------------------------------------------------------------*/
private RythmConfiguration _conf = null;
/**
* Return {@link RythmConfiguration configuration} of the engine
*
* Usually user application should not call this method
*
* @return rythm configuration
*/
public RythmConfiguration conf() {
if (null == _conf) {
throw new IllegalStateException("Rythm engine not initialized");
}
return _conf;
}
/**
* Return Version string of the engine instance. The version string
* is composed by {@link #version Rythm version} and the
* configured {@link RythmConfigurationKey#ENGINE_PLUGIN_VERSION plugin version}. The version
* string will be used by Rythm to see if compiled bytecodes cached on disk should
* be refreshed in an new version or not.
*
* Note
, this method is not generally used by user application
*
* @return engine version along with plugin version
*/
public String version() {
return version + "-" + conf().pluginVersion();
}
private Rythm.Mode _mode = null;
/**
* Return the engine {@link Rythm.Mode mode}
*
* @return engine running mode
*/
public Rythm.Mode mode() {
if (null == _mode) {
_mode = conf().get(RythmConfigurationKey.ENGINE_MODE);
}
return _mode;
}
private String _id = null;
/**
* Return the instance {@link com.greenlaw110.rythm.conf.RythmConfigurationKey#ENGINE_ID}
*
* @return the id
*/
public String id() {
if (null == _id) {
_id = conf().get(RythmConfigurationKey.ENGINE_ID);
}
return _id;
}
/**
* Is this engine the default {@link Rythm#engine} instance?
*
* Note , not to be used by user application
*
* @return true if this is the default engine
*/
public boolean isSingleton() {
return Rythm.engine == this;
}
/**
* Is the engine running in {@link Rythm.Mode#prod product} mode?
*
* @return true if engine is running in prod mode
*/
public boolean isProdMode() {
return mode() == Rythm.Mode.prod;
}
/**
* Is the engine running in {@link Rythm.Mode#dev development} mode?
*
* @return true if engine is running is debug mode
*/
public boolean isDevMode() {
return mode() != Rythm.Mode.prod;
}
private TemplateResourceManager _resourceManager;
/**
* Get {@link TemplateResourceManager resource manager} of the engine
*
* Note , this method should not be used by user application
*
* @return resource manager
*/
public TemplateResourceManager resourceManager() {
return _resourceManager;
}
private TemplateClassManager _classes;
/**
* Get {@link TemplateClassManager template class manager} of the engine
*
* Note , this method should not be used by user application
*
* @return template class manager
*/
public TemplateClassManager classes() {
return _classes;
}
private TemplateClassLoader _classLoader = null;
/**
* Get {@link TemplateClassLoader class loader} of the engine
*
* Note , this method should not be used by user application
*
* @return template class loader
*/
public TemplateClassLoader classLoader() {
return _classLoader;
}
private TemplateClassCache _classCache = null;
/**
* Get {@link TemplateClassCache class cache} of the engine
*
* Note , this method should not be used by user application
*
* @return template class cache
*/
public TemplateClassCache classCache() {
return _classCache;
}
private ExtensionManager _extensionManager;
/**
* Return {@link ExtensionManager} of this engine
*
* @return extension manager
*/
public ExtensionManager extensionManager() {
return _extensionManager;
}
private final DialectManager _dialectManager = new DialectManager();
public DialectManager dialectManager() {
return _dialectManager;
}
private ICacheService _cacheService = null;
/**
* Define the render time settings, which is intialized each time a renderXX method
* get called
*/
public class RenderSettings {
private final ThreadLocal _locale = new ThreadLocal() {
@Override
protected Locale initialValue() {
return RythmEngine.this.conf().locale();
}
};
private final ThreadLocal _codeType = new ThreadLocal() {
@Override
protected ICodeType initialValue() {
//return RythmEngine.this.conf().defaultCodeType();
return null; // there are logic in TemplateClass.asTemplate() to further deduct code type from resource
}
};
private final ThreadLocal> _usrCtx = new ThreadLocal>();
/**
* Init the render time by setting {@link com.greenlaw110.rythm.extension.ICodeType code type}.
* This method should called before calling render methods to setup the context of this render
*
* @param codeType
* @return the render setting instance
*/
public final RenderSettings init(ICodeType codeType) {
if (null != codeType) _codeType.set(codeType);
else _codeType.remove();
return this;
}
/**
* Init the render time by setting {@link java.util.Locale locale}.
* This method should called before calling render methods to setup the context of this render
*
* @param locale
* @return the render setting instance
*/
public final RenderSettings init(Locale locale) {
if (null != locale) _locale.set(locale);
else _locale.remove();
return this;
}
/**
* Init the render time by setting user context
* This method should called before calling render methods to setup the context of this render
*
* @param usrCtx
* @return the render setting instance
*/
public final RenderSettings init(Map usrCtx) {
if (null != usrCtx) _usrCtx.set(usrCtx);
else _usrCtx.remove();
return this;
}
/**
* Return ThreadLocal locale.
*
* @return locale setting for this render process
*/
public final Locale locale() {
return _locale.get();
}
/**
* Return thread local code type
*
* @return {@link com.greenlaw110.rythm.extension.ICodeType code type} setting for this render process
*/
public final ICodeType codeType() {
return _codeType.get();
}
/**
* Return thread local user context map
*
* @return the user context map
*/
public final Map userContext() {
return _usrCtx.get();
}
/**
* Clear the render time after render process done
* @return
*/
public final RythmEngine clear() {
_locale.remove();
_codeType.remove();
_usrCtx.remove();
return RythmEngine.this;
}
}
/**
* The RenderSettings instance keep the environment settings for one render operation
*/
public final RenderSettings renderSettings = new RenderSettings();
/**
* Prepare the render operation environment settings
*
* @param codeType
* @param locale
* @param usrCtx
* @return the engine instance itself
*/
public final RythmEngine prepare(ICodeType codeType, Locale locale, Map usrCtx) {
renderSettings.init(codeType).init(locale).init(usrCtx);
return this;
}
/**
* Prepare the render operation environment settings
*
* @param codeType
* @return the engine instance itself
*/
public final RythmEngine prepare(ICodeType codeType) {
renderSettings.init(codeType);
return this;
}
/**
* Prepare the render operation environment settings
*
* @param locale
* @return the engine instance itself
*/
public final RythmEngine prepare(Locale locale) {
renderSettings.init(locale);
return this;
}
/**
* Prepare the render operation environment settings
*
* @param userContext
* @return the engine instance itself
*/
public final RythmEngine prepare(Map userContext) {
renderSettings.init(userContext);
return this;
}
/* -----------------------------------------------------------------------------
Constructors, Configuration and Initializing
-------------------------------------------------------------------------------*/
private void _initLogger(Map conf) {
boolean logEnabled = (Boolean)RythmConfigurationKey.LOG_ENABLED.getConfiguration(conf);
if (logEnabled) {
ILoggerFactory factory = RythmConfigurationKey.LOG_FACTORY_IMPL.getConfiguration(conf);
Logger.registerLoggerFactory(factory);
} else {
Logger.registerLoggerFactory(new NullLogger.Factory());
}
}
// trim "rythm." from conf keys
private Map _processConf(Map conf) {
Map m = new HashMap(conf.size());
for (String s : conf.keySet()) {
Object o = conf.get(s);
if (s.startsWith("rythm.")) s = s.replaceFirst("rythm\\.", "");
m.put(s, o);
}
return m;
}
private void _initConf(Map conf, File confFile) {
// load conf from disk
Map rawConf = _loadConfFromDisk(confFile);
rawConf = _processConf(rawConf);
// load conf from System.properties
Properties sysProps = System.getProperties();
rawConf.putAll((Map) sysProps);
// load conf from user supplied configuration
if (null != conf) rawConf.putAll((Map)_processConf(conf));
_initLogger(rawConf);
// initialize the configuration with all loaded data
this._conf = new RythmConfiguration(rawConf);
// check if it needs to debug the conf information
if (rawConf.containsKey("rythm.debug_conf") && Boolean.parseBoolean(rawConf.get("rythm.debug_conf").toString())) {
this._conf.debug();
}
}
/**
* Create a rythm engine instance with default configuration
*
* @see com.greenlaw110.rythm.conf.RythmConfigurationKey
*/
public RythmEngine() {
init(null, null);
}
/**
* Create a rythm engine instance with either template root or configuration file specified
*
* @param file if is directory then the template root, otherwise then the configuration file
* @see com.greenlaw110.rythm.conf.RythmConfigurationKey
*/
public RythmEngine(File file) {
init(null, file);
}
public RythmEngine(Properties userConfiguration) {
this((Map) userConfiguration);
}
/**
* Create a rythm engine instance with user supplied configuration data
*
* @param userConfiguration
* @see com.greenlaw110.rythm.conf.RythmConfigurationKey
*/
public RythmEngine(Map userConfiguration) {
init(userConfiguration, null);
}
private Map properties = new HashMap();
/**
* Set user property to the engine. Note The property is not used by the engine program
* it's purely a place for user to add any tags or properties to the engine and later
* get fetched out and use in the user application
*
* @param key
* @param val
*/
public void setProperty(String key, Object val) {
properties.put(key, val);
}
/**
* Get user property by key
* @param key
* @param
* @return the property being cast to type T
*/
public T getProperty(String key) {
return (T) properties.get(key);
}
private Map _loadConfFromDisk(File conf) {
InputStream is = null;
boolean emptyConf = false;
if (null == conf) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (null == cl) cl = Rythm.class.getClassLoader();
is = cl.getResourceAsStream("rythm.conf");
} else {
try {
is = new FileInputStream(conf);
} catch (IOException e) {
if (!emptyConf) logger.warn(e, "Error opening conf file:" + conf);
}
}
if (null != is) {
Properties p = new Properties();
try {
p.load(is);
return p;
} catch (Exception e) {
logger.warn(e, "Error loading rythm.conf");
} finally {
try {
if (null != is) is.close();
} catch (Exception e) {
//ignore
}
}
}
return new HashMap();
}
private void init(Map conf, File file) {
if (null == file || file.isDirectory()) {
_initConf(conf, null);
if (null != file) _conf.setTemplateHome(file);
} else if (file.isFile() && file.canRead()) {
_initConf(conf, file);
}
// post configuration initializations
_mode = _conf.get(RythmConfigurationKey.ENGINE_MODE);
_classes = new TemplateClassManager(this);
_classLoader = new TemplateClassLoader(this);
_classCache = new TemplateClassCache(this);
_resourceManager = new TemplateResourceManager(this);
_extensionManager = new ExtensionManager(this);
int ttl = (Integer) _conf.get(RythmConfigurationKey.DEFAULT_CACHE_TTL);
_cacheService = _conf.get(RythmConfigurationKey.CACHE_SERVICE_IMPL);
_cacheService.setDefaultTTL(ttl);
_cacheService.startup();
// register built-in transformers if enabled
boolean enableBuiltInJavaExtensions = (Boolean) _conf.get(RythmConfigurationKey.BUILT_IN_TRANSFORMER_ENABLED);
if (enableBuiltInJavaExtensions) {
registerTransformer("rythm", "(com.greenlaw110.rythm.utils.S|s\\(\\))", S.class);
}
boolean enableBuiltInTemplateLang = (Boolean) _conf.get(RythmConfigurationKey.BUILT_IN_CODE_TYPE_ENABLED);
if (enableBuiltInTemplateLang) {
ExtensionManager em = extensionManager();
em.registerCodeType(ICodeType.DefImpl.HTML);
em.registerCodeType(ICodeType.DefImpl.JS);
em.registerCodeType(ICodeType.DefImpl.JSON);
em.registerCodeType(ICodeType.DefImpl.CSV);
em.registerCodeType(ICodeType.DefImpl.CSS);
}
_templates.clear();
_templates.put("chain", new JavaTagBase() {
@Override
protected void call(__ParameterList params, __Body body) {
body.render(__getBuffer());
}
});
Object o = _conf.get(RythmConfigurationKey.TRANSFORMER_UDT);
if (null != o) {
List udts = new ArrayList();
if (o.getClass().isArray()) {
int len = Array.getLength(o);
for (int i = 0; i < len; ++i) {
Object e = Array.get(o, i);
if (e instanceof Class) {
udts.add((Class)e);
} else if (null != e) {
String s = e.toString();
try {
udts.add(Class.forName(s));
} catch (ClassNotFoundException ce) {
logger.warn("User defined transformer class not found: %s", s);
}
}
}
} else if (o instanceof List) {
List l = (List) o;
for (Object e : l) {
if (e instanceof Class) {
udts.add((Class)e);
} else if (null != e) {
String s = e.toString();
try {
udts.add(Class.forName(s));
} catch (ClassNotFoundException ce) {
logger.warn("User defined transformer class not found: %s", s);
}
}
}
} else if (o instanceof Class) {
udts.add((Class)o);
} else {
String s = o.toString();
for (String tc: s.split("[, \t]+")){
try {
Class c = Class.forName(tc);
udts.add(c);
} catch (ClassNotFoundException e) {
logger.warn("User defined transformer class not found: %s", tc);
}
}
}
registerTransformer(udts.toArray(new Class[]{}));
}
if (isDevMode()) {
resourceManager().scan(conf().templateHome());
}
logger.debug("Rythm-%s started in %s mode", version, mode());
}
/* -----------------------------------------------------------------------------
Registrations
-------------------------------------------------------------------------------*/
/**
* Register {@link Transformer transformers} using namespace specified in
* the namespace {@link com.greenlaw110.rythm.extension.Transformer#value() value} defined in
* the annotation.
*
* @param transformerClasses
*/
public void registerTransformer(Class>... transformerClasses) {
registerTransformer(null, null, transformerClasses);
}
/**
* Register {@link Transformer transformers} using namespace specified to
* replace the namespace {@link com.greenlaw110.rythm.extension.Transformer#value() value} defined in
* the annotation.
*
* @param transformerClasses
*/
public void registerTransformer(String namespace, String waivePattern, Class>... transformerClasses) {
ExtensionManager jem = extensionManager();
for (Class> extensionClass : transformerClasses) {
Transformer t = extensionClass.getAnnotation(Transformer.class);
boolean classAnnotated = null != t;
String nmsp = namespace;
boolean namespaceIsEmpty = S.empty(namespace);
if (classAnnotated && namespaceIsEmpty) {
nmsp = t.value();
}
String waive = waivePattern;
boolean waiveIsEmpty = S.empty(waive);
if (classAnnotated && waiveIsEmpty) {
waive = t.waivePattern();
}
boolean clsRequireTemplate = null == t ? false : t.requireTemplate();
for (Method m : extensionClass.getDeclaredMethods()) {
int flag = m.getModifiers();
if (!Modifier.isPublic(flag) || !Modifier.isStatic(flag)) continue;
int len = m.getParameterTypes().length;
if (len <= 0) continue;
Transformer tm = m.getAnnotation(Transformer.class);
boolean methodAnnotated = null != tm;
if (!methodAnnotated && !classAnnotated) continue;
String mnmsp = nmsp;
if (methodAnnotated && namespaceIsEmpty) {
mnmsp = tm.value();
if (S.empty(mnmsp)) mnmsp = nmsp;
}
String mwaive = waive;
if (methodAnnotated && waiveIsEmpty) {
mwaive = tm.waivePattern();
if (S.empty(mwaive)) mwaive = waive;
}
String cn = extensionClass.getSimpleName();
if (S.empty(mwaive)) mwaive = cn;
boolean requireTemplate = clsRequireTemplate;
if (null != tm && tm.requireTemplate()) {
requireTemplate = true;
}
String cn0 = extensionClass.getName();
String mn = m.getName();
String fullName = String.format("%s.%s", cn0, mn);
if (S.notEmpty(mnmsp) && !"rythm".equals(mnmsp)) {
mn = mnmsp + "_" + mn;
}
if (len == 1) {
jem.registerJavaExtension(new IJavaExtension.VoidParameterExtension(mwaive, mn, fullName, requireTemplate));
} else {
jem.registerJavaExtension(new IJavaExtension.ParameterExtension(mwaive, mn, ".+", fullName, requireTemplate));
}
}
}
}
/* -----------------------------------------------------------------------------
Rendering methods and APIs
-------------------------------------------------------------------------------*/
private void setRenderArgs(ITemplate t, Object... args) {
if (null == args) {
t.__setRenderArg(0, null);
} else if (1 == args.length) {
Object o0 = args[0];
if (o0 instanceof Map) {
t.__setRenderArgs((Map) args[0]);
} else if (o0 instanceof JSONWrapper) {
t.__setRenderArg((JSONWrapper) o0);
} else {
t.__setRenderArgs(args);
}
} else {
t.__setRenderArgs(args);
}
//if (mode.isDev()) cceCounter.remove();
}
@Deprecated
private void handleCCE(ClassCastException ce) {
// Integer I = cceCounter.get();
// if (null == I) {
// I = 0;
// cceCounter.set(1);
// } else {
// I++;
// cceCounter.set(I);
// }
// if (I > 2) {
// cceCounter.remove();
// throw ce;
// }
// restart(ce);
}
//static ThreadLocal cceCounter = new ThreadLocal();
private ITemplate getTemplate(IDialect dialect, String template, Object... args) {
set(this);
boolean typeInferenceEnabled = conf().typeInferenceEnabled();
if (typeInferenceEnabled) {
ParamTypeInferencer.registerParams(this, args);
}
String key = template;
if (typeInferenceEnabled) {
key += ParamTypeInferencer.uuid();
}
TemplateClass tc = classes().getByTemplate(key);
if (null == tc) {
tc = new TemplateClass(template, this, dialect);
//classes().add(key, tc);
}
ITemplate t = tc.asTemplate();
String fullTagName = resourceManager().getFullTagName(tc);
tc.setFullName(fullTagName);
_templates.put(fullTagName, t);
setRenderArgs(t, args);
// try {
// __setRenderArgs(t, args);
// } catch (ClassCastException ce) {
// if (mode.isDev()) {
// handleCCE(ce);
// return getTemplate(dialect, template, args);
// }
// throw ce;
// }
return t;
}
/**
* Get an new {@link ITemplate template} instance from a String and an array
* of render args. The string parameter could be either a template file path
* or the inline template source content.
*
* When the args array contains only one element and is of {@link java.util.Map} type
* the the render args are passed to template
* {@link ITemplate#__setRenderArgs(java.util.Map) by name},
* otherwise they passes to template instance by position
*
* @param template
* @param args
* @return template instance
*/
@SuppressWarnings("unchecked")
public ITemplate getTemplate(String template, Object... args) {
return getTemplate(null, template, args);
}
/**
* (3rd party API, not for user application)
* Get an new template class by {@link com.greenlaw110.rythm.resource.ITemplateResource template resource}
*
* @param resource the template resource
* @return template class
*/
public TemplateClass getTemplateClass(ITemplateResource resource) {
String key = S.str(resource.getKey());
TemplateClass tc = classes().getByTemplate(key);
if (null == tc) {
tc = new TemplateClass(resource, this);
//classes().add(key, tc);
}
return tc;
}
/**
* Get an new template instance by template source {@link java.io.File file}
* and an array of arguments.
*
* When the args array contains only one element and is of {@link java.util.Map} type
* the the render args are passed to template
* {@link ITemplate#__setRenderArgs(java.util.Map) by name},
* otherwise they passes to template instance by position
*
* @param file the template source file
* @param args the render args. See {@link #getTemplate(String, Object...)}
* @return template instance
*/
@SuppressWarnings("unchecked")
public ITemplate getTemplate(File file, Object... args) {
boolean typeInferenceEnabled = conf().typeInferenceEnabled();
if (typeInferenceEnabled) {
ParamTypeInferencer.registerParams(this, args);
}
String key = S.str(resourceManager().get(file).getKey());
if (typeInferenceEnabled) {
key += ParamTypeInferencer.uuid();
}
TemplateClass tc = classes().getByTemplate(key);
ITemplate t;
if (null == tc) {
tc = new TemplateClass(file, this);
t = tc.asTemplate();
if (null == t) return null;
String fullTagName = resourceManager().getFullTagName(tc);
tc.setFullName(fullTagName);
_templates.put(fullTagName, t);
//classes().add(key, tc);
} else {
t = tc.asTemplate();
}
setRenderArgs(t, args);
return t;
}
/**
* Render template by string parameter and an array of
* template args. The string parameter could be either
* a path point to the template source file, or the inline
* template source content. The render result is returned
* as a String
*
* See {@link #getTemplate(java.io.File, Object...)} for note on
* render args
*
* @param template either the path of template source file or inline template content
* @param args render args array
* @return render result
*/
public String render(String template, Object... args) {
ITemplate t = getTemplate(template, args);
return t.render();
}
/**
* Render template by string parameter and an array of
* template args. The string parameter could be either
* a path point to the template source file, or the inline
* template source content. The render result is output
* to the specified binary output stream
*
* See {@link #getTemplate(java.io.File, Object...)} for note on
* render args
*
* @param os the output stream
* @param template either the path of template source file or inline template content
* @param args render args array
*/
public void render(OutputStream os, String template, Object... args) {
outputMode.set(OutputMode.os);
ITemplate t = getTemplate(template, args);
t.render(os);
}
/**
* Render template by string parameter and an array of
* template args. The string parameter could be either
* a path point to the template source file, or the inline
* template source content. The render result is output
* to the specified character based writer
*
* See {@link #getTemplate(java.io.File, Object...)} for note on
* render args
*
* @param w the writer
* @param template either the path of template source file or inline template content
* @param args render args array
*/
public void render(Writer w, String template, Object... args) {
outputMode.set(OutputMode.writer);
ITemplate t = getTemplate(template, args);
t.render(w);
}
/**
* Render template with source specified by {@link java.io.File file instance}
* and an array of render args. Render result return as a String
*
* See {@link #getTemplate(java.io.File, Object...)} for note on
* render args
*
* @param file the template source file
* @param args render args array
* @return render result
*/
public String render(File file, Object... args) {
ITemplate t = getTemplate(file, args);
return t.render();
}
/**
* Render template with source specified by {@link java.io.File file instance}
* and an array of render args. Render result output into the specified binary
* {@link java.io.OutputStream}
*
* See {@link #getTemplate(java.io.File, Object...)} for note on
* render args
*
* @param os the output stream
* @param file the template source file
* @param args render args array
*/
public void render(OutputStream os, File file, Object... args) {
outputMode.set(OutputMode.os);
ITemplate t = getTemplate(file, args);
t.render(os);
}
/**
* Render template with source specified by {@link java.io.File file instance}
* and an array of render args. Render result output into the specified binary
* {@link java.io.Writer}
*
* See {@link #getTemplate(java.io.File, Object...)} for note on
* render args
*
* @param w the writer
* @param file the template source file
* @param args render args array
*/
public void render(Writer w, File file, Object... args) {
outputMode.set(OutputMode.writer);
ITemplate t = getTemplate(file, args);
t.render(w);
}
/**
* Render template by string typed inline template content and an array of
* template args. The render result is returned as a String
*
* See {@link #getTemplate(java.io.File, Object...)} for note on
* render args
*
* @param template the inline template content
* @param args the render args array
* @return render result
*/
public String renderStr(String template, Object... args) {
return renderString(template, args);
}
/**
* Alias of {@link #renderString(String, Object...)}
*
* See {@link #getTemplate(java.io.File, Object...)} for note on
* render args
*
* @param template the inline template content
* @param args the render args array
* @return render result
*/
@SuppressWarnings("unchecked")
public String renderString(String template, Object... args) {
boolean typeInferenceEnabled = conf().typeInferenceEnabled();
if (typeInferenceEnabled) {
ParamTypeInferencer.registerParams(this, args);
}
String key = template;
if (typeInferenceEnabled) {
key += ParamTypeInferencer.uuid();
}
TemplateClass tc = classes().getByTemplate(key, false);
if (null == tc) {
tc = new TemplateClass(new StringTemplateResource(template), this);
//classes().add(key, tc);
}
ITemplate t = tc.asTemplate();
setRenderArgs(t, args);
return t.render();
}
/**
* Render template in substitute mode by string typed template source
* and an array of render args. The string parameter could be either
* a path point to the template source file, or the inline
* template source content. Return render result as String
*
* See {@link #getTemplate(java.io.File, Object...)} for note on
* render args
*
* @param template either the template source file path or the inline template content
* @param args the render args array
* @return render result
*/
public String substitute(String template, Object... args) {
ITemplate t = getTemplate(BasicRythm.INSTANCE, template, args);
return t.render();
}
/**
* Render template in substitute mode by File typed template source
* and an array of render args. Return render result as String
*
* See {@link #getTemplate(java.io.File, Object...)} for note on
* render args
*
* @param file the template source file
* @param args the render args array
* @return render result
*/
public String substitute(File file, Object... args) {
ITemplate t = getTemplate(file, args, BasicRythm.INSTANCE);
return t.render();
}
/**
* Render template in ToString mode by string typed template source
* and one object instance which state is to be output.
* The string parameter could be either a path point to the template
* source file, or the inline template source content. Return render
* result as String
*
* @param template either the template source file path or the inline template content
* @param obj the object instance which state is to be output as a string
* @return render result
*/
public String toString(String template, Object obj) {
Class argClass = obj.getClass();
String clsName = argClass.getName();
if (clsName.matches(".*\\$[0-9].*")) {
argClass = obj.getClass().getSuperclass();
}
String key = template + argClass;
TemplateClass tc = classes().getByTemplate(key);
if (null == tc) {
tc = new TemplateClass(template, this, new ToString(argClass));
//classes().add(key, tc);
}
ITemplate t = tc.asTemplate();
t.__setRenderArg(0, obj);
return t.render();
}
/**
* Render template in AutoToString mode by one object instance which state is
* to be output. Return render result as String
*
* @param obj the object instance which state is to be output as a string
* @return render result
*/
public String toString(Object obj) {
return toString(obj, ToStringOption.DEFAULT_OPTION, (ToStringStyle) null);
}
/**
* Render template in AutoToString mode by one object instance which state is
* to be output and {@link ToStringOption option} and {@link ToStringStyle stype}.
* Return render result as String
*
* @param obj the object instance which state is to be output as a string
* @param option the output option
* @param style the output style
* @return render result
*/
public String toString(Object obj, ToStringOption option, ToStringStyle style) {
Class> c = obj.getClass();
AutoToString.AutoToStringData key = new AutoToString.AutoToStringData(c, option, style);
//String template = AutoToString.templateStr(c, option, style);
TemplateClass tc = classes().getByTemplate(key);
if (null == tc) {
tc = new TemplateClass(new ToStringTemplateResource(key), this, new AutoToString(c, key));
//classes().add(key, tc);
}
ITemplate t = tc.asTemplate();
t.__setRenderArg(0, obj);
return t.render();
}
/**
* Render template in AutoToString mode by one object instance which state is
* to be output and {@link ToStringOption option} and
* {@link org.apache.commons.lang3.builder.ToStringStyle apache commons to string stype}.
* Return render result as String
*
* @param obj the object instance which state is to be output as a string
* @param option the output option
* @param style the output style specified as apache commons ToStringStyle
* @return render result
*/
public String commonsToString(Object obj, ToStringOption option, org.apache.commons.lang3.builder.ToStringStyle style) {
return toString(obj, option, ToStringStyle.fromApacheStyle(style));
}
private Set nonExistsTemplates = new HashSet();
private class NonExistsTemplatesChecker {
boolean started = false;
private ScheduledExecutorService scheduler = new ScheduledThreadPoolExecutor(1);
NonExistsTemplatesChecker() {
scheduler.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
List toBeRemoved = new ArrayList();
for (String template : nonExistsTemplates) {
ITemplateResource rsrc = resourceManager().getResource(template);
if (rsrc.isValid()) {
toBeRemoved.add(template);
}
}
nonExistsTemplates.removeAll(toBeRemoved);
toBeRemoved.clear();
TemplateClass tc = classes().all().get(0);
for (String tag : _nonExistsTags) {
if (null != resourceManager().tryLoadTemplate(tag, tc)) {
toBeRemoved.add(tag);
}
}
_nonExistsTags.removeAll(toBeRemoved);
toBeRemoved.clear();
}
}, 0, 1000 * 10, TimeUnit.MILLISECONDS);
}
}
private NonExistsTemplatesChecker nonExistsTemplatesChecker = null;
/**
* Render template if specified template exists, otherwise return empty string
*
* @param template the template source path
* @param args render args. See {@link #getTemplate(String, Object...)}
* @return render result
*/
public String renderIfTemplateExists(String template, Object... args) {
boolean typeInferenceEnabled = conf().typeInferenceEnabled();
if (typeInferenceEnabled) {
ParamTypeInferencer.registerParams(this, args);
}
if (nonExistsTemplates.contains(template)) return "";
String key = template;
if (typeInferenceEnabled) {
key += ParamTypeInferencer.uuid();
}
TemplateClass tc = classes().getByTemplate(template);
if (null == tc) {
ITemplateResource rsrc = resourceManager().getResource(template);
if (rsrc.isValid()) {
tc = new TemplateClass(rsrc, this);
//classes().add(key, tc);
} else {
nonExistsTemplates.add(template);
if (mode().isDev() && nonExistsTemplatesChecker == null) {
nonExistsTemplatesChecker = new NonExistsTemplatesChecker();
}
return "";
}
}
ITemplate t = tc.asTemplate();
setRenderArgs(t, args);
return t.render();
}
/* -----------------------------------------------------------------------------
Eval
-------------------------------------------------------------------------------*/
/**
* Evaluate a script and return executing result. Note the API is not mature yet
* don't use it in your application
*
* @param script
* @return the result
*/
public Object eval(String script) {
// use Java's ScriptEngine at the moment
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine jsEngine = manager.getEngineByName("JavaScript");
try {
return jsEngine.eval(script);
} catch (ScriptException e) {
throw new RuntimeException(e);
}
}
/* -----------------------------------------------------------------------------
Tags
-------------------------------------------------------------------------------*/
private final Map _templates = new HashMap();
private final Set _nonTmpls = new HashSet();
/**
* Whether a {@link ITemplate template} is registered to the engine by name specified
*
* Not an API for user application
*
* @param tmplName
* @return true if there is a template with the name specified
*/
public boolean templateRegistered(String tmplName) {
return _templates.containsKey(tmplName);
}
/**
* Get a {@link ITemplate template} registered to the engine by name
*
* Not an API for user application
*
* @param tmplName
* @return the template instance
*/
public ITemplate getRegisteredTemplate(String tmplName) {
return _templates.get(tmplName);
}
/**
* Return {@link TemplateClass} from a tag name
*
* Not an API for user application
*
* @param name
* @return template class
*/
public TemplateClass getRegisteredTemplateClass(String name) {
TemplateBase tmpl = (TemplateBase) _templates.get(name);
if (null == tmpl) return null;
return tmpl.__getTemplateClass(false);
}
/**
* Register a template class and return self
*
* @param tc
* @return this engine instance
*/
public RythmEngine registerTemplateClass(TemplateClass tc) {
String fullName = resourceManager().getFullTagName(tc);
tc.setFullName(fullName);
classes().add(tc);
return this;
}
/**
* Check if a template exists and return it's name
*
* Not an API for user application
*
* @param name
* @param callerClass
* @return template name
*/
public String testTemplate(String name, TemplateClass callerClass) {
if (Keyword.THIS.toString().equals(name)) {
return resourceManager().getFullTagName(callerClass);
}
if (mode().isProd() && _nonTmpls.contains(name)) return null;
if (templateRegistered(name)) return name;
// try imported path
if (null != callerClass.importPaths) {
for (String s : callerClass.importPaths) {
String name0 = s + "." + name;
if (_templates.containsKey(name0)) return name0;
}
}
// try relative path
// TODO: handle logic error here, caller foo.bar.html, tag: zee
// then should try foo.zee.html, if tag is zee.js, then should try foo.zee.js
String callerName = resourceManager().getFullTagName(callerClass);
int pos = callerName.lastIndexOf(".");
if (-1 != pos) {
String s = callerName.substring(0, pos);
String name0 = s + "." + name;
if (_templates.containsKey(name0)) return name0;
pos = s.lastIndexOf(".");
if (-1 != pos){
s = callerName.substring(0, pos);
name0 = s + "." + name;
if (_templates.containsKey(name0)) return name0;
}
}
try {
// try to ask resource manager
TemplateClass tc = resourceManager().tryLoadTemplate(name, callerClass);
if (null == tc) {
if (mode().isProd()) _nonTmpls.add(name);
return null;
}
String fullName = tc.getFullName();
return fullName;
} catch (TagLoadException e) {
throw e;
} catch (RythmException e) {
throw e;
} catch (Exception e) {
logger.error(e, "error trying load tag[%s]", name);
// see if the
}
return null;
}
/**
* Register a template. If there is name collision then registration
* will fail
*
* Not an API for user application
*
* @return true if registration success
*/
public boolean registerTemplate(ITemplate template) {
//TemplateResourceManager rm = resourceManager();
String name;
if (template instanceof JavaTagBase) {
name = template.__getName();
} else {
name = template.__getTemplateClass(false).getFullName();
}
return registerTemplate(name, template);
}
/**
* Register a tag using the given name
*
* Not an API for user application
*
* @param name
* @param template
* @return false if registration failed
*/
public boolean registerTemplate(String name, ITemplate template) {
if (null == template) throw new NullPointerException();
if (_templates.containsKey(name)) {
return false;
}
_templates.put(name, template);
logger.trace("tag %s registered", name);
return true;
}
/**
* Invoke a template
*
* Not an API for user application
*
* @param line
* @param name
* @param caller
* @param params
* @param body
* @param context
*/
public void invokeTemplate(int line, String name, ITemplate caller, ITag.__ParameterList params, ITag.__Body body, ITag.__Body context) {
invokeTemplate(line, name, caller, params, body, context, false);
}
private Set _nonExistsTags = new HashSet();
/**
* Invoke a tag
*
* Not an API for user application
*
* @param line
* @param name
* @param caller
* @param params
* @param body
* @param context
* @param ignoreNonExistsTag
*/
public void invokeTemplate(int line, String name, ITemplate caller, ITag.__ParameterList params, ITag.__Body body, ITag.__Body context, boolean ignoreNonExistsTag) {
if (_nonExistsTags.contains(name)) return;
// try tag registry first
ITemplate t = _templates.get(name);
TemplateClass tc = caller.__getTemplateClass(true);
if (null == t) {
// is calling self
if (S.isEqual(name, caller.__getName())) t = caller;
}
if (null == t) {
// try imported path
if (null != tc.importPaths) {
for (String s : tc.importPaths) {
String name0 = s + "." + name;
t = _templates.get(name0);
if (null != t) break;
}
}
// try relative path
if (null == t) {
String callerName = resourceManager().getFullTagName(tc);
int pos = callerName.lastIndexOf(".");
if (-1 != pos) {
String name0 = callerName.substring(0, pos) + "." + name;
t = _templates.get(name0);
}
}
// try load the tag from resource
if (null == t) {
tc = resourceManager().tryLoadTemplate(name, tc);
if (null != tc) t = _templates.get(tc.getFullName());
if (null == t) {
if (ignoreNonExistsTag) {
if (logger.isDebugEnabled()) {
logger.debug("cannot find tag: " + name);
}
_nonExistsTags.add(name);
if (mode().isDev() && nonExistsTemplatesChecker == null) {
nonExistsTemplatesChecker = new NonExistsTemplatesChecker();
}
return;
} else {
throw new NullPointerException("cannot find tag: " + name);
}
}
t = t.__cloneMe(this, caller);
}
}
if (!(t instanceof JavaTagBase)) {
// try refresh the tag loaded from template file under tag root
// note Java source tags are not reloaded here
String cn = t.getClass().getName();
TemplateClass tc0 = classes().getByClassName(cn);
if (null == tc0) {
System.out.println(t.getClass());
System.out.println(name);
System.out.println(cn);
System.out.println(caller.getClass());
}
t = tc0.asTemplate(caller);
} else {
t = t.__cloneMe(this, caller);
}
if (null != params) {
if (t instanceof JavaTagBase) {
((JavaTagBase) t).__setRenderArgs0(params);
} else {
for (int i = 0; i < params.size(); ++i) {
ITag.__Parameter param = params.get(i);
if (null != param.name) t.__setRenderArg(param.name, param.value);
else t.__setRenderArg(i, param.value);
}
}
}
if (null == body && null != params) {
body = (ITag.__Body)params.getByName("__body");
if (null == body) {
body = (ITag.__Body)params.getByName("_body");
}
}
if (null != body) {
t.__setRenderArg("__body", body);
t.__setRenderArg("_body", body); // for compatiblity
}
RythmEvents.ON_TAG_INVOCATION.trigger(this, F.T2((TemplateBase) caller, t));
try {
if (null != context) {
t.__setBodyContext(context);
}
t.__call(line);
} finally {
RythmEvents.TAG_INVOKED.trigger(this, F.T2((TemplateBase) caller, t));
}
}
// -- cache api
/**
* Cache object using key and args for ttl seconds
*
* Not an API for user application
*
* @param key
* @param o
* @param ttl if zero then defaultTTL used, if negative then never expire
* @param args
*/
public void cache(String key, Object o, int ttl, Object... args) {
if (conf().cacheDisabled()) return;
ICacheService cacheService = _cacheService;
Serializable value = null == o ? "" : (o instanceof Serializable ? (Serializable) o : o.toString());
if (args.length > 0) {
StringBuilder sb = new StringBuilder(key);
for (Object arg : args) {
sb.append("-").append(arg);
}
key = sb.toString();
}
cacheService.put(key, value, ttl);
}
/**
* Store object o into cache service with ttl equals to duration specified.
*
* The duration is a string to be parsed by @{link #durationParser}
*
* The object o is associated with given key and a list of argument values
*
* Not an API for user application
*
* @param key
* @param o
* @param duration
* @param args
*/
public void cache(String key, Object o, String duration, Object... args) {
if (conf().cacheDisabled()) return;
IDurationParser dp = conf().durationParser();
int ttl = null == duration ? 0 : dp.parseDuration(duration);
cache(key, o, ttl, args);
}
/**
* Get cached value using key and a list of argument values
*
* Not an API for user application
*
* @param key
* @param args
* @return cached item
*/
public Serializable cached(String key, Object... args) {
if (conf().cacheDisabled()) return null;
ICacheService cacheService = _cacheService;
if (args.length > 0) {
StringBuilder sb = new StringBuilder(key);
for (Object arg : args) {
sb.append("-").append(arg);
}
key = sb.toString();
}
return cacheService.get(key);
}
// -- SPI interface
// -- issue #47
private Map> extendMap = new HashMap>();
/**
* Not an API for user application
*
* @param parent
* @param child
*/
public void addExtendRelationship(TemplateClass parent, TemplateClass child) {
if (mode().isProd()) return;
Set children = extendMap.get(parent);
if (null == children) {
children = new HashSet();
extendMap.put(parent, children);
}
children.add(child);
}
/**
* Not an API for user application
*
* @param parent
*/
// called to invalidate all template class which extends the parent
public void invalidate(TemplateClass parent) {
if (mode().isProd()) return;
Set children = extendMap.get(parent);
if (null == children) return;
for (TemplateClass child : children) {
invalidate(child);
child.reset();
}
}
// -- Sandbox
private SandboxExecutingService _secureExecutor = null;
private SandboxExecutingService secureExecutor() {
if (null == _secureExecutor) {
int poolSize = (Integer) conf().get(RythmConfigurationKey.SANDBOX_POOL_SIZE);
SecurityManager sm = conf().get(RythmConfigurationKey.SANDBOX_SECURITY_MANAGER_IMPL);
int timeout = (Integer) conf().get(RythmConfigurationKey.SANDBOX_TIMEOUT);
SandboxThreadFactory fact = conf().get(RythmConfigurationKey.SANBOX_THREAD_FACTORY_IMPL);
if (null == fact) fact = new SandboxThreadFactory(sm, null, this);
_secureExecutor = new SandboxExecutingService(poolSize, fact, timeout, this);
}
return _secureExecutor;
}
/**
* Create a {@link Sandbox} instance to render the template
*
* @return an new sandbox instance
*/
public Sandbox sandbox() {
return new Sandbox(this, secureExecutor());
}
/**
* Create a {@link Sandbox} instance with user supplied context
*
* @param context
* @return an new sandbox instance
*/
public Sandbox sandbox(Map context) {
return new Sandbox(this, secureExecutor()).setUserContext(context);
}
// dispatch rythm events
private IEventDispatcher eventDispatcher = null;
private IEventDispatcher eventDispatcher() {
if (null == eventDispatcher) {
eventDispatcher = new EventBus(this);
}
return eventDispatcher;
}
/**
* Not an API for user application
*
* @param event
* @param param
* @return event handler process result
*/
@Override
public Object accept(IEvent event, Object param) {
return eventDispatcher().accept(event, param);
}
/* -----------------------------------------------------------------------------
Output Mode
-------------------------------------------------------------------------------*/
/**
* Defines the output method for render result.
*
* os: output to binary {@link java.io.OutputStream}
* writer: output to character based {@link java.io.Writer}
* str: return render result as a {@link java.lang.String}
*
*
* This is not an API used in user application
*/
public static enum OutputMode {
os, writer, str {
@Override
public boolean writeOutput() {
return false;
}
};
/**
* Return true if the current output mode is to output to {@link java.io.OutputStream}
* or {@link java.io.Writer}
*
* @return true if output mode is not return string
*/
public boolean writeOutput() {
return true;
}
}
private final static InheritableThreadLocal outputMode = new InheritableThreadLocal() {
@Override
protected OutputMode initialValue() {
return OutputMode.str;
}
};
/**
* Return current {@link OutputMode}. Not to be used in user application
*
* @return output mode
*/
public static OutputMode outputMode() {
return outputMode.get();
}
/* -----------------------------------------------------------------------------
Restart and Shutdown
-------------------------------------------------------------------------------*/
/**
* Restart the engine with an exception as the cause.
* Note , this is not supposed to be called by user application
*
* @param cause
*/
public void restart(RuntimeException cause) {
if (isProdMode()) throw cause;
if (!(cause instanceof ClassReloadException)) {
String msg = cause.getMessage();
if (cause instanceof RythmException) {
RythmException re = (RythmException) cause;
msg = re.getSimpleMessage();
}
logger.warn("restarting rythm engine due to %s", msg);
}
restart();
}
private void restart() {
if (isProdMode()) return;
_classLoader = new TemplateClassLoader(this);
// clear all template tags which is managed by TemplateClassManager
List templateTags = new ArrayList();
for (String name : _templates.keySet()) {
ITag tag = _templates.get(name);
if (!(tag instanceof JavaTagBase)) {
templateTags.add(name);
}
}
for (String name : templateTags) {
_templates.remove(name);
}
}
interface IShutdownListener {
void onShutdown();
}
private IShutdownListener shutdownListener = null;
void setShutdownListener(IShutdownListener listener) {
this.shutdownListener = listener;
}
/**
* Shutdown this rythm engine
*/
public void shutdown() {
if (null != _cacheService) {
try {
_cacheService.shutdown();
} catch (Exception e) {
logger.error(e, "Error shutdown cache service");
}
}
if (null != _secureExecutor) {
try {
_secureExecutor.shutdown();
} catch (Exception e) {
logger.error(e, "Error shutdown secure executor");
}
}
if (null != shutdownListener) {
try {
shutdownListener.onShutdown();
} catch (Exception e) {
logger.error(e, "Error execute shutdown listener");
}
}
if (null != _templates) _templates.clear();
if (null != _classes) _classes.clear();
if (null != _nonExistsTags) _nonExistsTags.clear();
if (null != _nonTmpls) _nonTmpls.clear();
}
}