groovy.text.markup.MarkupTemplateEngine Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of groovy-templates Show documentation
Show all versions of groovy-templates Show documentation
Groovy: A powerful multi-faceted language for the JVM
The newest version!
/*
* 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 groovy.text.markup;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyCodeSource;
import groovy.lang.Writable;
import groovy.text.Template;
import groovy.text.TemplateEngine;
import groovy.transform.TypeChecked;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.classgen.asm.BytecodeDumper;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer;
import org.codehaus.groovy.control.customizers.CompilationCustomizer;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A template engine which leverages {@link groovy.xml.StreamingMarkupBuilder} to generate XML/XHTML.
*/
public class MarkupTemplateEngine extends TemplateEngine {
static final ClassNode MARKUPTEMPLATEENGINE_CLASSNODE = ClassHelper.make(MarkupTemplateEngine.class);
static final String MODELTYPES_ASTKEY = "MTE.modelTypes";
private static final Pattern LOCALIZED_RESOURCE_PATTERN = Pattern.compile("(.+?)(?:_([a-z]{2}(?:_[A-Z]{2,3})))?\\.([\\p{Alnum}.]+)$");
private static final boolean DEBUG_BYTECODE = Boolean.getBoolean("markuptemplateengine.compiler.debug");
private static final AtomicLong counter = new AtomicLong();
private final TemplateGroovyClassLoader groovyClassLoader;
private final CompilerConfiguration compilerConfiguration;
private final TemplateConfiguration templateConfiguration;
private final Map codeSourceCache = new LinkedHashMap<>();
private final TemplateResolver templateResolver;
public MarkupTemplateEngine() {
this(new TemplateConfiguration());
}
public MarkupTemplateEngine(final TemplateConfiguration config) {
this(MarkupTemplateEngine.class.getClassLoader(), config, null);
}
public MarkupTemplateEngine(final ClassLoader parentLoader, final TemplateConfiguration config) {
this(parentLoader, config, null);
}
public MarkupTemplateEngine(final ClassLoader parentLoader, final TemplateConfiguration config, final TemplateResolver resolver) {
templateConfiguration = config;
compilerConfiguration = new CompilerConfiguration();
List customizers = compilerConfiguration.getCompilationCustomizers();
customizers.add(new TemplateASTTransformer(templateConfiguration));
customizers.add(new ASTTransformationCustomizer(
Collections.singletonMap("extensions", "groovy.text.markup.MarkupTemplateTypeCheckingExtension"), TypeChecked.class));
if (templateConfiguration.isAutoNewLine()) {
customizers.add(new CompilationCustomizer(CompilePhase.CONVERSION) {
@Override
public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) {
new AutoNewLineTransformer(source).visitClass(classNode);
}
});
}
if (DEBUG_BYTECODE) {
compilerConfiguration.setBytecodePostprocessor(BytecodeDumper.STANDARD_ERR);
}
groovyClassLoader = doPrivileged(() -> new TemplateGroovyClassLoader(parentLoader, compilerConfiguration));
templateResolver = resolver != null ? resolver : new DefaultTemplateResolver();
templateResolver.configure(groovyClassLoader, templateConfiguration);
}
/**
* Convenience constructor to build a template engine which searches for templates into a directory
*
* @param templateDirectory directory where to find templates
* @param tplConfig template engine configuration
*/
public MarkupTemplateEngine(final ClassLoader parentLoader, final File templateDirectory, TemplateConfiguration tplConfig) {
this(doPrivileged(
new PrivilegedAction() {
@Override
public URLClassLoader run() {
return new URLClassLoader(buildURLs(templateDirectory), parentLoader);
}
}),
tplConfig,
null);
}
@SuppressWarnings("removal") // TODO a future Groovy version should perform the operation not as a privileged action
private static T doPrivileged(PrivilegedAction action) {
return java.security.AccessController.doPrivileged(action);
}
private static URL[] buildURLs(final File templateDirectory) {
try {
return new URL[]{templateDirectory.toURI().toURL()};
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Invalid directory", e);
}
}
@Override
public Template createTemplate(final Reader reader) throws CompilationFailedException, ClassNotFoundException, IOException {
return new MarkupTemplateMaker(reader, null, null);
}
public Template createTemplate(final Reader reader, String sourceName) throws CompilationFailedException, ClassNotFoundException, IOException {
return new MarkupTemplateMaker(reader, sourceName, null);
}
public Template createTemplateByPath(final String templatePath) throws CompilationFailedException, ClassNotFoundException, IOException {
return new MarkupTemplateMaker(resolveTemplate(templatePath), null);
}
public Template createTypeCheckedModelTemplate(final String source, Map modelTypes) throws CompilationFailedException, ClassNotFoundException, IOException {
return new MarkupTemplateMaker(new StringReader(source), null, modelTypes);
}
public Template createTypeCheckedModelTemplate(final String source, String sourceName, Map modelTypes) throws CompilationFailedException, ClassNotFoundException, IOException {
return new MarkupTemplateMaker(new StringReader(source), sourceName, modelTypes);
}
public Template createTypeCheckedModelTemplate(final Reader reader, Map modelTypes) throws CompilationFailedException, ClassNotFoundException, IOException {
return new MarkupTemplateMaker(reader, null, modelTypes);
}
public Template createTypeCheckedModelTemplate(final Reader reader, String sourceName, Map modelTypes) throws CompilationFailedException, ClassNotFoundException, IOException {
return new MarkupTemplateMaker(reader, sourceName, modelTypes);
}
public Template createTypeCheckedModelTemplateByPath(final String templatePath, Map modelTypes) throws CompilationFailedException, ClassNotFoundException, IOException {
return new MarkupTemplateMaker(resolveTemplate(templatePath), modelTypes);
}
@Override
public Template createTemplate(final URL resource) throws CompilationFailedException, ClassNotFoundException, IOException {
return new MarkupTemplateMaker(resource, null);
}
public Template createTypeCheckedModelTemplate(final URL resource, Map modelTypes) throws CompilationFailedException, ClassNotFoundException, IOException {
return new MarkupTemplateMaker(resource, modelTypes);
}
public GroovyClassLoader getTemplateLoader() {
return groovyClassLoader;
}
public CompilerConfiguration getCompilerConfiguration() {
return compilerConfiguration;
}
public TemplateConfiguration getTemplateConfiguration() {
return templateConfiguration;
}
public URL resolveTemplate(String templatePath) throws IOException {
return templateResolver.resolveTemplate(templatePath);
}
/**
* Implements the {@link groovy.text.Template} interface by caching a compiled template script and keeping a
* reference to the optional map of types of the model elements.
*/
private class MarkupTemplateMaker implements Template {
final Class templateClass;
final Map modeltypes;
@SuppressWarnings("unchecked")
MarkupTemplateMaker(final Reader reader, String sourceName, Map modelTypes) {
String name = sourceName != null ? sourceName : "GeneratedMarkupTemplate" + counter.getAndIncrement();
templateClass = groovyClassLoader.parseClass(new GroovyCodeSource(reader, name, "x"), modelTypes);
this.modeltypes = modelTypes;
}
@SuppressWarnings("unchecked")
public MarkupTemplateMaker(final URL resource, Map modelTypes) throws IOException {
boolean cache = templateConfiguration.isCacheTemplates();
GroovyCodeSource codeSource;
if (cache) {
// we use a map in addition to the internal caching mechanism of Groovy because the latter
// will always read from the URL even if it's cached
String key = resource.toExternalForm();
codeSource = codeSourceCache.get(key);
if (codeSource == null) {
codeSource = new GroovyCodeSource(resource);
codeSourceCache.put(key, codeSource);
}
} else {
codeSource = new GroovyCodeSource(resource);
}
codeSource.setCachable(cache);
templateClass = groovyClassLoader.parseClass(codeSource, modelTypes);
this.modeltypes = modelTypes;
}
@Override
public Writable make() {
return make(Collections.emptyMap());
}
@Override
public Writable make(final Map binding) {
return DefaultGroovyMethods.newInstance(templateClass, new Object[]{MarkupTemplateEngine.this, binding, modeltypes, templateConfiguration});
}
}
/**
* A specialized GroovyClassLoader which will support passing values to the type checking extension thanks to a
* thread local.
*/
static class TemplateGroovyClassLoader extends GroovyClassLoader {
static final ThreadLocal