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

org.grails.gsp.GroovyPagesTemplateEngine Maven / Gradle / Ivy

/*
 * Copyright 2004-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.gsp;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

import groovy.lang.GroovyClassLoader;
import groovy.text.Template;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.runtime.IOGroovyMethods;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.UrlResource;
import org.springframework.scripting.ScriptSource;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.util.Assert;

import grails.config.Config;
import grails.config.Settings;
import grails.core.GrailsApplication;
import grails.core.GrailsClass;
import grails.io.IOUtils;
import grails.util.CacheEntry;
import grails.util.Environment;
import grails.util.GrailsUtil;

import org.grails.core.artefact.DomainClassArtefactHandler;
import org.grails.core.exceptions.DefaultErrorsPrinter;
import org.grails.core.io.support.GrailsFactoriesLoader;
import org.grails.exceptions.ExceptionUtils;
import org.grails.gsp.compiler.GroovyPageParser;
import org.grails.gsp.io.DefaultGroovyPageLocator;
import org.grails.gsp.io.GroovyPageCompiledScriptSource;
import org.grails.gsp.io.GroovyPageLocator;
import org.grails.gsp.io.GroovyPageResourceScriptSource;
import org.grails.gsp.io.GroovyPageScriptSource;
import org.grails.gsp.jsp.TagLibraryResolver;
import org.grails.taglib.TagInvocationContextCustomizer;
import org.grails.taglib.TagLibraryLookup;

/**
 * Based on (but not extending) the existing TemplateEngine implementations
 * within Groovy. It allows GSP pages to be re-used in different context using code like the below:
 *
 * 
 *      Template t = new GroovyPagesTemplateEngine()
 *                          .createTemplate(context,request,response);
 *      t.make()
 *       .writeTo(out);
 * 
 *
 * @author Graeme Rocher
 * @author Lari Hotari
 *
 * @since 0.1
 */
public class GroovyPagesTemplateEngine extends ResourceAwareTemplateEngine
        implements ResourceLoaderAware, ApplicationContextAware, InitializingBean, BeanClassLoaderAware {

    private static final Log logger = LogFactory.getLog(GroovyPagesTemplateEngine.class);

    public static final String CONFIG_PROPERTY_DISABLE_CACHING_RESOURCES = Settings.GSP_DISABLE_CACHING_RESOURCES;

    public static final String CONFIG_PROPERTY_GSP_ENABLE_RELOAD = Settings.GSP_ENABLE_RELOAD;

    public static final String BEAN_ID = ResourceAwareTemplateEngine.BEAN_ID;

    private static final String GENERATED_GSP_NAME_PREFIX = "gsp_script_";

    private static File dumpLineNumbersTo;

    private ConcurrentMap> pageCache = new ConcurrentHashMap<>();

    private ClassLoader classLoader;

    private AtomicInteger scriptNameCount = new AtomicInteger(0);

    private GroovyPageLocator groovyPageLocator = new DefaultGroovyPageLocator();

    private boolean reloadEnabled;

    private TagLibraryLookup tagLibraryLookup;

    private TagLibraryResolver jspTagLibraryResolver;

    private boolean cacheResources = true;

    private String gspEncoding = System.getProperty("file.encoding", GroovyPageParser.DEFAULT_ENCODING);

    private GrailsApplication grailsApplication;

    private Map> cachedDomainsWithoutPackage;

    private List groovyPageSourceDecorators = new ArrayList<>();

    private List tagInvocationContextCustomizers = new ArrayList<>();

    static {
        String dirPath = System.getProperty("grails.dump.gsp.line.numbers.to.dir");
        if (dirPath != null) {
            File dir = new File(dirPath);
            if (dir.exists() || dir.mkdirs()) {
                dumpLineNumbersTo = dir;
            }
        }
    }

    public GroovyPagesTemplateEngine() {
        // default
    }

    public void setGroovyPageSourceDecorators(List groovyPageSourceDecorators) {
        this.groovyPageSourceDecorators = groovyPageSourceDecorators;
    }

    public List getGroovyPageSourceDecorators() {
        return this.groovyPageSourceDecorators;
    }

    public void setGroovyPageLocator(GroovyPageLocator groovyPageLocator) {
        this.groovyPageLocator = groovyPageLocator;
    }

    public GroovyPageLocator getGroovyPageLocator() {
        return this.groovyPageLocator;
    }

    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.groovyPageLocator.addResourceLoader(resourceLoader);
    }

    public void afterPropertiesSet() {
        if (this.classLoader == null) {
            this.classLoader = initGroovyClassLoader(Thread.currentThread().getContextClassLoader());
        }
        else if (!this.classLoader.getClass().equals(GroovyPageClassLoader.class)) {
            this.classLoader = initGroovyClassLoader(this.classLoader);
        }
        if (!Environment.isDevelopmentMode()) {
            this.cachedDomainsWithoutPackage = createDomainClassMap();
        }
    }

    private GroovyClassLoader initGroovyClassLoader(ClassLoader parent) {
        CompilerConfiguration compConfig = new CompilerConfiguration();
        compConfig.setSourceEncoding(GroovyPageParser.GROOVY_SOURCE_CHAR_ENCODING);
        return new GroovyPageClassLoader(parent, compConfig);
    }

    public void setTagLibraryLookup(TagLibraryLookup tagLibraryLookup) {
        this.tagLibraryLookup = tagLibraryLookup;
    }

    public void setJspTagLibraryResolver(TagLibraryResolver jspTagLibraryResolver) {
        this.jspTagLibraryResolver = jspTagLibraryResolver;
    }

    /**
     * Sets the ClassLoader that the TemplateEngine should use to
     * @param classLoader The ClassLoader to use when compilation of Groovy Pages occurs
     */
    public void setClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    /**
     * Retrieves a line number matrix for the specified page that can be used
     * to retrieve the actual line number within the GSP page if the line number within the
     * compiled GSP is known
     *
     * @param url The URL of the page
     * @return An array where the index is the line number witin the compiled GSP and the value is the line number within the source
     */
    public int[] calculateLineNumbersForPage(String url) {
        try {
            Template t = createTemplate(url);
            if (t instanceof GroovyPageTemplate) {
                return ((GroovyPageTemplate) t).getMetaInfo().getLineNumbers();
            }
        }
        catch (Exception e) {
            // ignore, non critical method used for retrieving debug info
            logger.warn("Exception retrieving line numbers from GSP: " + url + ", message: " + e.getMessage());
            logger.debug("Full stack trace of error", e);
        }
        return new int[0];
    }

    public int mapStackLineNumber(String url, int lineNumber) {
        int[] lineNumbers = calculateLineNumbersForPage(url);
        if (lineNumber < lineNumbers.length) {
            lineNumber = lineNumbers[lineNumber - 1];
        }
        return lineNumber;
    }

    /**
     * Creates a Template for the given Spring Resource instance
     *
     * @param resource The Resource to create the Template for
     * @return The Template instance
     */
    @Override
    public Template createTemplate(Resource resource) {
        return createTemplate(resource, this.cacheResources);
    }

    /**
     * Creates a Template for the given Spring Resource instance
     *
     * @param resource The Resource to create the Template for
     * @param cacheable The resource can be cached or not
     * @return The Template instance
     */
    @Override
    public Template createTemplate(Resource resource, final boolean cacheable) {
        if (resource == null) {
            throw new GroovyPagesException("Resource is null. No Groovy page found.");
        }
        // Yags: Because, "pageName" was sent as null originally, it is never go in pageCache,
        // but will force to compile the String again and till the time this request
        // is getting executed, it will occupy space in PermGen space.
        // So if there are 1000 request for the same resource at a particular instance, there will be 1000 instance
        // class in PermGen instead of ideally being 1 as they as essentially same resource.
        // we will cache metaInfo only is Developer wants-to.
        // Developer will make sure that he creates unique key for every unique pages s/he wants to put in cache
        final String pageName = establishPageName(resource, cacheable);
        try {
            return createTemplate(resource, pageName, cacheable);
        }
        catch (IOException e) {
            throw new GroovyPagesException("Error loading template", e);
        }
    }

    protected Template createTemplate(Resource resource, final String pageName, final boolean cacheable) throws IOException {
        GroovyPageMetaInfo meta;
        if (cacheable) {
            meta = CacheEntry.getValue(this.pageCache, pageName, -1, null,
                    new GroovyPagesTemplateEngineCallable(new GroovyPagesTemplateEngineCacheEntry(pageName)), true, resource);
        }
        else {
            meta = buildPageMetaInfo(resource, pageName);
        }
        return new GroovyPageTemplate(meta);
    }

    protected String establishPageName(Resource resource, final boolean cacheable) {
        String name;
        if (cacheable) {
            name = establishPageName(resource, getPathForResource(resource));
        }
        else {
            name = establishPageName(resource, null);
        }
        return name;
    }

    /**
     * Creates a Template using the given URI.
     *
     * @param uri The URI of the page to create the template for
     * @return The Template instance
     * @throws CompilationFailedException
     */
    @Override
    public Template createTemplate(String uri) {
        return createTemplateForUri(uri);
    }

    private Template createTemplateFromPrecompiled(GroovyPageCompiledScriptSource compiledScriptSource) {
        GroovyPageMetaInfo meta = initializeCompiledMetaInfo(compiledScriptSource.getGroovyPageMetaInfo());
        if (isReloadEnabled()) {
            GroovyPageResourceScriptSource changedResourceScriptSource = compiledScriptSource.getReloadableScriptSource();
            if (changedResourceScriptSource != null) {
                this.groovyPageLocator.removePrecompiledPage(compiledScriptSource);
                return createTemplate(changedResourceScriptSource);
            }
        }
        return new GroovyPageTemplate(meta);
    }

    private GroovyPageMetaInfo initializeCompiledMetaInfo(GroovyPageMetaInfo meta) {
        meta.initializeOnDemand(new GroovyPageMetaInfo.GroovyPageMetaInfoInitializer() {
            public void initialize(GroovyPageMetaInfo metaInfo) {
                metaInfo.setGrailsApplication(GroovyPagesTemplateEngine.this.grailsApplication);
                metaInfo.setJspTagLibraryResolver(GroovyPagesTemplateEngine.this.jspTagLibraryResolver);
                metaInfo.setTagLibraryLookup(GroovyPagesTemplateEngine.this.tagLibraryLookup);
                metaInfo.initialize();
                GroovyPagesMetaUtils.registerMethodMissingForGSP(metaInfo.getPageClass(), GroovyPagesTemplateEngine.this.tagLibraryLookup);
            }
        });
        return meta;
    }

    public Template createTemplateForUri(String uri) {
        return createTemplateForUri(new String[] { uri });
    }

    public Template createTemplateForUri(String[] uris) {
        GroovyPageScriptSource scriptSource = findScriptSource(uris);

        if (scriptSource != null) {
            return createTemplate(scriptSource);
        }
        return null;
    }

    public GroovyPageScriptSource findScriptSource(String uri) {
        return findScriptSource(new String[] { uri });
    }

    public GroovyPageScriptSource findScriptSource(String[] uris) {
        GroovyPageScriptSource scriptSource = null;

        for (String uri : uris) {
            scriptSource = this.groovyPageLocator.findPage(uri);
            if (scriptSource != null) {
                break;
            }
        }
        return scriptSource;
    }

    public Template createTemplate(ScriptSource scriptSource) {
        if (scriptSource instanceof GroovyPageCompiledScriptSource) {
            // handle pre-compiled
            return createTemplateFromPrecompiled((GroovyPageCompiledScriptSource) scriptSource);
        }

        if (scriptSource instanceof ResourceScriptSource) {
            ResourceScriptSource resourceSource = (ResourceScriptSource) scriptSource;
            Resource resource = resourceSource.getResource();
            return createTemplate(resource, true);
        }

        try {
            return createTemplate(scriptSource.getScriptAsString(), scriptSource.suggestedClassName());
        }
        catch (IOException e) {
            throw new RuntimeException("IOException in createTemplate", e);
        }
    }

    /**
     * Creates a Template using the given text for the Template and the given name. The name
     * of the template is required
     *
     * @param txt The URI of the page to create the template for
     * @param pageName The name of the page being parsed
     *
     * @return The Template instance
     * @throws CompilationFailedException
     * @throws IOException Thrown if an IO exception occurs creating the Template
     */
    public Template createTemplate(String txt, String pageName) throws IOException {
        Assert.hasLength(txt, "Argument [txt] cannot be null or blank");
        Assert.hasLength(pageName, "Argument [pageName] cannot be null or blank");

        return createTemplate(new ByteArrayResource(txt.getBytes("UTF-8"), pageName), pageName, pageName != null);
    }

    /**
     * Creates a Template for the given file
     *
     * @param file The File to use to construct the template with
     * @return A Groovy Template instance
     *
     * @throws CompilationFailedException When an error occured compiling the Template
     * @throws ClassNotFoundException When a Class cannot be found within the given Template
     * @throws IOException When a I/O Exception occurs reading the Template
     */
    @Override
    public Template createTemplate(File file) throws CompilationFailedException, ClassNotFoundException, IOException {
        return createTemplate(new FileSystemResource(file));
    }

    /**
     * Creates a Template for the given URL
     *
     * @param url The URL to use to construct the template with
     * @return A Groovy Template instance
     *
     * @throws CompilationFailedException When an error occured compiling the Template
     * @throws ClassNotFoundException When a Class cannot be found within the given Template
     * @throws IOException When a I/O Exception occurs reading the Template
     */
    @Override
    public Template createTemplate(URL url) throws CompilationFailedException, ClassNotFoundException, IOException {
        return createTemplate(new UrlResource(url));
    }

    /**
     * Create a Template for the given InputStream
     * @param inputStream The InputStream to create the Template for
     * @return The Template instance
     */
    @Override
    public Template createTemplate(InputStream inputStream) {
        GroovyPageMetaInfo metaInfo = buildPageMetaInfo(inputStream, null, null);
        return new GroovyPageTemplate(metaInfo);
    }

    protected GroovyPageMetaInfo buildPageMetaInfo(Resource resource, String pageName) throws IOException {
        InputStream inputStream = resource.getInputStream();
        try {
            return buildPageMetaInfo(inputStream, resource, pageName);
        }
        finally {
            inputStream.close();
        }
    }

    private StringBuilder decorateGroovyPageSource(StringBuilder source) throws IOException {
        for (GroovyPageSourceDecorator groovyPageSourceDecorator : this.groovyPageSourceDecorators) {
            source = groovyPageSourceDecorator.decorate(source);
        }
        return source;
    }

    /**
     * Establishes whether a Groovy page is reloadable. A GSP is only reloadable in the development environment.
     *
     * @param resource The Resource to check.
     * @param meta The current GroovyPageMetaInfo instance
     * @return true if it is reloadable
     */
    private boolean isGroovyPageReloadable(final Resource resource, GroovyPageMetaInfo meta) {
        return isReloadEnabled() && meta.shouldReload(new PrivilegedAction() {
            public Resource run() {
                return resource;
            }
        });
    }

    /**
     * Return whether reload is enabled for the GroovyPagesTemplateEngine
     *
     * @return true if it is
     */
    public boolean isReloadEnabled() {
        return this.reloadEnabled;
    }

    /**
     * Sets whether reloading is enabled
     *
     * @param b True if it is enabled
     */
    public void setReloadEnabled(boolean b) {
        this.reloadEnabled = b;
    }

    /**
     * Attempts to retrieve a reference to a GSP as a Spring Resource instance for the given URI.
     *
     * @param uri The URI to check
     * @return A Resource instance
     */
    public Resource getResourceForUri(String uri) {
        GroovyPageScriptSource scriptSource = getResourceWithinContext(uri);
        if (scriptSource != null && (scriptSource instanceof GroovyPageResourceScriptSource)) {
            return ((GroovyPageResourceScriptSource) scriptSource).getResource();
        }
        return null;
    }

    private GroovyPageScriptSource getResourceWithinContext(String uri) {
        Assert.state(this.groovyPageLocator != null, "TemplateEngine not initialised correctly, no [groovyPageLocator] specified!");
        GroovyPageScriptSource scriptSource = this.groovyPageLocator.findPage(uri);
        if (scriptSource != null) {
            return scriptSource;
        }
        return null;
    }

    /**
     * Constructs a GroovyPageMetaInfo instance which holds the script class, modified date and so on
     *
     * @param inputStream The InputStream to construct the GroovyPageMetaInfo instance from
     * @param res The Spring Resource to construct the MetaInfo from
     * @param pageName The name of the page (can be null, in which case method responsible for calculating appropriate alternative)
     * @return The GroovyPageMetaInfo instance
     */
    protected GroovyPageMetaInfo buildPageMetaInfo(InputStream inputStream, Resource res, String pageName) {
        String name = establishPageName(res, pageName);

        GroovyPageParser parser;
        String path = getPathForResource(res);
        try {
            String gspSource = IOUtils.toString(inputStream, getGspEncoding());
            parser = new GroovyPageParser(name, path, path, decorateGroovyPageSource(new StringBuilder(gspSource)).toString());

            if (this.grailsApplication != null) {
                Config config = this.grailsApplication.getConfig();
                parser.configure(config);
            }
        }
        catch (IOException e) {
            throw new GroovyPagesException("I/O parsing Groovy page [" + (res != null ? res.getDescription() : name) + "]: " + e.getMessage(), e);
        }

        InputStream in = parser.parse();

        // Make a new metaInfo
        GroovyPageMetaInfo metaInfo = createPageMetaInfo(parser, in);
        metaInfo.applyLastModifiedFromResource(res);
        try {
            metaInfo.setPageClass(compileGroovyPage(in, name, path, metaInfo));
            metaInfo.setHtmlParts(parser.getHtmlPartsArray());
            metaInfo.setTagInvocationContextCustomizers(this.tagInvocationContextCustomizers);
        }
        catch (GroovyPagesException e) {
            metaInfo.setCompilationException(e);
        }

        return metaInfo;
    }

    private String getPathForResource(Resource res) {
        if (res == null) {
            return "";
        }

        String path = null;
        try {
            File file = res.getFile();
            if (file != null) {
                path = file.getAbsolutePath();
            }
        }
        catch (IOException e) {
            // ignore
        }
        if (path != null) {
            return path;
        }
        if (res.getDescription() != null) {
            return res.getDescription();
        }
        return "";
    }

    /**
     * Attempts to compile the given InputStream into a Groovy script using the given name
     * @param in The InputStream to read the Groovy code from
     * @param name The name of the class to use
     * @param pageName The page name
     * @param metaInfo
     * @return The compiled java.lang.Class, which is an instance of groovy.lang.Script
     */
    private Class compileGroovyPage(InputStream in, String name, String pageName, GroovyPageMetaInfo metaInfo) {
        GroovyClassLoader groovyClassLoader = findOrInitGroovyClassLoader();

        // Compile the script into an object
        Class scriptClass;
        try {
            String groovySource = IOGroovyMethods.getText(in, GroovyPageParser.GROOVY_SOURCE_CHAR_ENCODING);
            //System.out.println(groovySource);
            scriptClass = groovyClassLoader.parseClass(groovySource, name);
        }
        catch (CompilationFailedException e) {
            logger.error("Compilation error compiling GSP [" + name + "]:" + e.getMessage(), e);

            int lineNumber = ExceptionUtils.extractLineNumber(e);

            final int[] lineMappings = metaInfo.getLineNumbers();
            if (lineNumber > 0 && lineNumber < lineMappings.length) {
                lineNumber = lineMappings[lineNumber - 1];
            }
            String relativePageName = DefaultErrorsPrinter.makeRelativeIfPossible(pageName);
            throw new GroovyPagesException("Could not parse script [" + relativePageName + "]: " + e.getMessage(), e, lineNumber, pageName);
        }
        catch (IOException e) {
            String relativePageName = DefaultErrorsPrinter.makeRelativeIfPossible(pageName);
            throw new GroovyPagesException("IO exception parsing script [" + relativePageName + "]: " + e.getMessage(), e);
        }
        GroovyPagesMetaUtils.registerMethodMissingForGSP(scriptClass, this.tagLibraryLookup);

        return scriptClass;
    }

    private synchronized GroovyClassLoader findOrInitGroovyClassLoader() {
        if (!(this.classLoader instanceof GroovyPageClassLoader)) {
            this.classLoader = initGroovyClassLoader(this.classLoader);
        }
        return (GroovyClassLoader) this.classLoader;
    }

    /**
     * Creates a GroovyPageMetaInfo instance from the given Parse object, and initialises it with the the specified
     * last modifed date and InputStream
     *
     * @param parse The Parse object
     * @param in The InputStream instance
     * @return A GroovyPageMetaInfo instance
     */
    private GroovyPageMetaInfo createPageMetaInfo(GroovyPageParser parse, InputStream in) {
        GroovyPageMetaInfo pageMeta = new GroovyPageMetaInfo();
        pageMeta.setGrailsApplication(this.grailsApplication);
        pageMeta.setJspTagLibraryResolver(this.jspTagLibraryResolver);
        pageMeta.setTagLibraryLookup(this.tagLibraryLookup);
        pageMeta.setContentType(parse.getContentType());
        pageMeta.setLineNumbers(parse.getLineNumberMatrix());
        pageMeta.setJspTags(parse.getJspTags());

        pageMeta.setStaticCodecName(parse.getStaticCodecDirectiveValue());
        pageMeta.setExpressionCodecName(parse.getExpressionCodecDirectiveValue());
        pageMeta.setOutCodecName(parse.getOutCodecDirectiveValue());
        pageMeta.setTaglibCodecName(parse.getTaglibCodecDirectiveValue());
        pageMeta.setCompileStaticMode(parse.isCompileStaticMode());
        pageMeta.setModelFieldsMode(parse.isModelFieldsMode());

        pageMeta.initialize();
        // just return groovy and don't compile if asked
        if (GrailsUtil.isDevelopmentEnv()) {
            pageMeta.setGroovySource(in);
        }

        if (dumpLineNumbersTo != null) {
            String fileName = parse.getClassName() + GroovyPageMetaInfo.LINENUMBERS_DATA_POSTFIX;
            File file = new File(dumpLineNumbersTo, fileName);
            try {
                parse.writeLineNumbers(file);
            }
            catch (IOException ignored) {
                if (file.exists()) {
                    file.delete();
                }
            }
        }

        return pageMeta;
    }

    /**
     * Establishes the name to use for the given resource
     *
     * @param res The Resource to calculate the name for
     * @param pageName The name of the page, can be null, in which case method responsible for calculation
     *
     * @return The name as a String
     */
    protected String establishPageName(Resource res, String pageName) {
        if (res == null) {
            return generateTemplateName();
        }

        try {
            String name = pageName != null ? pageName : res.getURL().getPath();
            // As the name take the first / off and then replace all characters that aren't
            // a word character or a digit with an underscore
            if (name.startsWith("/")) {
                name = name.substring(1);
            }
            return name.replaceAll("[^\\w\\d]", "_");
        }
        catch (IllegalStateException e) {
            return generateTemplateName();
        }
        catch (IOException ioex) {
            return generateTemplateName();
        }
    }

    /**
     * Generates the template name to use if it cannot be established from the Resource
     *
     * @return The template name
     */
    private String generateTemplateName() {
        return GENERATED_GSP_NAME_PREFIX + this.scriptNameCount.incrementAndGet();
    }

    /**
     * Sets the ResourceLoader from the ApplicationContext
     *
     * @param applicationContext The ApplicationContext
     * @throws BeansException Thrown when an error occurs with the ApplicationContext
     */
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (applicationContext.containsBean(GrailsApplication.APPLICATION_ID)) {
            this.grailsApplication = applicationContext.getBean(GrailsApplication.APPLICATION_ID, GrailsApplication.class);
            Config config = this.grailsApplication.getConfig();
            this.gspEncoding = config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_ENCODING,
                    System.getProperty("file.encoding", GroovyPageParser.DEFAULT_ENCODING));

            this.tagInvocationContextCustomizers = GrailsFactoriesLoader.loadFactories(TagInvocationContextCustomizer.class,
                    getClass().getClassLoader());
            this.tagInvocationContextCustomizers.addAll(applicationContext.getBeansOfType(TagInvocationContextCustomizer.class).values());
        }
    }

    /**
     * Returns the path to the view of the relative URI within the Grails views directory
     *
     * @param relativeUri The relative URI
     * @return The path of the URI within the Grails view directory
     */
    protected String getUriWithinGrailsViews(String relativeUri) {
        StringBuilder buf = new StringBuilder();
        String[] tokens;
        if (relativeUri.startsWith("/")) {
            relativeUri = relativeUri.substring(1);
        }

        if (relativeUri.indexOf('/') > -1) {
            tokens = relativeUri.split("/");
        }
        else {
            tokens = new String[] { relativeUri };
        }

        buf.append(DefaultGroovyPageLocator.PATH_TO_WEB_INF_VIEWS);
        for (String token : tokens) {
            buf.append('/').append(token);
        }
        if (!relativeUri.endsWith(GroovyPage.EXTENSION)) {
            buf.append(GroovyPage.EXTENSION);
        }
        return buf.toString();
    }

    /**
     * Clears the page cache. Views will be re-compiled.
     */
    public void clearPageCache() {
        for (Iterator>> it = this.pageCache.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry> entry = it.next();
            GroovyPageMetaInfo metaInfo = entry.getValue().getValue();
            if (metaInfo != null) {
                metaInfo.removePageMetaClass();
            }
            it.remove();
        }
    }

    public boolean isCacheResources() {
        return this.cacheResources;
    }

    public void setCacheResources(boolean cacheResources) {
        this.cacheResources = cacheResources;
    }

    public Map> getDomainClassMap() {
        if (this.cachedDomainsWithoutPackage != null) {
            return this.cachedDomainsWithoutPackage;
        }
        return createDomainClassMap();
    }

    /**
     * The domainClassMap is used in GSP binding to "auto-import" domain classes in packages without package prefix.
     * real imports aren't used, instead each class is added to the binding
     *
     * This feature has existed earlier, the code has just been refactored and moved to GroovyPagesTemplateEngine
     * to prevent using the static cache that was used previously.
     */
    private Map> createDomainClassMap() {
        Map> domainsWithoutPackage = new HashMap<>();
        if (this.grailsApplication != null) {
            GrailsClass[] domainClasses = this.grailsApplication.getArtefacts(DomainClassArtefactHandler.TYPE);
            for (GrailsClass domainClass : domainClasses) {
                final Class theClass = domainClass.getClazz();
                domainsWithoutPackage.put(theClass.getName(), theClass);
            }
        }
        return domainsWithoutPackage;
    }

    @Override
    public void setBeanClassLoader(ClassLoader beanClassLoader) {
        // support passing BeanClassLoader as parent classloader for templates
        // don't set the classLoader field if it already has an explicit value
        if (beanClassLoader != null && this.classLoader == null) {
            this.classLoader = beanClassLoader;
        }
    }

    public String getGspEncoding() {
        return this.gspEncoding;
    }

    private class GroovyPagesTemplateEngineCacheEntry extends CacheEntry {

        private final String pageName;

        GroovyPagesTemplateEngineCacheEntry(String pageName) {
            this.pageName = pageName;
        }

        @Override
        protected boolean hasExpired(long timeout, Object cacheRequestObject) {
            GroovyPageMetaInfo meta = getValue();
            Resource resource = (Resource) cacheRequestObject;
            return meta == null || isGroovyPageReloadable(resource, meta);
        }

        @Override
        protected GroovyPageMetaInfo updateValue(GroovyPageMetaInfo oldValue, Callable updater, Object cacheRequestObject)
                throws Exception {
            if (oldValue != null) {
                oldValue.removePageMetaClass();
            }
            Resource resource = (Resource) cacheRequestObject;
            return buildPageMetaInfo(resource, this.pageName);
        }

    }

    private static class GroovyPagesTemplateEngineCallable implements Callable> {

        private final CacheEntry cacheEntry;

        GroovyPagesTemplateEngineCallable(CacheEntry cacheEntry) {
            this.cacheEntry = cacheEntry;
        }

        @Override
        public CacheEntry call() throws Exception {
            return this.cacheEntry;
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy