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

org.grails.gsp.GroovyPage 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.Writer;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.GroovyObject;
import groovy.lang.Script;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.runtime.InvokerHelper;

import grails.core.GrailsApplication;
import grails.util.CollectionUtils;

import org.grails.buffer.GrailsPrintWriter;
import org.grails.encoder.Encoder;
import org.grails.exceptions.ExceptionUtils;
import org.grails.gsp.jsp.JspTag;
import org.grails.gsp.jsp.JspTagLib;
import org.grails.gsp.jsp.TagLibraryResolver;
import org.grails.taglib.AbstractTemplateVariableBinding;
import org.grails.taglib.GrailsTagException;
import org.grails.taglib.GroovyPageAttributes;
import org.grails.taglib.TagBodyClosure;
import org.grails.taglib.TagInvocationContext;
import org.grails.taglib.TagInvocationContextCustomizer;
import org.grails.taglib.TagLibraryLookup;
import org.grails.taglib.TagOutput;
import org.grails.taglib.encoder.OutputContext;
import org.grails.taglib.encoder.OutputEncodingStack;
import org.grails.taglib.encoder.OutputEncodingStackAttributes;
import org.grails.taglib.encoder.WithCodecHelper;

/**
 * 

NOTE: Based on work done by on the GSP standalone project (https://gsp.dev.java.net/) *

*

Base class for a GroovyPage (at the moment there is nothing in here but could be useful for * providing utility methods etc.

* * @author Troy Heninger * @author Graeme Rocher * @author Lari Hotari */ public abstract class GroovyPage extends Script { private static final Log logger = LogFactory.getLog(GroovyPage.class); private static final String APPLY_CODEC_TAG_NAME = "applyCodec"; public static final String ENCODE_AS_ATTRIBUTE_NAME = "encodeAs"; public static final Closure EMPTY_BODY_CLOSURE = TagOutput.EMPTY_BODY_CLOSURE; public static final String OUT = "out"; public static final String EXPRESSION_OUT = "expressionOut"; public static final String EXPRESSION_OUT_STATEMENT = EXPRESSION_OUT; // "getCodecOut()"; public static final String OUT_STATEMENT = OUT; // "getOut()"; public static final String CODEC_VARNAME = "Codec"; public static final String PLUGIN_CONTEXT_PATH = "pluginContextPath"; public static final String EXTENSION = ".gsp"; public static final String DEFAULT_NAMESPACE = "g"; public static final String PAGE_SCOPE = "pageScope"; public static final Collection RESERVED_NAMES = CollectionUtils.newSet( OUT, EXPRESSION_OUT, CODEC_VARNAME, PLUGIN_CONTEXT_PATH, PAGE_SCOPE); private static final String BINDING = "binding"; private static final String BLANK_STRING = ""; @SuppressWarnings("rawtypes") private Map jspTags = Collections.emptyMap(); private TagLibraryResolver jspTagLibraryResolver; protected TagLibraryLookup gspTagLibraryLookup; private String[] htmlParts; private Set htmlPartsSet; private GrailsPrintWriter out; private GrailsPrintWriter staticOut; private GrailsPrintWriter expressionOut; private OutputEncodingStack outputStack; protected OutputContext outputContext; private String pluginContextPath; private Encoder rawEncoder; private final List> bodyClosures = new ArrayList<>(15); private List tagInvocationContextCustomizers = new ArrayList<>(); public GroovyPage() { init(); } protected void init() { // do nothing } public final Writer getOut() { return this.out; } public final Writer getExpressionOut() { return this.expressionOut; } public void setOut(Writer newWriter) { throw new IllegalStateException("Setting out in page isn't allowed."); } public void initRun(Writer target, OutputContext outputContext, GroovyPageMetaInfo metaInfo) { OutputEncodingStackAttributes.Builder attributesBuilder = new OutputEncodingStackAttributes.Builder(); if (metaInfo != null) { setJspTags(metaInfo.getJspTags()); setJspTagLibraryResolver(metaInfo.getJspTagLibraryResolver()); setGspTagLibraryLookup(metaInfo.getTagLibraryLookup()); setHtmlParts(metaInfo.getHtmlParts()); setPluginContextPath(metaInfo.getPluginPath()); setTagInvocationContextCustomizers(metaInfo.getTagInvocationContextCustomizers()); attributesBuilder.outEncoder(metaInfo.getOutEncoder()); attributesBuilder.staticEncoder(metaInfo.getStaticEncoder()); attributesBuilder.expressionEncoder(metaInfo.getExpressionEncoder()); attributesBuilder.defaultTaglibEncoder(metaInfo.getTaglibEncoder()); applyModelFieldsFromBinding(metaInfo.getModelFields()); } attributesBuilder.allowCreate(true).topWriter(target).autoSync(false).pushTop(true); attributesBuilder.outputContext(outputContext); attributesBuilder.inheritPreviousEncoders(false); this.outputStack = OutputEncodingStack.currentStack(attributesBuilder.build()); this.out = this.outputStack.getOutWriter(); this.staticOut = this.outputStack.getStaticWriter(); this.expressionOut = this.outputStack.getExpressionWriter(); this.outputContext = outputContext; if (outputContext != null) { outputContext.setCurrentWriter(this.out); GrailsApplication grailsApplication = outputContext.getGrailsApplication(); if (grailsApplication != null) { this.rawEncoder = WithCodecHelper.lookupEncoder(grailsApplication, "Raw"); } } setVariableDirectly(OUT, this.out); setVariableDirectly(EXPRESSION_OUT, this.expressionOut); } private void applyModelFieldsFromBinding(Iterable modelFields) { for (Field field : modelFields) { try { Object value = getProperty(field.getName()); if (value != null) { field.set(this, value); } } catch (IllegalAccessException e) { throw new GroovyPagesException("Error setting model field '" + field.getName() + "'", e, -1, getGroovyPageFileName()); } } } public Object raw(Object value) { if (this.rawEncoder == null) { return InvokerHelper.invokeMethod(value, "encodeAsRaw", null); } return this.rawEncoder.encode(value); } @SuppressWarnings("unchecked") private void setVariableDirectly(String name, Object value) { Binding binding = getBinding(); if (binding instanceof AbstractTemplateVariableBinding) { ((AbstractTemplateVariableBinding) binding).setVariableDirectly(name, value); } else { binding.getVariables().put(name, value); } } public String getPluginContextPath() { return this.pluginContextPath != null ? this.pluginContextPath : BLANK_STRING; } public void setPluginContextPath(String pluginContextPath) { this.pluginContextPath = pluginContextPath; } public void cleanup() { this.outputStack.pop(true); } public final void createClosureForHtmlPart(int partNumber, int bodyClosureIndex) { final String htmlPart = this.htmlParts[partNumber]; setBodyClosure(bodyClosureIndex, new TagOutput.ConstantClosure(htmlPart)); } public final void setBodyClosure(int index, Closure bodyClosure) { while (index >= this.bodyClosures.size()) { this.bodyClosures.add(null); } this.bodyClosures.set(index, bodyClosure); } public final Closure getBodyClosure(int index) { if (index >= 0) { return this.bodyClosures.get(index); } return null; } /** * Sets the JSP tag library resolver to use to resolve JSP tags * * @param jspTagLibraryResolver The JSP tag resolve */ public void setJspTagLibraryResolver(TagLibraryResolver jspTagLibraryResolver) { this.jspTagLibraryResolver = jspTagLibraryResolver; } /** * Sets the GSP tag library lookup class * * @param gspTagLibraryLookup The class used to lookup a GSP tag library */ public void setGspTagLibraryLookup(TagLibraryLookup gspTagLibraryLookup) { this.gspTagLibraryLookup = gspTagLibraryLookup; } /** * Obtains a reference to the JSP tag library resolver instance * * @return The JSP TagLibraryResolver instance */ TagLibraryResolver getTagLibraryResolver() { return this.jspTagLibraryResolver; } /** * Set the customizers * @param tagInvocationContextCustomizers the customizer * @since 2022.0.0 */ void setTagInvocationContextCustomizers(List tagInvocationContextCustomizers) { this.tagInvocationContextCustomizers = tagInvocationContextCustomizers; } /** * In the development environment this method is used to evaluate expressions and improve error reporting * * @param exprText The expression text * @param lineNumber The line number * @param outerIt The other reference to the variable 'it' * @param evaluator The expression evaluator * @return The result */ public Object evaluate(String exprText, int lineNumber, Object outerIt, Closure evaluator) { try { return evaluator.call(outerIt); } catch (Exception e) { throw new GroovyPagesException("Error evaluating expression [" + exprText + "] on line [" + lineNumber + "]: " + e.getMessage(), e, lineNumber, getGroovyPageFileName()); } } public abstract String getGroovyPageFileName(); @Override public Object getProperty(String property) { if (OUT.equals(property)) { return this.out; } if (EXPRESSION_OUT.equals(property)) { return this.expressionOut; } // in GSP we assume if a property doesn't exist that // it is null rather than throw an error this works nicely // with the Groovy Truth if (BINDING.equals(property)) { return getBinding(); } return resolveProperty(property); } protected Object resolveProperty(String property) { Object value = getBinding().getVariable(property); if (value != null) { return value; } // check if a taglib can be found value = lookupTagDispatcher(property); if (value == null) { value = lookupJspTagLib(property); } if (value != null) { // cache lookup for next execution setVariableDirectly(property, value); } return value; } protected Object lookupTagDispatcher(String namespace) { return this.gspTagLibraryLookup != null ? this.gspTagLibraryLookup.lookupNamespaceDispatcher(namespace) : null; } protected JspTagLib lookupJspTagLib(String jspTagLibName) { String uri = (String) this.jspTags.get(jspTagLibName); if (uri != null) { TagLibraryResolver tagResolver = getTagLibraryResolver(); return tagResolver.resolveTagLibrary(uri); } return null; } /* Static call for jsp tags resolution * * @param uri tag uri * @param name tag name * @return resolved tag if any */ public JspTag getJspTag(String uri, String name) { if (this.jspTagLibraryResolver == null) { return null; } JspTagLib tagLib = this.jspTagLibraryResolver.resolveTagLibrary(uri); return tagLib != null ? tagLib.getTag(name) : null; } /** * Attempts to invoke a dynamic tag * * @param tagName The name of the tag * @param tagNamespace The taglib's namespace * @param lineNumber GSP source lineNumber * @param attrs The tags attributes * @param bodyClosureIndex The index of the body variable */ @SuppressWarnings({ "unchecked", "rawtypes" }) public final void invokeTag(String tagName, String tagNamespace, int lineNumber, Map attrs, int bodyClosureIndex) { // Handling custom namespace and tags TagInvocationContext tagInvocationContext = new TagInvocationContext(tagNamespace, tagName, attrs); applyTagInvocationContextCustomizers(tagInvocationContext); String theNamespace = tagInvocationContext.getNamespace(); String theTagName = tagInvocationContext.getTagName(); Map theAttrs = tagInvocationContext.getAttrs(); Closure body = getBodyClosure(bodyClosureIndex); try { GroovyObject tagLib = getTagLib(theNamespace, theTagName); if (tagLib != null || (this.gspTagLibraryLookup != null && this.gspTagLibraryLookup.hasNamespace(theNamespace))) { if (tagLib != null) { boolean returnsObject = this.gspTagLibraryLookup.doesTagReturnObject(theNamespace, theTagName); Object tagLibClosure = tagLib.getProperty(theTagName); if (tagLibClosure instanceof Closure) { Map encodeAsForTag = this.gspTagLibraryLookup.getEncodeAsForTag(theNamespace, theTagName); invokeTagLibClosure(theTagName, theNamespace, (Closure) tagLibClosure, theAttrs, body, returnsObject, encodeAsForTag); } else { throw new GrailsTagException("Tag [" + theTagName + "] does not exist in tag library [" + tagLib.getClass().getName() + "]", getGroovyPageFileName(), lineNumber); } } else { throw new GrailsTagException("Tag [" + theTagName + "] does not exist. No tag library found for namespace: " + theNamespace, getGroovyPageFileName(), lineNumber); } } else { this.staticOut.append('<').append(theNamespace).append(':').append(theTagName); for (Object o : theAttrs.entrySet()) { Map.Entry entry = (Map.Entry) o; this.staticOut.append(' '); this.staticOut.append(entry.getKey()).append('='); String value = String.valueOf(entry.getValue()); // handle attribute value quotes & possible escaping " -> " boolean containsQuotes = (value.indexOf('"') > -1); boolean containsSingleQuote = (value.indexOf('\'') > -1); if (containsQuotes && !containsSingleQuote) { this.staticOut.append('\'').append(value).append('\''); } else if (containsQuotes & containsSingleQuote) { this.staticOut.append('\"').append(value.replaceAll("\"", """)).append('\"'); } else { this.staticOut.append('\"').append(value).append('\"'); } } if (body == null) { this.staticOut.append("/>"); } else { this.staticOut.append('>'); Object bodyOutput = body.call(); if (bodyOutput != null) { this.staticOut.print(bodyOutput); } this.staticOut.append("'); } } } catch (Throwable e) { if (logger.isTraceEnabled()) { logger.trace("Full exception for problem at " + getGroovyPageFileName() + ":" + lineNumber, e); } // The capture* tags are internal tags and not to be displayed to the user // hence we don't wrap the exception and simple rethrow it if (theTagName.matches("capture(Body|Head|Meta|Title|Component)")) { RuntimeException rte = ExceptionUtils.getFirstRuntimeException(e); if (rte == null) { throwRootCause(theTagName, theNamespace, lineNumber, e); } else { throw rte; } } else { throwRootCause(theTagName, theNamespace, lineNumber, e); } } } private void invokeTagLibClosure(String tagName, String tagNamespace, Closure tagLibClosure, Map attrs, Closure body, boolean returnsObject, Map defaultEncodeAs) { Closure tag = (Closure) tagLibClosure.clone(); if (!(attrs instanceof GroovyPageAttributes)) { attrs = new GroovyPageAttributes(attrs); } ((GroovyPageAttributes) attrs).setGspTagSyntaxCall(true); boolean encodeAsPushedToStack = false; try { Map codecSettings = TagOutput.createCodecSettings(tagNamespace, tagName, attrs, defaultEncodeAs); if (codecSettings != null) { this.outputStack.push(WithCodecHelper.createOutputStackAttributesBuilder(codecSettings, this.outputContext.getGrailsApplication()) .build()); encodeAsPushedToStack = true; } Object tagresult = null; switch (tag.getParameterTypes().length) { case 1: tagresult = tag.call(new Object[] { attrs }); outputTagResult(returnsObject, tagresult); if (body != null && body != TagOutput.EMPTY_BODY_CLOSURE) { body.call(); } break; case 2: tagresult = tag.call(new Object[] { attrs, (body != null) ? body : TagOutput.EMPTY_BODY_CLOSURE }); outputTagResult(returnsObject, tagresult); break; } } finally { if (encodeAsPushedToStack) { this.outputStack.pop(); } } } private void outputTagResult(boolean returnsObject, Object tagresult) { if (returnsObject && tagresult != null && !(tagresult instanceof Writer)) { if (tagresult instanceof String && isHtmlPart((String) tagresult)) { this.staticOut.print(tagresult); } else { this.outputStack.getTaglibWriter().print(tagresult); } } } private void throwRootCause(String tagName, String tagNamespace, int lineNumber, Throwable e) { Throwable cause = ExceptionUtils.getRootCause(e); if (cause instanceof GrailsTagException) { // catch and rethrow with context throw new GrailsTagException(cause.getMessage(), getGroovyPageFileName(), lineNumber); } throw new GrailsTagException("Error executing tag <" + tagNamespace + ":" + tagName + ">: " + e.getMessage(), e, getGroovyPageFileName(), lineNumber); } private GroovyObject getTagLib(String namespace, String tagName) { return TagOutput.lookupCachedTagLib(this.gspTagLibraryLookup, namespace, tagName); } /** * Return whether the given name cannot be used within the binding of a GSP * * @param name True if it can't * @return A boolean true or false */ public static final boolean isReservedName(String name) { return RESERVED_NAMES.contains(name); } public final void printHtmlPart(final int partNumber) { this.staticOut.write(this.htmlParts[partNumber]); } /** * Sets the JSP tags used by this GroovyPage instance * * @param jspTags The JSP tags used */ @SuppressWarnings("rawtypes") public void setJspTags(Map jspTags) { this.jspTags = jspTags; } public String[] getHtmlParts() { return this.htmlParts; } protected boolean isHtmlPart(String htmlPart) { return this.htmlPartsSet != null && htmlPart != null && this.htmlPartsSet.contains(System.identityHashCode(htmlPart)); } public void setHtmlParts(String[] htmlParts) { this.htmlParts = htmlParts; this.htmlPartsSet = new HashSet(); if (htmlParts != null) { for (String htmlPart : htmlParts) { if (htmlPart != null) { this.htmlPartsSet.add(System.identityHashCode(htmlPart)); } } } } public final OutputEncodingStack getOutputStack() { return this.outputStack; } public OutputContext getOutputContext() { return this.outputContext; } public void applyTagInvocationContextCustomizers(TagInvocationContext tagInvocationContext) { for (TagInvocationContextCustomizer customizer : this.tagInvocationContextCustomizers) { customizer.customize(tagInvocationContext); } } public final void registerSitemeshPreprocessMode() { /* TODO: grails-gsp refactoring if (request == null) { return; } GSPSitemeshPage page = (GSPSitemeshPage) request.getAttribute(GrailsLayoutView.GSP_SITEMESH_PAGE); if (page != null) { page.setUsed(true); } */ } public final void createTagBody(int bodyClosureIndex, Closure bodyClosure) { TagBodyClosure tagBody = new TagBodyClosure(this, this.outputContext, bodyClosure, true); setBodyClosure(bodyClosureIndex, tagBody); } public void changeItVariable(Object value) { setVariableDirectly("it", value); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy