com.greenlaw110.rythm.template.TemplateBase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rythm-engine Show documentation
Show all versions of rythm-engine Show documentation
A strong typed high performance Java Template engine with .Net Razor like syntax
The newest version!
/*
* 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.template;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.greenlaw110.rythm.Rythm;
import com.greenlaw110.rythm.RythmEngine;
import com.greenlaw110.rythm.conf.RythmConfiguration;
import com.greenlaw110.rythm.exception.FastRuntimeException;
import com.greenlaw110.rythm.exception.RythmException;
import com.greenlaw110.rythm.extension.ICodeType;
import com.greenlaw110.rythm.extension.II18nMessageResolver;
import com.greenlaw110.rythm.internal.IEvent;
import com.greenlaw110.rythm.internal.RythmEvents;
import com.greenlaw110.rythm.internal.TemplateBuilder;
import com.greenlaw110.rythm.internal.compiler.ClassReloadException;
import com.greenlaw110.rythm.internal.compiler.TemplateClass;
import com.greenlaw110.rythm.logger.ILogger;
import com.greenlaw110.rythm.logger.Logger;
import com.greenlaw110.rythm.utils.*;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Modifier;
import java.util.*;
/**
* The base class of template implementation. It provides a set of
* protected methods which is handy to use in template authoring
*/
public abstract class TemplateBase extends TemplateBuilder implements ITemplate {
/**
* The logger
*/
protected static final ILogger __logger = Logger.get(TemplateBase.class);
/**
* The rythm engine that run this template
*/
protected transient RythmEngine __engine = null;
/**
* The template class
*/
private transient TemplateClass __templateClass = null;
/**
* Set template class and template code type to this template instance
*
* Not to be called in user application or template
*
* @param templateClass
*/
public void __setTemplateClass(TemplateClass templateClass) {
this.__templateClass = templateClass;
}
public void __prepareRender(ICodeType type, Locale locale) {
__ctx.init(this, type, locale);
Class extends TemplateBase> c = getClass();
Class> pc = c.getSuperclass();
if (TemplateBase.class.isAssignableFrom(pc) && !Modifier.isAbstract(pc.getModifiers())) {
try {
TemplateClass ptc = __engine().classes().getByClassName(pc.getName());
if (null != ptc) {
__parent = (TemplateBase) ptc.asTemplate();
} else {
throw new RuntimeException("Cannot find template class for parent class: " + pc);
}
//__parent.__setTemplateClass(__engine().classes.getByClassName(pc.getName()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
if (null != __parent) {
__parent.__ctx.init(__parent, type, locale);
}
}
/**
* Return {@link com.greenlaw110.rythm.utils.S#INSTANCE String helper instance}. Could be used in
* template authoring. For example:
*
*
* {@literal @}if (s().empty(userRight)){
* {@literal @}return
* }
*
*/
protected S s() {
return S.INSTANCE;
}
private Writer w;
private OutputStream os;
@Override
public ITemplate __setWriter(Writer writer) {
if (null == writer) throw new NullPointerException();
if (null != os) throw new IllegalStateException("Cannot set writer to template when outputstream is presented");
if (null != this.w)
throw new IllegalStateException("Cannot set writer to template when an writer is presented");
this.w = writer;
return this;
}
@Override
public ITemplate __setOutputStream(OutputStream os) {
if (null == os) throw new NullPointerException();
if (null != w) throw new IllegalStateException("Cannot set output stream to template when writer is presented");
if (null != this.os)
throw new IllegalStateException("Cannot set output stream to template when an outputstream is presented");
this.os = os;
return this;
}
/**
* Stores render args of this template. The generated template source code
* will also declare render args as separate protected field while keeping
* a copy inside this Map data structure
*/
protected Map __renderArgs = new HashMap();
/**
* Return the {@link RythmEngine engine} running this template
*
* @return the engine running the template
*/
public RythmEngine __engine() {
return null == __engine ? Rythm.engine() : __engine;
}
/**
* Invoke a tag. Usually should not used directly in user template
*
* @param line
* @param name
*/
protected void __invokeTag(int line, String name) {
__engine.invokeTemplate(line, name, this, null, null, null);
}
/**
* Invoke a tag. Usually should not used directly in user template
*
* @param line
* @param name
* @param ignoreNonExistsTag
*/
protected void __invokeTag(int line, String name, boolean ignoreNonExistsTag) {
__engine.invokeTemplate(line, name, this, null, null, null, ignoreNonExistsTag);
}
/**
* Invoke a tag. Usually should not used directly in user template
*
* @param line
* @param name
* @param params
*/
protected void __invokeTag(int line, String name, ITag.__ParameterList params) {
__engine.invokeTemplate(line, name, this, params, null, null);
}
/**
* Invoke a tag. Usually should not used directly in user template
*
* @param line
* @param name
* @param params
* @param ignoreNonExistsTag
*/
protected void __invokeTag(int line, String name, ITag.__ParameterList params, boolean ignoreNonExistsTag) {
__engine.invokeTemplate(line, name, this, params, null, null, ignoreNonExistsTag);
}
/**
* Invoke a tag. Usually should not used directly in user template
*
* @param line
* @param name
* @param params
* @param body
*/
protected void __invokeTag(int line, String name, ITag.__ParameterList params, ITag.__Body body) {
__engine.invokeTemplate(line, name, this, params, body, null);
}
/**
* Invoke a tag. Usually should not used directly in user template
*
* @param line
* @param name
* @param params
* @param body
* @param ignoreNoExistsTag
*/
protected void __invokeTag(int line, String name, ITag.__ParameterList params, ITag.__Body body, boolean ignoreNoExistsTag) {
__engine.invokeTemplate(line, name, this, params, body, null, ignoreNoExistsTag);
}
/**
* Invoke a tag. Usually should not used directly in user template
*
* @param line
* @param name
* @param params
* @param body
* @param context
*/
protected void __invokeTag(int line, String name, ITag.__ParameterList params, ITag.__Body body, ITag.__Body context) {
__engine.invokeTemplate(line, name, this, params, body, context);
}
/**
* Invoke a tag. Usually should not used directly in user template
*
* @param line
* @param name
* @param params
* @param body
* @param context
* @param ignoreNonExistsTag
*/
protected void __invokeTag(int line, String name, ITag.__ParameterList params, ITag.__Body body, ITag.__Body context, boolean ignoreNonExistsTag) {
__engine.invokeTemplate(line, name, this, params, body, context, ignoreNonExistsTag);
}
/* to be used by dynamic generated sub classes */
private String layoutContent = "";
private Map layoutSections = new HashMap();
private Map renderProperties = new HashMap();
/**
* The parent template (layout template)
*/
protected TemplateBase __parent = null;
/**
* Construct a template instance
*/
public TemplateBase() {
super();
}
/**
* Render another template from this template. Could be used in template authoring.
* For example:
*
*
* {@literal @}args String customTemplate, Map customParams
* {@literal @}{ Object renderResult = render(customTemplate, customParams);
* }
* @renderResult
*
*
* @param template
* @param args
* @return render result
*/
protected RawData __render(String template, Object... args) {
if (null == template) return new RawData("");
return S.raw(__engine.sandbox().render(template, args));
}
/**
* Render another template from within this template. Using the renderArgs
* of this template.
*
* @param template
* @return render result as {@link com.greenlaw110.rythm.utils.RawData}
* @see #__render(String, Object...)
*/
protected RawData __render(String template) {
if (null == template) return new RawData("");
return S.raw(__engine.sandbox().render(template, __renderArgs));
}
/**
* Set layout content. Should not be used in user application or template
*
* @param body
*/
protected final void __setLayoutContent(String body) {
layoutContent = body;
}
/**
* Add layout section. Should not be used in user application or template
*
* @param name
* @param section
*/
private void __addLayoutSection(String name, String section) {
if (layoutSections.containsKey(name)) return;
layoutSections.put(name, section);
}
private StringBuilder tmpOut = null;
private String section = null;
private TextBuilder tmpCaller = null;
/**
* Start a layout section. Not to be used in user application or template
*
* @param name
*/
protected void __startSection(String name) {
if (null == name) throw new NullPointerException("section name cannot be null");
if (null != tmpOut) throw new IllegalStateException("section cannot be nested");
tmpCaller = __caller;
__caller = null;
tmpOut = __buffer;
__buffer = new StringBuilder();
section = name;
}
/**
* End a layout section. Not to be used in user application or template
*/
protected void __endSection() {
__endSection(false);
}
/**
* End a layout section with a boolean flag mark if it is a default content or not.
* Not to be used in user application or template
*
* @param def
*/
protected void __endSection(boolean def) {
if (null == tmpOut && null == tmpCaller) throw new IllegalStateException("section has not been started");
__addLayoutSection(section, __buffer.toString());
__buffer = tmpOut;
__caller = tmpCaller;
tmpOut = null;
tmpCaller = null;
}
/**
* Print a layout section by name. Not to be used in user application or template
*
* @param name
*/
protected void __pLayoutSection(String name) {
p(layoutSections.get(name));
}
/**
* Get a section content as {@link com.greenlaw110.rythm.utils.RawData} by name. Not to be used in user application or template
*
* @param name
* @return section data by name
*/
protected RawData __getSection(String name) {
return S.raw(layoutSections.get(name));
}
/**
* Get layout content as {@link com.greenlaw110.rythm.utils.RawData}. Not to be used in user application or template
*
* @return layout content
*/
protected RawData __getSection() {
return S.raw(S.isEmpty(layoutContent) ? layoutSections.get("__CONTENT__") : layoutContent);
}
/**
* Print the layout content. Not to be used in user application or template
*/
protected void __pLayoutContent() {
p(__getSection());
}
private void addAllLayoutSections(Map sections) {
if (null != sections) layoutSections.putAll(sections);
}
private void addAllRenderProperties(Map properties) {
if (null != properties) renderProperties.putAll(properties);
}
/**
* Not to be used in user application or template
*
* @return a TemplateBase
*/
protected TemplateBase __internalClone() {
try {
return (TemplateBase) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException();
}
}
/**
* Not to be used in user application or template
*
* @param engine the rythm engine
* @param caller the caller template
* @return cloned template
*/
@Override
public ITemplate __cloneMe(RythmEngine engine, ITemplate caller) {
if (null == engine) throw new NullPointerException();
TemplateBase tmpl = __internalClone();
if (tmpl.__parent != null) {
tmpl.__parent = (TemplateBase) tmpl.__parent.__cloneMe(engine, caller);
}
tmpl.__engine = engine;
tmpl.__templateClass = __templateClass;
tmpl.__ctx = new __Context(__ctx);
//if (null != buffer) tmpl.__buffer = buffer;
if (null != __buffer) tmpl.__buffer = new StringBuilder();
tmpl.__renderArgs = new HashMap(__renderArgs.size());
tmpl.layoutContent = "";
tmpl.layoutSections = new HashMap();
tmpl.renderProperties = new HashMap();
tmpl.section = null;
tmpl.tmpCaller = null;
tmpl.tmpOut = null;
tmpl.__logTime = __logTime;
tmpl.w = null;
tmpl.os = null;
if (null != caller) {
tmpl.__caller = (TextBuilder) caller;
Map callerRenderArgs = new HashMap(((TemplateBase)caller).__renderArgs);
Map types = tmpl.__renderArgTypeMap();
for (String s: callerRenderArgs.keySet()) {
if (tmpl.__renderArgs.containsKey(s)) continue;
Object o = callerRenderArgs.get(s);
if (null == o) continue;
Class> c = types.get(s);
if (null == c || c.isAssignableFrom(o.getClass())) {
tmpl.__setRenderArg(s, o);
}
}
}
tmpl.__setUserContext(engine.renderSettings.userContext());
return tmpl;
}
/**
* Not to be used in user application or template
*/
protected void __internalInit() {
__loadExtendingArgs();
__init();
}
/**
* the implementation of this method is to be generated by {@link com.greenlaw110.rythm.internal.CodeBuilder}.
* Not to be used in user application or template
*/
protected void __setup() {
}
/**
* the implementation of this method is to be generated by {@link com.greenlaw110.rythm.internal.CodeBuilder}.
* Not to be used in user application or template
*/
protected void __loadExtendingArgs() {
}
/**
* the implementation of this method is to be generated by {@link com.greenlaw110.rythm.internal.CodeBuilder}.
* Not to be used in user application or template
*/
@Override
public void __init() {
}
private boolean __logTime() {
return __logger.isDebugEnabled() && (__logTime || __engine.conf().logRenderTime());
}
/**
* Get the template class of this template. Not to be used in user application or template
*
* @param useCaller
* @return a TemplateClass
*/
public TemplateClass __getTemplateClass(boolean useCaller) {
TemplateClass tc = __templateClass;
if (useCaller && null == tc) {
TemplateBase caller = __caller();
if (null != caller) return caller.__getTemplateClass(true);
}
return tc;
}
/**
* Render to binary output stream. This method is usually called from API defined in
* {@link RythmEngine}
*
* @param os
*/
@Override
public final void render(OutputStream os) {
__setOutputStream(os);
render();
}
/**
* Render to character based writer. This method is usually called from API defined in
* {@link RythmEngine}
*
* @param w
*/
@Override
public final void render(Writer w) {
__setWriter(w);
render();
}
/**
* Trigger render events.
* Not an API for user application
* @param event
* @param engine
*/
protected void __triggerRenderEvent(IEvent event, RythmEngine engine) {
event.trigger(engine, this);
}
/**
* Render and return result in String. This method is usually called from API defined in
* {@link RythmEngine}
*/
@Override
public final String render() {
RythmEngine engine = __engine();
RythmEngine.set(engine);
try {
long l = 0l;
if (__logTime()) {
l = System.currentTimeMillis();
}
__triggerRenderEvent(RythmEvents.ON_RENDER, engine);
__setup();
if (__logTime()) {
__logger.debug("< preprocess [%s]: %sms", getClass().getName(), System.currentTimeMillis() - l);
l = System.currentTimeMillis();
}
String s = __internalRender();
__triggerRenderEvent(RythmEvents.RENDERED, engine);
if (__logTime()) {
__logger.debug("<<<<<<<<<<<< [%s] total render: %sms", getClass().getName(), System.currentTimeMillis() - l);
}
return s;
} catch (ClassReloadException e) {
if (__logger.isDebugEnabled()) {
__logger.debug("Cannot hotswap class, try to restart engine...");
}
engine.restart(e);
return render();
}
// catch (ClassCastException e) {
// if (__logger.isDebugEnabled()) {
// __logger.debug("ClassCastException found, force refresh template and try again...");
// }
// engine.restart(e);
// TemplateClass tc = engine.classes().getByClassName(getClass().getName());
// tc.refresh(true);
// ITemplate t = tc.asTemplate(__ctx.currentCodeType());
// t.__setRenderArgs(__renderArgs);
// return t.render();
// }
}
private Writer w_ = null;
/**
* Set output file path
*
* @param path
*/
protected void __setOutput(String path) {
try {
w_ = new BufferedWriter(new FileWriter(path));
} catch (Exception e) {
throw new FastRuntimeException(e.getMessage());
}
}
/**
* Set output file
*
* @param file
*/
protected void __setOutput(File file) {
try {
w_ = new BufferedWriter(new FileWriter(file));
} catch (Exception e) {
throw new FastRuntimeException(e.getMessage());
}
}
/**
* Set output stream
*
* @param os
*/
protected void __setOutput(OutputStream os) {
w_ = new OutputStreamWriter(os);
}
/**
* Set output writer
*
* @param w
*/
protected void __setOutput(Writer w) {
w_ = w;
}
private static final ThreadLocal cce_ = new ThreadLocal(){
@Override
protected Boolean initialValue() {
return false;
}
};
/**
* Not to be used in user application or template
*/
protected void __internalBuild() {
w_ = null; // reset output destination
RythmEngine engine = __engine();
//if (!(engine.recordTemplateSourceOnError || engine.recordJavaSourceOnError)) {
if (false) {
__internalInit();
build();
} else {
// try {
try {
long l = 0l;
if (__logTime()) {
l = System.currentTimeMillis();
}
__internalInit();
build();
if (__logTime()) {
__logger.debug("<<<<<<<<<<<< [%s] build: %sms", getClass().getName(), System.currentTimeMillis() - l);
}
} catch (RythmException e) {
throw e;
} catch (Throwable e) {
// if (engine.isDevMode() && e instanceof ClassCastException) {
// // give one time retry for CCE
// boolean cce = cce_.get();
// if (!cce) {
// cce_.set(true);
// throw (ClassCastException)e;
// } else {
// cce_.set(false);
// }
// }
StackTraceElement[] stackTrace = e.getStackTrace();
String msg = null;
for (StackTraceElement se : stackTrace) {
String cName = se.getClassName();
if (cName.contains(TemplateClass.CN_SUFFIX)) {
// is it the embedded class?
if (cName.indexOf("$") != -1) {
cName = cName.substring(0, cName.lastIndexOf("$"));
}
TemplateClass tc = __engine.classes().getByClassName(cName);
if (null == tc) {
continue;
}
if (null == msg) {
msg = e.getMessage();
if (S.isEmpty(msg)) {
msg = "Rythm runtime exception caused by " + e.getClass().getName();
}
}
RythmException re = new RythmException(__engine, e, tc, se.getLineNumber(), -1, msg);
int lineNo = re.templateLineNumber;
String key = tc.getKey().toString();
int i = key.indexOf('\n');
if (i == -1) i = key.indexOf('\r');
if (i > -1) {
key = key.substring(0, i - 1) + "...";
}
if (key.length() > 80) key = key.substring(0, 80) + "...";
if (lineNo != -1) {
StackTraceElement[] newStack = new StackTraceElement[stackTrace.length + 1];
newStack[0] = new StackTraceElement(tc.name(), "", key, lineNo);
System.arraycopy(stackTrace, 0, newStack, 1, stackTrace.length);
re.setStackTrace(newStack);
}
throw re;
}
}
throw (e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e));
}
// }
// } catch (RuntimeException e) {
// // try to restart engine
// if (!Rythm.insideSandbox()) {
// try {
// __engine.restart(e);
// } catch (RuntimeException e0) {
// // ignore it because we already thrown it out
// }
// }
// throw e;
// }
}
if (null != w_) {
try {
IO.writeContent(toString(), w_);
w_ = null;
} catch (Exception e) {
Logger.error(e, "failed to write template content to output destination");
}
}
}
/**
* Not to be used in user application or template
*/
protected String __internalRender() {
__internalBuild();
if (null != __parent && __parent != this) {
__parent.__setLayoutContent(toString());
__parent.addAllLayoutSections(layoutSections);
__parent.addAllRenderProperties(renderProperties);
__parent.__setRenderArgs(__renderArgs);
//__parent.__renderArgs.putAll(__renderArgs);
return __parent.render();
} else {
return toString();
}
}
/**
* The {@link com.greenlaw110.rythm.internal.CodeBuilder} will generate the
* implementation of this method usually
*
* @return this template as a {@link com.greenlaw110.rythm.utils.TextBuilder}
*/
@Override
public TextBuilder build() {
return this;
}
private Map userCtx;
@Override
public Map __getUserContext() {
return null == userCtx ? Collections.EMPTY_MAP : userCtx;
}
@Override
public ITemplate __setUserContext(Map context) {
this.userCtx = context;
return this;
}
/**
* Return render arg type in array. Not to be used in user application or template
*/
protected Class[] __renderArgTypeArray() {
return null;
}
/**
* Return render arg name by position
* @param i
* @return render argument name by position
*/
protected String __renderArgName(int i) {
return null;
}
/**
* Return render arg type in Map. Not to be used in user application or template
*
* @return render args types mapped by name
*/
protected Map __renderArgTypeMap() {
return Collections.EMPTY_MAP;
}
@Override
public ITemplate __setRenderArgs(Map args) {
__renderArgs.putAll(args);
return this;
}
@Override
public ITemplate __setRenderArg(JSONWrapper jsonData) {
if (jsonData.isArray()) {
setJSONArray(jsonData.getArray());
} else {
setJSONObject(jsonData.getObject());
}
return this;
}
private void setJSONArray(List
© 2015 - 2024 Weber Informatics LLC | Privacy Policy