org.grails.web.gsp.GroovyPagesTemplateRenderer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of grace-web-gsp Show documentation
Show all versions of grace-web-gsp Show documentation
Grace Framework : Grace Web Gsp
/*
* Copyright 2011-2023 the original author or authors.
*
* 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
*
* https://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 org.grails.web.gsp;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import groovy.text.Template;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import grails.core.GrailsDomainClass;
import grails.util.CacheEntry;
import grails.util.Environment;
import grails.util.GrailsNameUtils;
import grails.util.GrailsStringUtils;
import org.grails.buffer.CodecPrintWriter;
import org.grails.buffer.FastStringWriter;
import org.grails.encoder.EncodedAppenderWriterFactory;
import org.grails.encoder.Encoder;
import org.grails.encoder.StreamingEncoder;
import org.grails.encoder.StreamingEncoderWriter;
import org.grails.gsp.GroovyPage;
import org.grails.gsp.GroovyPageBinding;
import org.grails.gsp.GroovyPageMetaInfo;
import org.grails.gsp.GroovyPagesTemplateEngine;
import org.grails.gsp.io.GroovyPageScriptSource;
import org.grails.io.support.GrailsResourceUtils;
import org.grails.taglib.GrailsTagException;
import org.grails.taglib.TemplateVariableBinding;
import org.grails.taglib.encoder.OutputEncodingSettings;
import org.grails.taglib.encoder.WithCodecHelper;
import org.grails.web.gsp.io.GrailsConventionGroovyPageLocator;
import org.grails.web.servlet.mvc.GrailsWebRequest;
import org.grails.web.util.GrailsApplicationAttributes;
/**
* Service that provides the actual implementation to RenderTagLib's render tag.
*
* This is an internal Grails service and should not be used by plugins directly.
* The implementation was moved from RenderTagLib, ported to Java and then refactored.
*
* @author Lari Hotari
* @author Graeme Rocher
*
* @since 2.0
*/
@SuppressWarnings("deprecation")
public class GroovyPagesTemplateRenderer implements InitializingBean {
private GrailsConventionGroovyPageLocator groovyPageLocator;
private GroovyPagesTemplateEngine groovyPagesTemplateEngine;
private ConcurrentMap> templateCache = new ConcurrentHashMap<>();
private Object scaffoldingTemplateGenerator;
private Map> scaffoldedActionMap;
private Map controllerToScaffoldedDomainClassMap;
private Method generateViewMethod;
private boolean reloadEnabled;
private boolean cacheEnabled = !Environment.isDevelopmentMode();
public void afterPropertiesSet() throws Exception {
if (this.scaffoldingTemplateGenerator != null) {
// use reflection to locate method (would cause cyclic dependency otherwise)
this.generateViewMethod = ReflectionUtils.findMethod(this.scaffoldingTemplateGenerator.getClass(), "generateView", new Class>[] {
GrailsDomainClass.class, String.class, Writer.class });
}
this.reloadEnabled = this.groovyPagesTemplateEngine.isReloadEnabled();
}
public void clearCache() {
this.templateCache.clear();
}
public void render(GrailsWebRequest webRequest, TemplateVariableBinding pageScope,
Map attrs, Object body, Writer out) throws IOException {
Assert.state(this.groovyPagesTemplateEngine != null, "Property [groovyPagesTemplateEngine] must be set!");
String templateName = getStringValue(attrs, "template");
if (GrailsStringUtils.isBlank(templateName)) {
throw new GrailsTagException("Tag [render] is missing required attribute [template]");
}
String uri = webRequest.getAttributes().getTemplateUri(templateName, webRequest.getRequest());
String contextPath = getStringValue(attrs, "contextPath");
String pluginName = getStringValue(attrs, "plugin");
final Object controller = webRequest.getAttribute(GrailsApplicationAttributes.CONTROLLER, GrailsWebRequest.SCOPE_REQUEST);
Template t = findAndCacheTemplate(controller, pageScope, templateName, contextPath, pluginName, uri);
if (t == null) {
throw new GrailsTagException("Template not found for name [" + templateName + "] and path [" + uri + "]");
}
makeTemplate(webRequest, t, attrs, body, out);
}
// required for binary compatibility: GRAILS-11598
private Template findAndCacheTemplate(Object controller, GrailsWebRequest webRequest, GroovyPageBinding pageScope, String templateName,
String contextPath, String pluginName, String uri) throws IOException {
return findAndCacheTemplate(controller, pageScope, templateName, contextPath, pluginName, uri);
}
private Template findAndCacheTemplate(Object controller, TemplateVariableBinding pageScope, String templateName,
String contextPath, String pluginName, final String uri) throws IOException {
String templatePath = GrailsStringUtils.isNotEmpty(contextPath)
? GrailsResourceUtils.appendPiecesForUri(contextPath, templateName) : templateName;
final GroovyPageScriptSource scriptSource;
if (pluginName == null) {
scriptSource = this.groovyPageLocator.findTemplateInBinding(controller, templatePath, pageScope);
}
else {
scriptSource = this.groovyPageLocator.findTemplateInBinding(controller, pluginName, templatePath, pageScope);
}
String cacheKey;
if (scriptSource == null) {
cacheKey = contextPath + pluginName + uri;
}
else {
if (pluginName != null) {
cacheKey = contextPath + pluginName + scriptSource.getURI();
}
else {
cacheKey = scriptSource.getURI();
}
}
return CacheEntry.getValue(this.templateCache, cacheKey, this.reloadEnabled ? GroovyPageMetaInfo.LASTMODIFIED_CHECK_INTERVAL : -1, null,
new Callable>() {
public CacheEntry call() {
return new CacheEntry() {
boolean allowCaching = GroovyPagesTemplateRenderer.this.cacheEnabled;
boolean neverExpire = false;
@Override
protected boolean hasExpired(long timeout, Object cacheRequestObject) {
return this.neverExpire ? false : (this.allowCaching ? super.hasExpired(timeout, cacheRequestObject) : true);
}
@Override
public boolean isInitialized() {
return this.allowCaching ? super.isInitialized() : false;
}
@Override
public void setValue(Template val) {
if (this.allowCaching) {
super.setValue(val);
}
}
@Override
protected Template updateValue(Template oldValue, Callable updater, Object cacheRequestObject)
throws Exception {
Template t = null;
if (scriptSource != null) {
t = GroovyPagesTemplateRenderer.this.groovyPagesTemplateEngine.createTemplate(scriptSource);
}
if (t == null && GroovyPagesTemplateRenderer.this.scaffoldingTemplateGenerator != null) {
t = generateScaffoldedTemplate(GrailsWebRequest.lookup(), uri);
// always enable caching for generated
// scaffolded template
this.allowCaching = true;
// never expire scaffolded entry since scaffolding plugin flushes the whole cache on any change
this.neverExpire = true;
}
return t;
}
};
}
}, true, null);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void makeTemplate(GrailsWebRequest webRequest, Template t, Map attrs, Object body, Writer out) throws IOException {
Writer newOut = wrapWriterWithEncoder(webRequest, attrs, out);
boolean writerWrapped = (newOut != out);
out = newOut;
String var = getStringValue(attrs, "var");
Map b = new LinkedHashMap();
b.put("body", body);
if (attrs.get("model") instanceof Map) {
b.putAll((Map) attrs.get("model"));
}
if (attrs.containsKey("bean")) {
if (GrailsStringUtils.isNotBlank(var)) {
b.put(var, attrs.get("bean"));
}
else {
b.put("it", attrs.get("bean"));
}
}
if (attrs.containsKey("collection")) {
String key = null;
Iterator> iterator = InvokerHelper.asIterator(attrs.get("collection"));
while (iterator.hasNext()) {
Object it = iterator.next();
if (key == null && GrailsStringUtils.isBlank(var) && it != null) {
key = GrailsNameUtils.getPropertyName(it.getClass());
}
Map itmap = new LinkedHashMap();
itmap.putAll(b);
if (GrailsStringUtils.isNotBlank(var)) {
itmap.put(var, it);
}
else {
itmap.put("it", it);
itmap.put(key, it);
}
t.make(itmap).writeTo(out);
}
}
else {
t.make(b).writeTo(out);
}
if (writerWrapped) {
out.flush();
}
}
private Writer wrapWriterWithEncoder(GrailsWebRequest webRequest, Map attrs, Writer out) {
Object encodeAs = attrs.get(GroovyPage.ENCODE_AS_ATTRIBUTE_NAME);
if (encodeAs != null) {
Map codecSettings = WithCodecHelper.makeSettingsCanonical(encodeAs);
String codecForTaglibs = (String) codecSettings.get(OutputEncodingSettings.TAGLIB_CODEC_NAME);
if (codecForTaglibs != null) {
Encoder encoder = WithCodecHelper.lookupEncoder(webRequest.getAttributes().getGrailsApplication(), codecForTaglibs);
if (out instanceof EncodedAppenderWriterFactory) {
out = ((EncodedAppenderWriterFactory) out).getWriterForEncoder(encoder, webRequest.getEncodingStateRegistry());
}
else if (encoder instanceof StreamingEncoder) {
out = new StreamingEncoderWriter(out, (StreamingEncoder) encoder, webRequest.getEncodingStateRegistry());
}
else {
out = new CodecPrintWriter(out, encoder, webRequest.getEncodingStateRegistry());
}
}
}
return out;
}
private Template generateScaffoldedTemplate(GrailsWebRequest webRequest, String uri) throws IOException {
Template t = null;
Collection controllerActions = this.scaffoldedActionMap.get(webRequest.getControllerName());
if (controllerActions != null && controllerActions.contains(webRequest.getActionName())) {
GrailsDomainClass domainClass = this.controllerToScaffoldedDomainClassMap.get(webRequest.getControllerName());
if (domainClass != null) {
int i = uri.lastIndexOf('/');
String scaffoldedtemplateName = i > -1 ? uri.substring(i) : uri;
if (scaffoldedtemplateName.toLowerCase().endsWith(".gsp")) {
scaffoldedtemplateName = scaffoldedtemplateName.substring(0, scaffoldedtemplateName.length() - 4);
}
FastStringWriter sw = new FastStringWriter();
ReflectionUtils.invokeMethod(this.generateViewMethod, this.scaffoldingTemplateGenerator, domainClass, scaffoldedtemplateName, sw);
t = this.groovyPagesTemplateEngine.createTemplate(
new ByteArrayResource(sw.toString().getBytes("UTF-8"), uri), false);
}
}
return t;
}
private String getStringValue(Map attrs, String key) {
Object val = attrs.get(key);
if (val == null) {
return "";
}
return String.valueOf(val);
}
public void setGroovyPageLocator(GrailsConventionGroovyPageLocator locator) {
this.groovyPageLocator = locator;
}
public void setGroovyPagesTemplateEngine(GroovyPagesTemplateEngine engine) {
this.groovyPagesTemplateEngine = engine;
}
public void setScaffoldingTemplateGenerator(Object generator) {
this.scaffoldingTemplateGenerator = generator;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setScaffoldedActionMap(Map map) {
this.scaffoldedActionMap = map;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public void setControllerToScaffoldedDomainClassMap(Map map) {
this.controllerToScaffoldedDomainClassMap = map;
}
public boolean isCacheEnabled() {
return this.cacheEnabled;
}
public void setCacheEnabled(boolean cacheEnabled) {
this.cacheEnabled = cacheEnabled;
}
}