All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.grails.web.gsp.GroovyPagesTemplateRenderer Maven / Gradle / Ivy

There is a newer version: 2023.1.0-RC1
Show newest version
/*
 * 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