/**
* jetbrick-template
* http://subchen.github.io/jetbrick-template/
*
* Copyright 2010-2014 Guoqiang Chen. All rights reserved.
* Email: subchen@gmail.com
*
* Licensed 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 jetbrick.template;
import java.io.*;
import java.util.Map;
import jetbrick.template.JetConfig.CompileStrategy;
import jetbrick.template.compiler.JavaCompiler;
import jetbrick.template.compiler.JavaSource;
import jetbrick.template.parser.*;
import jetbrick.template.parser.code.Code;
import jetbrick.template.parser.grammer.*;
import jetbrick.template.parser.grammer.JetTemplateParser.TemplateContext;
import jetbrick.template.resource.*;
import jetbrick.template.runtime.*;
import jetbrick.template.utils.ExceptionUtils;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class JetTemplate {
private static final Logger log = LoggerFactory.getLogger(JetTemplate.class);
private final JetEngine engine;
private final Resource resource;
private final String encoding;
private final boolean reloadable;
private final File javaClassFile;
private long lastCompiledTimestamp;
private JetPage pageObject;
protected JetTemplate(JetEngine engine, Resource resource) {
JetConfig config = engine.getConfig();
CompileStrategy compileStrategy = config.getCompileStrategy();
this.engine = engine;
this.resource = resource;
this.encoding = config.getOutputEncoding();
this.reloadable = config.isTemplateReloadable() && compileStrategy != CompileStrategy.none;
this.javaClassFile = engine.getClassLoader().getGeneratedJavaClassFile(resource.getQualifiedClassName());
this.lastCompiledTimestamp = javaClassFile.lastModified();
// compile and load
switch (compileStrategy) {
case precompile:
case always:
compileAndLoadClass();
break;
case auto:
if (lastCompiledTimestamp > resource.lastModified()) {
try {
loadClassFile();
} catch (Throwable e) {
// 无法 load 的话,尝试重新编译
log.warn(e.getClass().getName() + ": " + e.getMessage());
log.warn("Try to recompile this template.");
compileAndLoadClass();
}
} else {
compileAndLoadClass();
}
break;
case none:
if (resource instanceof SourceCodeResource) {
// source code 的情况下必须编译
compileAndLoadClass();
} else if (resource instanceof CompiledClassResource) {
try {
loadClassFile();
} catch (Exception e) {
throw ExceptionUtils.uncheck(e);
}
} else {
throw new IllegalStateException("Invalid resource when " + JetConfig.COMPILE_STRATEGY + " is " + compileStrategy);
}
break;
}
}
// 检测模板是否已更新
protected void checkLastModified() {
if (reloadable && lastCompiledTimestamp < resource.lastModified()) {
synchronized (this) {
// double check
if (lastCompiledTimestamp < resource.lastModified()) {
compileAndLoadClass();
}
}
}
}
// 从 disk 的缓存文件中读取 class
private void loadClassFile() throws Exception {
Class<?> compiledKlass;
if (resource instanceof CompiledClassResource) {
log.info("Loading template from classpath: {}.", resource.getName());
compiledKlass = ((CompiledClassResource) resource).getCompiledClass();
} else {
log.info("Loading template class file: {}", javaClassFile.getAbsolutePath());
compiledKlass = engine.getClassLoader().loadClass(resource.getQualifiedClassName());
// 判断编码匹配
if (!encoding.equals(compiledKlass.getDeclaredField("$ENC").get(null))) {
throw new IllegalStateException("The encoding of last compiled template class is not " + encoding);
}
}
pageObject = (JetPage) compiledKlass.newInstance();
}
// 编译 source 为 class, 然后 load class
private void compileAndLoadClass() {
boolean notPrecompileThread = !"JetPreCompiler".equals(Thread.currentThread().getName());
if (notPrecompileThread) {
log.info("Loading template source file: " + resource.getAbsolutePath());
}
// generateJavaSource
String source = generateJavaSource(resource);
if (notPrecompileThread && log.isInfoEnabled() && engine.getConfig().isCompileDebug()) {
File javaSourceFile = engine.getClassLoader().getGeneratedJavaSourceFile(resource.getQualifiedClassName());
StringBuilder sb = new StringBuilder(source.length() + 128);
sb.append("generateJavaSource: ");
sb.append(javaSourceFile.getAbsolutePath());
sb.append("\n");
sb.append("===========================================================================\n");
sb.append(source);
sb.append("\n");
sb.append("===========================================================================");
log.info(sb.toString());
}
JavaCompiler javaCompiler = engine.getJavaCompiler();
// compile
long ts = System.currentTimeMillis();
JavaSource javaSource = new JavaSource(resource.getQualifiedClassName(), source, javaCompiler.getOutputdir());
Class<?> cls = javaCompiler.compile(javaSource);
if (notPrecompileThread) {
ts = System.currentTimeMillis() - ts;
log.info("generateJavaClass: {}, {}ms", javaClassFile.getAbsolutePath(), ts);
}
try {
lastCompiledTimestamp = javaClassFile.lastModified();
pageObject = (JetPage) cls.newInstance();
} catch (Exception e) {
throw ExceptionUtils.uncheck(e);
}
}
private String generateJavaSource(Resource resource) {
char[] source = resource.getSource();
ANTLRInputStream is = new ANTLRInputStream(source, source.length);
is.name = resource.getAbsolutePath(); // set source file name, it will be displayed in error report.
JetTemplateLexer lexer = new JetTemplateLexer(is);
lexer.removeErrorListeners(); // remove ConsoleErrorListener
lexer.addErrorListener(JetTemplateErrorListener.getInstance());
JetTemplateParser parser = new JetTemplateParser(new CommonTokenStream(lexer));
parser.removeErrorListeners(); // remove ConsoleErrorListener
parser.addErrorListener(JetTemplateErrorListener.getInstance());
parser.setErrorHandler(new JetTemplateErrorStrategy());
TemplateContext templateParseTree = parser.template();
JetTemplateCodeVisitor visitor = new JetTemplateCodeVisitor(engine, engine.getVariableResolver(), engine.getSecurityManager(), parser, resource);
Code code = templateParseTree.accept(visitor);
return code.toString();
}
public void render(Map<String, Object> context, Writer out) {
JetContext ctx = new JetContext(context);
JetWriter writer = JetWriter.create(out, encoding);
render(new JetPageContext(this, ctx, writer));
}
public void render(Map<String, Object> context, OutputStream out) {
JetContext ctx = new JetContext(context);
JetWriter writer = JetWriter.create(out, encoding);
render(new JetPageContext(this, ctx, writer));
}
public void render(JetContext context, Writer out) {
if (context == null) {
context = new JetContext(null);
} else if (context.isSimpleModel()) {
// simpleModel 的情况代表是用户自己 new 出来的 JetContext
// 为了防止 #set 污染 context,这里重新 new 一个新的。
context = new JetContext(context.getContext());
}
JetWriter writer = JetWriter.create(out, encoding);
render(new JetPageContext(this, context, writer));
}
public void render(JetContext context, OutputStream out) {
if (context == null) {
context = new JetContext(null);
} else if (context.isSimpleModel()) {
// simpleModel 的情况代表是用户自己 new 出来的 JetContext
// 为了防止 #set 污染 context,这里重新 new 一个新的。
context = new JetContext(context.getContext());
}
JetWriter writer = JetWriter.create(out, encoding);
render(new JetPageContext(this, context, writer));
}
public void render(JetContext context, JetWriter writer) {
render(new JetPageContext(this, context, writer));
}
private void render(JetPageContext ctx) {
try {
if (engine.getGlobalVariables() != null) {
ctx.getContext().setGlobalVariables(engine.getGlobalVariables());
}
pageObject.render(ctx);
} catch (Throwable e) {
// JSP use response.getWriter() to ignore all io exceptions.
// I use response.getOutputStream() and only ignore ClientAbortException.
if ("org.apache.catalina.connector.ClientAbortException".equals(e.getClass().getName())) {
log.warn(e.toString());
} else {
throw ExceptionUtils.uncheck(e);
}
}
}
public JetEngine getEngine() {
return engine;
}
public String getName() {
return resource.getName();
}
}