You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@grails.apache.org by "jdaugherty (via GitHub)" <gi...@apache.org> on 2025/07/02 11:45:51 UTC
[PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
jdaugherty opened a new pull request, #14875:
URL: https://github.com/apache/grails-core/pull/14875
See discussion [1]. This will likely fix:
* #14193
* #13988
* #14197
* #13849
Please note that the sitemesh license terms has these sections:
* `Products derived from this software may not be called "OpenSymphony" or "SiteMesh", nor may "OpenSymphony" or "SiteMesh" appear in their name`
* `The end-user documentation included with the redistribution, if any, must include the following acknowledgment: "This product includes software developed by the OpenSymphony Group (http://www.opensymphony.com/)."`
For this reason, I've renamed most of our classes away from "sitemesh" to a variant of Grails Layout & updated the end user documentation.
[1] https://lists.apache.org/thread/8lynrl0yd6ykn749md1g2wjb8jph3s5l
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#issuecomment-3028886132
I don't see that on first glance of this PR. If you can take a look at this review to help me find that, I think decoupling by introducing interfaces/intermediate classes in Grails so in the future we can remove sitemesh2 is very reasonable.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#issuecomment-3033973120
I had to revert the cast that shows an error in intellij. Otherwise, a large amount of tests fail with a cast exception.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "matrei (via GitHub)" <gi...@apache.org>.
matrei commented on code in PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#discussion_r2182796895
##########
grails-testing-support-web/src/main/groovy/org/grails/testing/spock/WebCleanupSpecInterceptor.groovy:
##########
@@ -29,7 +31,24 @@ class WebCleanupSpecInterceptor implements IMethodInterceptor {
@Override
void intercept(IMethodInvocation invocation) throws Throwable {
- ConvertersConfigurationHolder.clear()
- invocation.proceed()
+ try {
+ invocation.proceed()
+ }
+ finally {
+ ConvertersConfigurationHolder.clear()
+
+ GrailsWebUnitTest test = (GrailsWebUnitTest)invocation.instance
+ if(test.purgeTagLibMetaClass) {
Review Comment:
Align on space after `if`?
##########
gradle/test-config.gradle:
##########
@@ -73,3 +73,31 @@ tasks.withType(Test).configureEach {
}
}
+if(hasProperty('testBisect')) {
+ tasks.withType(Test).configureEach {
+ maxParallelForks = 1
+ forkEvery = 0
+
+ develocity {
+ testDistribution {
+ enabled.set(false)
+ retryInSameJvm.set(true)
+ }
+ testRetry {
+ maxRetries = 1
+ maxFailures = 1
+ failOnPassedAfterRetry = true
+ }
+ }
+
+ beforeTest { testDescriptor ->
+ logger.info "STARTED TEST: ${testDescriptor.className} > ${testDescriptor.name}"
Review Comment:
- Align on parentheses for method calls?
- Align on lazily evaluated log messages?
##########
grails-fields/src/test/groovy/grails/plugin/formfields/taglib/EmbeddedPropertiesSpec.groovy:
##########
@@ -41,7 +41,7 @@ class EmbeddedPropertiesSpec extends AbstractFormFieldsTagLibSpec implements Tag
mockFormFieldsTemplateService.getWidgetPrefix() >> 'input-'
tagLib.formFieldsTemplateService = mockFormFieldsTemplateService
- mockEmbeddedSitemeshLayout tagLib
+ mockEmbeddedGrailsLayout tagLib
Review Comment:
Align on parentheses for method calls?
##########
grails-testing-support-web/src/main/groovy/org/grails/testing/spock/WebCleanupSpecInterceptor.groovy:
##########
@@ -29,7 +31,24 @@ class WebCleanupSpecInterceptor implements IMethodInterceptor {
@Override
void intercept(IMethodInvocation invocation) throws Throwable {
- ConvertersConfigurationHolder.clear()
- invocation.proceed()
+ try {
+ invocation.proceed()
+ }
+ finally {
+ ConvertersConfigurationHolder.clear()
+
+ GrailsWebUnitTest test = (GrailsWebUnitTest)invocation.instance
+ if(test.purgeTagLibMetaClass) {
+ try {
+ LazyTagLibraryLookup lookup = test.grailsApplication.mainContext.getBean(LazyTagLibraryLookup)
+ if(lookup) {
+ lookup.cleanTagLibsMetaClass()
+ }
+ }
+ catch(ignored) {
Review Comment:
Align on space after `catch`?
##########
grails-fields/src/test/groovy/grails/plugin/formfields/taglib/WithTagSpec.groovy:
##########
@@ -41,7 +41,7 @@ class WithTagSpec extends AbstractFormFieldsTagLibSpec implements TagLibUnitTest
mockFormFieldsTemplateService.getWidgetPrefix() >> 'input-'
tagLib.formFieldsTemplateService = mockFormFieldsTemplateService
- mockEmbeddedSitemeshLayout tagLib
+ mockEmbeddedGrailsLayout tagLib
Review Comment:
Align on parentheses for method calls?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -247,6 +255,44 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertOutputEquals '<th id="1" class="sortable" ><a href="/custompathCustomNamespace/mockcontroller/list?sort=id&order=asc">ID</a></th>', template
}
+ void testPageProperty() {
+
+ def template = '<g:pageProperty name="foo.bar" />'
+
+ def head = ""
+ TokenizedHTMLPage page = new TokenizedHTMLPage([] as char[], new CharArray(0), new CharArray(0))
+ request[RequestConstants.PAGE] = page
+
+ page.addProperty("foo.bar", "good")
+
+ assertOutputEquals "good", template
+
+ template = '<g:pageProperty name="foo.bar" writeEntireProperty="true" />'
+ assertOutputEquals " bar=\"good\"", template
+ }
+
+ void testIfPageProperty() {
+ def template = '<g:ifPageProperty name="foo.bar">Hello</g:ifPageProperty>'
+
+ TokenizedHTMLPage page = new TokenizedHTMLPage([] as char[], new CharArray(0), new CharArray(0))
+ request[RequestConstants.PAGE] = page
+
+ page.addProperty("foo.bar", "true")
+
+ assertOutputEquals "Hello", template
+
+ template = '<g:ifPageProperty name="page.contentbuffer">Hello 2</g:ifPageProperty>'
+
+ GSPGrailsLayoutPage smpage = new GSPGrailsLayoutPage()
Review Comment:
`def`?
##########
gradle/test-config.gradle:
##########
@@ -73,3 +73,31 @@ tasks.withType(Test).configureEach {
}
}
+if(hasProperty('testBisect')) {
+ tasks.withType(Test).configureEach {
+ maxParallelForks = 1
+ forkEvery = 0
+
+ develocity {
+ testDistribution {
+ enabled.set(false)
+ retryInSameJvm.set(true)
+ }
+ testRetry {
+ maxRetries = 1
+ maxFailures = 1
+ failOnPassedAfterRetry = true
+ }
+ }
+
+ beforeTest { testDescriptor ->
+ logger.info "STARTED TEST: ${testDescriptor.className} > ${testDescriptor.name}"
+ }
+
+ afterSuite { desc, result ->
+ if (!desc.parent) {
+ logger.info("FINISHED - ${result.failedTestCount == 0 && result.testCount > 1 ? 'PASSED' : 'FAILED'} - ${result.testCount} tests executed, ${result.failedTestCount} failed")
Review Comment:
Align on lazily evaluated log messages?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/GrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,239 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.RequestConstants
+import grails.artefact.TagLibrary
+import grails.gsp.TagLib
+import groovy.transform.CompileStatic
+import jakarta.servlet.http.HttpServletRequest
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.GrailsPrintWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.encoder.CodecLookup
+import org.grails.encoder.Encoder
+import org.grails.gsp.compiler.GrailsLayoutPreprocessor
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+
+/**
+ * Internal Grails Layout pre-processor tags.
+ *
+ * @author Graeme Rocher
+ * @since 1.2
+ */
+@CompileStatic
+@TagLib
+class GrailsLayoutTagLib implements RequestConstants, TagLibrary {
+ protected static final String GSP_GRAILS_LAYOUT_PAGE = 'org.apache.grails.web.layout.EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE'
+
+ static String namespace = 'grailsLayout'
+ CodecLookup codecLookup
+
+ def captureTagContent(GrailsPrintWriter writer, String tagname, Map attrs, Object body, boolean noEndTagForEmpty=false) {
+ def content = null
+ if (body != null) {
+ if (body instanceof Closure) {
+ content = body()
+ }
+ else {
+ content = body
+ }
+ }
+
+ if (content instanceof StreamCharBuffer) {
+ content.setPreferSubChunkWhenWritingToOtherBuffer(true)
+ }
+ writer << '<'
+ writer << tagname
+ def useXmlClosingForEmptyTag = false
+ if (attrs) {
+ def xmlClosingString = attrs.remove(GrailsLayoutPreprocessor.XML_CLOSING_FOR_EMPTY_TAG_ATTRIBUTE_NAME)
+ if (xmlClosingString=='/') {
+ useXmlClosingForEmptyTag = true
+ }
+ Encoder htmlEncoder = codecLookup?.lookupEncoder('HTML')
+ attrs.each { k, v ->
+ writer << ' '
+ writer << k
+ writer << '="'
+ writer << (htmlEncoder != null ? htmlEncoder.encode(v) : v)
+ writer << '"'
+ }
+ }
+
+ if (content) {
+ writer << '>'
+ // the following row must be written separately (append StreamCharBuffer gets appended as subchunk)
+ writer << content
+ writer << '</'
+ writer << tagname
+ writer << '>'
+ }
+ else {
+ if (!useXmlClosingForEmptyTag) {
+ writer << '>'
+ // in valid HTML , closing of an empty tag depends on the element name
+ // for empty title, the tag must be closed properly
+ // for empty meta tag shouldn't be closed at all, see GRAILS-5696
+ if (!noEndTagForEmpty) {
+ writer << '</'
+ writer << tagname
+ writer << '>'
+ }
+ }
+ else {
+ // XML / XHTML empty tag
+ writer << '/>'
+ }
+ }
+ content
+ }
+
+ def StreamCharBuffer wrapContentInBuffer(Object content) {
+ if (content instanceof Closure) {
+ content = content()
+ }
+ if (!(content instanceof StreamCharBuffer)) {
+ // the body closure might be a string constant, so wrap it in a StreamCharBuffer in that case
+ FastStringWriter stringWriter=new FastStringWriter()
+ stringWriter.print((Object)content)
+ StreamCharBuffer newbuffer = stringWriter.buffer
+ newbuffer.setPreferSubChunkWhenWritingToOtherBuffer(true)
+ return newbuffer
+ } else {
+ return (StreamCharBuffer)content
+ }
+ }
+
+ /**
+ * Captures the <head> tag.
+ */
+ Closure captureHead = { Map attrs, body ->
+ def content = captureTagContent(out, 'head', attrs, body)
+
+ if (content != null) {
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
+ if (smpage) {
+ smpage.setHeadBuffer(wrapContentInBuffer(content))
+ }
+ }
+ }
+
+ protected GSPGrailsLayoutPage findGSPGrailsLayoutPage(HttpServletRequest request) {
+ (GSPGrailsLayoutPage)request.getAttribute(GSP_GRAILS_LAYOUT_PAGE)
+ }
+
+ /**
+ * Allows passing of parameters to Grails layout.<br/>
+ *
+ * <grailsLayout:parameter name="foo" value="bar" />
+ */
+ Closure parameter = { Map attrs, body ->
+ GSPGrailsLayoutPage smpage=findGSPGrailsLayoutPage(request)
Review Comment:
- `def`?
- Spacing?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -247,6 +255,44 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertOutputEquals '<th id="1" class="sortable" ><a href="/custompathCustomNamespace/mockcontroller/list?sort=id&order=asc">ID</a></th>', template
}
+ void testPageProperty() {
+
+ def template = '<g:pageProperty name="foo.bar" />'
+
+ def head = ""
+ TokenizedHTMLPage page = new TokenizedHTMLPage([] as char[], new CharArray(0), new CharArray(0))
+ request[RequestConstants.PAGE] = page
+
+ page.addProperty("foo.bar", "good")
+
+ assertOutputEquals "good", template
+
+ template = '<g:pageProperty name="foo.bar" writeEntireProperty="true" />'
+ assertOutputEquals " bar=\"good\"", template
+ }
+
+ void testIfPageProperty() {
+ def template = '<g:ifPageProperty name="foo.bar">Hello</g:ifPageProperty>'
+
+ TokenizedHTMLPage page = new TokenizedHTMLPage([] as char[], new CharArray(0), new CharArray(0))
+ request[RequestConstants.PAGE] = page
+
+ page.addProperty("foo.bar", "true")
+
+ assertOutputEquals "Hello", template
+
+ template = '<g:ifPageProperty name="page.contentbuffer">Hello 2</g:ifPageProperty>'
+
+ GSPGrailsLayoutPage smpage = new GSPGrailsLayoutPage()
+ request[RequestConstants.PAGE] = smpage
+
+ FastStringWriter sw = new FastStringWriter()
+ sw.write("true")
+ smpage.setContentBuffer("contentbuffer", sw.getBuffer())
Review Comment:
Align on single quotes for strings?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
+ gspGrailsLayoutPage.setPageBuffer(stringWriter.buffer)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ }
+ }
+ finally {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage)
+ }
+ }
+ if (content == null) {
+ content = ''
+ }
+
+ Page page = null
+ if (!((TypeConvertingMap) attrs).boolean('parse') && gspGrailsLayoutPage != null && gspGrailsLayoutPage.isUsed()) {
+ page = gspGrailsLayoutPage
+ } else {
+ def parser = createPageParser(contentType)
+ char[] charArray
+ if (content instanceof StreamCharBuffer) {
+ charArray = ((StreamCharBuffer) content).toCharArray()
+ } else {
+ charArray = content.toString().toCharArray()
+ }
+ page = parser.parse(charArray)
+ }
+
+ def decorator = findDecorator(request, attrs.name as String)
+ if (decorator && decorator.page) {
+ pageParams.each { k, v ->
+ page.addProperty(k as String, v as String)
+ }
+ try {
+ request.setAttribute(PAGE, page)
+ Template template = findTemplate(decorator)
Review Comment:
`def`?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/FormTagLibTests.groovy:
##########
@@ -41,33 +42,25 @@ import spock.lang.Specification
* @author rvanderwerf
*/
class FormTagLibTests extends Specification implements TagLibUnitTest<FormTagLib> {
+ def setupSpec() {
+ // Test pollution causes these to be cached on the LazyTagLibraryLookup
+ [ApplicationTagLib, FormTagLib, UrlMappingTagLib].each {
+ GroovySystem.metaClassRegistry.removeMetaClass(it)
+ }
+ }
def setup() {
tagLib.requestDataValueProcessor = new MockRequestDataValueProcessor()
- }
- def setupSpec() {
- def mappingsClosure = {
- '/admin/books'(controller: 'books', namespace: 'admin')
- '/books'(controller: 'books')
+ grailsApplication.addArtefact(UrlMappingsArtefactHandler.TYPE, FormTagLibUrlMappings)
+
+ defineBeans {
+ grailsUrlMappingsHolder(UrlMappingsHolderFactoryBean) {
+ getDelegate().grailsApplication = grailsApplication
Review Comment:
Use groovy getter?
##########
grails-testing-support-web/src/main/groovy/org/grails/testing/spock/WebCleanupSpecInterceptor.groovy:
##########
@@ -29,7 +31,24 @@ class WebCleanupSpecInterceptor implements IMethodInterceptor {
@Override
void intercept(IMethodInvocation invocation) throws Throwable {
- ConvertersConfigurationHolder.clear()
- invocation.proceed()
+ try {
+ invocation.proceed()
+ }
+ finally {
+ ConvertersConfigurationHolder.clear()
+
+ GrailsWebUnitTest test = (GrailsWebUnitTest)invocation.instance
+ if(test.purgeTagLibMetaClass) {
+ try {
+ LazyTagLibraryLookup lookup = test.grailsApplication.mainContext.getBean(LazyTagLibraryLookup)
Review Comment:
`def`?
##########
gradle/test-config.gradle:
##########
@@ -73,3 +73,31 @@ tasks.withType(Test).configureEach {
}
}
+if(hasProperty('testBisect')) {
Review Comment:
Align on space after `if`?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/FormTagLibTests.groovy:
##########
@@ -582,24 +562,12 @@ class FormTagLibTests extends Specification implements TagLibUnitTest<FormTagLib
private void unRegisterRequestDataValueProcessor() {
tagLib.requestDataValueProcessor = null
}
+}
- private static final class MockGrailsUrlMappingsClass extends AbstractGrailsClass implements GrailsUrlMappingsClass {
-
- Closure mappingClosure
-
- MockGrailsUrlMappingsClass(Closure mappingClosure) {
- super(FormTagLibTests.class, 'UrlMappings')
- this.mappingClosure = mappingClosure
- }
-
- @Override
- Closure getMappingsClosure() {
- return mappingClosure
- }
-
- @Override
- List getExcludePatterns() {
- return null
- }
+@Artefact("UrlMappings")
Review Comment:
Align on single quotes for strings?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -247,6 +255,44 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertOutputEquals '<th id="1" class="sortable" ><a href="/custompathCustomNamespace/mockcontroller/list?sort=id&order=asc">ID</a></th>', template
}
+ void testPageProperty() {
+
+ def template = '<g:pageProperty name="foo.bar" />'
+
+ def head = ""
+ TokenizedHTMLPage page = new TokenizedHTMLPage([] as char[], new CharArray(0), new CharArray(0))
+ request[RequestConstants.PAGE] = page
+
+ page.addProperty("foo.bar", "good")
+
+ assertOutputEquals "good", template
+
+ template = '<g:pageProperty name="foo.bar" writeEntireProperty="true" />'
+ assertOutputEquals " bar=\"good\"", template
+ }
+
+ void testIfPageProperty() {
+ def template = '<g:ifPageProperty name="foo.bar">Hello</g:ifPageProperty>'
+
+ TokenizedHTMLPage page = new TokenizedHTMLPage([] as char[], new CharArray(0), new CharArray(0))
+ request[RequestConstants.PAGE] = page
+
+ page.addProperty("foo.bar", "true")
+
+ assertOutputEquals "Hello", template
+
+ template = '<g:ifPageProperty name="page.contentbuffer">Hello 2</g:ifPageProperty>'
+
+ GSPGrailsLayoutPage smpage = new GSPGrailsLayoutPage()
+ request[RequestConstants.PAGE] = smpage
+
+ FastStringWriter sw = new FastStringWriter()
+ sw.write("true")
+ smpage.setContentBuffer("contentbuffer", sw.getBuffer())
+
+ assertOutputEquals "Hello 2", template
Review Comment:
- Align on parentheses for method calls?
- Align on single quotes for strings?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -247,6 +255,44 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertOutputEquals '<th id="1" class="sortable" ><a href="/custompathCustomNamespace/mockcontroller/list?sort=id&order=asc">ID</a></th>', template
}
+ void testPageProperty() {
+
+ def template = '<g:pageProperty name="foo.bar" />'
+
+ def head = ""
+ TokenizedHTMLPage page = new TokenizedHTMLPage([] as char[], new CharArray(0), new CharArray(0))
+ request[RequestConstants.PAGE] = page
+
+ page.addProperty("foo.bar", "good")
+
+ assertOutputEquals "good", template
+
+ template = '<g:pageProperty name="foo.bar" writeEntireProperty="true" />'
+ assertOutputEquals " bar=\"good\"", template
+ }
+
+ void testIfPageProperty() {
+ def template = '<g:ifPageProperty name="foo.bar">Hello</g:ifPageProperty>'
+
+ TokenizedHTMLPage page = new TokenizedHTMLPage([] as char[], new CharArray(0), new CharArray(0))
+ request[RequestConstants.PAGE] = page
+
+ page.addProperty("foo.bar", "true")
Review Comment:
Align on single quotes for strings?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -247,6 +255,44 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertOutputEquals '<th id="1" class="sortable" ><a href="/custompathCustomNamespace/mockcontroller/list?sort=id&order=asc">ID</a></th>', template
}
+ void testPageProperty() {
+
+ def template = '<g:pageProperty name="foo.bar" />'
+
+ def head = ""
+ TokenizedHTMLPage page = new TokenizedHTMLPage([] as char[], new CharArray(0), new CharArray(0))
+ request[RequestConstants.PAGE] = page
+
+ page.addProperty("foo.bar", "good")
+
+ assertOutputEquals "good", template
+
+ template = '<g:pageProperty name="foo.bar" writeEntireProperty="true" />'
+ assertOutputEquals " bar=\"good\"", template
+ }
+
+ void testIfPageProperty() {
+ def template = '<g:ifPageProperty name="foo.bar">Hello</g:ifPageProperty>'
+
+ TokenizedHTMLPage page = new TokenizedHTMLPage([] as char[], new CharArray(0), new CharArray(0))
Review Comment:
`def`?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -693,6 +739,33 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertEquals 'my/contenttype', response.getContentType()
}
+ void testApplyLayout() {
+ GrailsLayoutDecoratorMapper decoratorMapper = new GrailsLayoutDecoratorMapper()
+ decoratorMapper.groovyPageLayoutFinder = appCtx.groovyPageLayoutFinder
+ FactoryHolder.setFactory([getDecoratorMapper: { -> decoratorMapper
+ }] as com.opensymphony.module.sitemesh.Factory)
+ def resourceLoader = new MockStringResourceLoader()
+ resourceLoader.registerMockResource('/layouts/layout.gsp', '<layoutapplied><g:layoutTitle /> - <g:layoutBody/></layoutapplied>')
+ appCtx.groovyPagesTemplateEngine.groovyPageLocator.addResourceLoader(resourceLoader)
+ def template = '<g:applyLayout name="layout"><html><head><title>title here</title></head><body>Hello world!</body></html></g:applyLayout>'
+ assertOutputEquals '<layoutapplied>title here - Hello world!</layoutapplied>', template
+ }
+
+ void testApplyLayoutParse() {
+ GrailsLayoutDecoratorMapper decoratorMapper = new GrailsLayoutDecoratorMapper()
+ decoratorMapper.groovyPageLayoutFinder = appCtx.groovyPageLayoutFinder
+ FactoryHolder.setFactory([getDecoratorMapper: { -> decoratorMapper
+ }, getPageParser : { String contentType -> new HTMLPageParser() }] as com.opensymphony.module.sitemesh.Factory)
+ def resourceLoader = new MockStringResourceLoader()
+ resourceLoader.registerMockResource('/layouts/layout.gsp', '<layoutapplied><g:layoutTitle /> - <g:layoutBody/></layoutapplied>')
+ appCtx.groovyPagesTemplateEngine.groovyPageLocator.addResourceLoader(resourceLoader)
+ def template = '<g:applyLayout name="layout" parse="${true}"><html><head><${"title"}>title here</${"title"}></head><body>Hello world!</body></html></g:applyLayout>'
+ assertOutputEquals '<layoutapplied>title here - Hello world!</layoutapplied>', template
Review Comment:
Align on parentheses for method calls?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -693,6 +739,33 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertEquals 'my/contenttype', response.getContentType()
}
+ void testApplyLayout() {
+ GrailsLayoutDecoratorMapper decoratorMapper = new GrailsLayoutDecoratorMapper()
Review Comment:
`def`?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -693,6 +739,33 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertEquals 'my/contenttype', response.getContentType()
}
+ void testApplyLayout() {
+ GrailsLayoutDecoratorMapper decoratorMapper = new GrailsLayoutDecoratorMapper()
+ decoratorMapper.groovyPageLayoutFinder = appCtx.groovyPageLayoutFinder
+ FactoryHolder.setFactory([getDecoratorMapper: { -> decoratorMapper
+ }] as com.opensymphony.module.sitemesh.Factory)
Review Comment:
Spacing?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -247,6 +255,44 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertOutputEquals '<th id="1" class="sortable" ><a href="/custompathCustomNamespace/mockcontroller/list?sort=id&order=asc">ID</a></th>', template
}
+ void testPageProperty() {
+
+ def template = '<g:pageProperty name="foo.bar" />'
+
+ def head = ""
+ TokenizedHTMLPage page = new TokenizedHTMLPage([] as char[], new CharArray(0), new CharArray(0))
+ request[RequestConstants.PAGE] = page
+
+ page.addProperty("foo.bar", "good")
+
+ assertOutputEquals "good", template
+
+ template = '<g:pageProperty name="foo.bar" writeEntireProperty="true" />'
+ assertOutputEquals " bar=\"good\"", template
+ }
+
+ void testIfPageProperty() {
+ def template = '<g:ifPageProperty name="foo.bar">Hello</g:ifPageProperty>'
+
+ TokenizedHTMLPage page = new TokenizedHTMLPage([] as char[], new CharArray(0), new CharArray(0))
+ request[RequestConstants.PAGE] = page
+
+ page.addProperty("foo.bar", "true")
+
+ assertOutputEquals "Hello", template
+
+ template = '<g:ifPageProperty name="page.contentbuffer">Hello 2</g:ifPageProperty>'
+
+ GSPGrailsLayoutPage smpage = new GSPGrailsLayoutPage()
+ request[RequestConstants.PAGE] = smpage
+
+ FastStringWriter sw = new FastStringWriter()
+ sw.write("true")
Review Comment:
Align on single quotes for strings?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -693,6 +739,33 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertEquals 'my/contenttype', response.getContentType()
}
+ void testApplyLayout() {
+ GrailsLayoutDecoratorMapper decoratorMapper = new GrailsLayoutDecoratorMapper()
+ decoratorMapper.groovyPageLayoutFinder = appCtx.groovyPageLayoutFinder
+ FactoryHolder.setFactory([getDecoratorMapper: { -> decoratorMapper
+ }] as com.opensymphony.module.sitemesh.Factory)
+ def resourceLoader = new MockStringResourceLoader()
+ resourceLoader.registerMockResource('/layouts/layout.gsp', '<layoutapplied><g:layoutTitle /> - <g:layoutBody/></layoutapplied>')
+ appCtx.groovyPagesTemplateEngine.groovyPageLocator.addResourceLoader(resourceLoader)
+ def template = '<g:applyLayout name="layout"><html><head><title>title here</title></head><body>Hello world!</body></html></g:applyLayout>'
+ assertOutputEquals '<layoutapplied>title here - Hello world!</layoutapplied>', template
+ }
+
+ void testApplyLayoutParse() {
+ GrailsLayoutDecoratorMapper decoratorMapper = new GrailsLayoutDecoratorMapper()
+ decoratorMapper.groovyPageLayoutFinder = appCtx.groovyPageLayoutFinder
+ FactoryHolder.setFactory([getDecoratorMapper: { -> decoratorMapper
+ }, getPageParser : { String contentType -> new HTMLPageParser() }] as com.opensymphony.module.sitemesh.Factory)
+ def resourceLoader = new MockStringResourceLoader()
+ resourceLoader.registerMockResource('/layouts/layout.gsp', '<layoutapplied><g:layoutTitle /> - <g:layoutBody/></layoutapplied>')
+ appCtx.groovyPagesTemplateEngine.groovyPageLocator.addResourceLoader(resourceLoader)
+ def template = '<g:applyLayout name="layout" parse="${true}"><html><head><${"title"}>title here</${"title"}></head><body>Hello world!</body></html></g:applyLayout>'
+ assertOutputEquals '<layoutapplied>title here - Hello world!</layoutapplied>', template
+
+ template = '<g:applyLayout name="layout" parse="false"><html><head><${"title"}>title here</${"title"}></head><body>Hello world!</body></html></g:applyLayout>'
+ assertOutputEquals '<layoutapplied> - Hello world!</layoutapplied>', template
Review Comment:
Align on parentheses for method calls?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -247,6 +255,44 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertOutputEquals '<th id="1" class="sortable" ><a href="/custompathCustomNamespace/mockcontroller/list?sort=id&order=asc">ID</a></th>', template
}
+ void testPageProperty() {
+
+ def template = '<g:pageProperty name="foo.bar" />'
+
+ def head = ""
+ TokenizedHTMLPage page = new TokenizedHTMLPage([] as char[], new CharArray(0), new CharArray(0))
+ request[RequestConstants.PAGE] = page
+
+ page.addProperty("foo.bar", "good")
+
+ assertOutputEquals "good", template
+
+ template = '<g:pageProperty name="foo.bar" writeEntireProperty="true" />'
+ assertOutputEquals " bar=\"good\"", template
+ }
+
+ void testIfPageProperty() {
+ def template = '<g:ifPageProperty name="foo.bar">Hello</g:ifPageProperty>'
+
+ TokenizedHTMLPage page = new TokenizedHTMLPage([] as char[], new CharArray(0), new CharArray(0))
+ request[RequestConstants.PAGE] = page
+
+ page.addProperty("foo.bar", "true")
+
+ assertOutputEquals "Hello", template
+
+ template = '<g:ifPageProperty name="page.contentbuffer">Hello 2</g:ifPageProperty>'
+
+ GSPGrailsLayoutPage smpage = new GSPGrailsLayoutPage()
+ request[RequestConstants.PAGE] = smpage
+
+ FastStringWriter sw = new FastStringWriter()
Review Comment:
`def`?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -247,6 +255,44 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertOutputEquals '<th id="1" class="sortable" ><a href="/custompathCustomNamespace/mockcontroller/list?sort=id&order=asc">ID</a></th>', template
}
+ void testPageProperty() {
+
+ def template = '<g:pageProperty name="foo.bar" />'
+
+ def head = ""
+ TokenizedHTMLPage page = new TokenizedHTMLPage([] as char[], new CharArray(0), new CharArray(0))
+ request[RequestConstants.PAGE] = page
+
+ page.addProperty("foo.bar", "good")
+
+ assertOutputEquals "good", template
+
+ template = '<g:pageProperty name="foo.bar" writeEntireProperty="true" />'
+ assertOutputEquals " bar=\"good\"", template
+ }
+
+ void testIfPageProperty() {
+ def template = '<g:ifPageProperty name="foo.bar">Hello</g:ifPageProperty>'
+
+ TokenizedHTMLPage page = new TokenizedHTMLPage([] as char[], new CharArray(0), new CharArray(0))
+ request[RequestConstants.PAGE] = page
+
+ page.addProperty("foo.bar", "true")
+
+ assertOutputEquals "Hello", template
Review Comment:
- Align on parentheses for method calls?
- Align on single quotes for strings?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -693,6 +739,33 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertEquals 'my/contenttype', response.getContentType()
}
+ void testApplyLayout() {
+ GrailsLayoutDecoratorMapper decoratorMapper = new GrailsLayoutDecoratorMapper()
+ decoratorMapper.groovyPageLayoutFinder = appCtx.groovyPageLayoutFinder
+ FactoryHolder.setFactory([getDecoratorMapper: { -> decoratorMapper
+ }] as com.opensymphony.module.sitemesh.Factory)
+ def resourceLoader = new MockStringResourceLoader()
+ resourceLoader.registerMockResource('/layouts/layout.gsp', '<layoutapplied><g:layoutTitle /> - <g:layoutBody/></layoutapplied>')
+ appCtx.groovyPagesTemplateEngine.groovyPageLocator.addResourceLoader(resourceLoader)
+ def template = '<g:applyLayout name="layout"><html><head><title>title here</title></head><body>Hello world!</body></html></g:applyLayout>'
+ assertOutputEquals '<layoutapplied>title here - Hello world!</layoutapplied>', template
Review Comment:
Align on parentheses for method calls?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -693,6 +739,33 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertEquals 'my/contenttype', response.getContentType()
}
+ void testApplyLayout() {
+ GrailsLayoutDecoratorMapper decoratorMapper = new GrailsLayoutDecoratorMapper()
+ decoratorMapper.groovyPageLayoutFinder = appCtx.groovyPageLayoutFinder
+ FactoryHolder.setFactory([getDecoratorMapper: { -> decoratorMapper
+ }] as com.opensymphony.module.sitemesh.Factory)
+ def resourceLoader = new MockStringResourceLoader()
+ resourceLoader.registerMockResource('/layouts/layout.gsp', '<layoutapplied><g:layoutTitle /> - <g:layoutBody/></layoutapplied>')
+ appCtx.groovyPagesTemplateEngine.groovyPageLocator.addResourceLoader(resourceLoader)
+ def template = '<g:applyLayout name="layout"><html><head><title>title here</title></head><body>Hello world!</body></html></g:applyLayout>'
+ assertOutputEquals '<layoutapplied>title here - Hello world!</layoutapplied>', template
+ }
+
+ void testApplyLayoutParse() {
+ GrailsLayoutDecoratorMapper decoratorMapper = new GrailsLayoutDecoratorMapper()
+ decoratorMapper.groovyPageLayoutFinder = appCtx.groovyPageLayoutFinder
+ FactoryHolder.setFactory([getDecoratorMapper: { -> decoratorMapper
+ }, getPageParser : { String contentType -> new HTMLPageParser() }] as com.opensymphony.module.sitemesh.Factory)
Review Comment:
Spacing?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -693,6 +739,33 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertEquals 'my/contenttype', response.getContentType()
}
+ void testApplyLayout() {
+ GrailsLayoutDecoratorMapper decoratorMapper = new GrailsLayoutDecoratorMapper()
+ decoratorMapper.groovyPageLayoutFinder = appCtx.groovyPageLayoutFinder
+ FactoryHolder.setFactory([getDecoratorMapper: { -> decoratorMapper
+ }] as com.opensymphony.module.sitemesh.Factory)
+ def resourceLoader = new MockStringResourceLoader()
+ resourceLoader.registerMockResource('/layouts/layout.gsp', '<layoutapplied><g:layoutTitle /> - <g:layoutBody/></layoutapplied>')
+ appCtx.groovyPagesTemplateEngine.groovyPageLocator.addResourceLoader(resourceLoader)
+ def template = '<g:applyLayout name="layout"><html><head><title>title here</title></head><body>Hello world!</body></html></g:applyLayout>'
+ assertOutputEquals '<layoutapplied>title here - Hello world!</layoutapplied>', template
+ }
+
+ void testApplyLayoutParse() {
+ GrailsLayoutDecoratorMapper decoratorMapper = new GrailsLayoutDecoratorMapper()
Review Comment:
`def`?
##########
gradle/test-config.gradle:
##########
@@ -73,3 +73,31 @@ tasks.withType(Test).configureEach {
}
}
+if(hasProperty('testBisect')) {
+ tasks.withType(Test).configureEach {
+ maxParallelForks = 1
+ forkEvery = 0
+
+ develocity {
+ testDistribution {
+ enabled.set(false)
+ retryInSameJvm.set(true)
+ }
Review Comment:
- Is test distribution used?
- Use groovy setters?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/GrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,239 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.RequestConstants
+import grails.artefact.TagLibrary
+import grails.gsp.TagLib
+import groovy.transform.CompileStatic
+import jakarta.servlet.http.HttpServletRequest
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.GrailsPrintWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.encoder.CodecLookup
+import org.grails.encoder.Encoder
+import org.grails.gsp.compiler.GrailsLayoutPreprocessor
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+
+/**
+ * Internal Grails Layout pre-processor tags.
+ *
+ * @author Graeme Rocher
+ * @since 1.2
+ */
+@CompileStatic
+@TagLib
+class GrailsLayoutTagLib implements RequestConstants, TagLibrary {
+ protected static final String GSP_GRAILS_LAYOUT_PAGE = 'org.apache.grails.web.layout.EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE'
+
+ static String namespace = 'grailsLayout'
+ CodecLookup codecLookup
+
+ def captureTagContent(GrailsPrintWriter writer, String tagname, Map attrs, Object body, boolean noEndTagForEmpty=false) {
+ def content = null
+ if (body != null) {
+ if (body instanceof Closure) {
+ content = body()
+ }
+ else {
+ content = body
+ }
+ }
+
+ if (content instanceof StreamCharBuffer) {
+ content.setPreferSubChunkWhenWritingToOtherBuffer(true)
+ }
+ writer << '<'
+ writer << tagname
+ def useXmlClosingForEmptyTag = false
+ if (attrs) {
+ def xmlClosingString = attrs.remove(GrailsLayoutPreprocessor.XML_CLOSING_FOR_EMPTY_TAG_ATTRIBUTE_NAME)
+ if (xmlClosingString=='/') {
+ useXmlClosingForEmptyTag = true
+ }
+ Encoder htmlEncoder = codecLookup?.lookupEncoder('HTML')
+ attrs.each { k, v ->
+ writer << ' '
+ writer << k
+ writer << '="'
+ writer << (htmlEncoder != null ? htmlEncoder.encode(v) : v)
+ writer << '"'
+ }
+ }
+
+ if (content) {
+ writer << '>'
+ // the following row must be written separately (append StreamCharBuffer gets appended as subchunk)
+ writer << content
+ writer << '</'
+ writer << tagname
+ writer << '>'
+ }
+ else {
+ if (!useXmlClosingForEmptyTag) {
+ writer << '>'
+ // in valid HTML , closing of an empty tag depends on the element name
+ // for empty title, the tag must be closed properly
+ // for empty meta tag shouldn't be closed at all, see GRAILS-5696
+ if (!noEndTagForEmpty) {
+ writer << '</'
+ writer << tagname
+ writer << '>'
+ }
+ }
+ else {
+ // XML / XHTML empty tag
+ writer << '/>'
+ }
+ }
+ content
+ }
+
+ def StreamCharBuffer wrapContentInBuffer(Object content) {
+ if (content instanceof Closure) {
+ content = content()
+ }
+ if (!(content instanceof StreamCharBuffer)) {
+ // the body closure might be a string constant, so wrap it in a StreamCharBuffer in that case
+ FastStringWriter stringWriter=new FastStringWriter()
+ stringWriter.print((Object)content)
+ StreamCharBuffer newbuffer = stringWriter.buffer
+ newbuffer.setPreferSubChunkWhenWritingToOtherBuffer(true)
+ return newbuffer
+ } else {
+ return (StreamCharBuffer)content
+ }
+ }
+
+ /**
+ * Captures the <head> tag.
+ */
+ Closure captureHead = { Map attrs, body ->
+ def content = captureTagContent(out, 'head', attrs, body)
+
+ if (content != null) {
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
+ if (smpage) {
+ smpage.setHeadBuffer(wrapContentInBuffer(content))
+ }
+ }
+ }
+
+ protected GSPGrailsLayoutPage findGSPGrailsLayoutPage(HttpServletRequest request) {
+ (GSPGrailsLayoutPage)request.getAttribute(GSP_GRAILS_LAYOUT_PAGE)
+ }
+
+ /**
+ * Allows passing of parameters to Grails layout.<br/>
+ *
+ * <grailsLayout:parameter name="foo" value="bar" />
+ */
+ Closure parameter = { Map attrs, body ->
+ GSPGrailsLayoutPage smpage=findGSPGrailsLayoutPage(request)
+ def name = attrs.name?.toString()
+ def val = attrs.value?.toString()
+ if (smpage && name && val != null) {
+ smpage.addProperty("page.$name", val)
+ }
+ }
+
+ /**
+ * Captures the <body> tag.
+ */
+ Closure captureBody = { Map attrs, body ->
+ def content = captureTagContent(out, 'body', attrs, body)
+ if (content != null) {
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
Review Comment:
`def`?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
Review Comment:
- `as StreamCharBuffer`?
- Use groovy setter?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/GrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,239 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.RequestConstants
+import grails.artefact.TagLibrary
+import grails.gsp.TagLib
+import groovy.transform.CompileStatic
+import jakarta.servlet.http.HttpServletRequest
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.GrailsPrintWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.encoder.CodecLookup
+import org.grails.encoder.Encoder
+import org.grails.gsp.compiler.GrailsLayoutPreprocessor
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+
+/**
+ * Internal Grails Layout pre-processor tags.
+ *
+ * @author Graeme Rocher
+ * @since 1.2
+ */
+@CompileStatic
+@TagLib
+class GrailsLayoutTagLib implements RequestConstants, TagLibrary {
+ protected static final String GSP_GRAILS_LAYOUT_PAGE = 'org.apache.grails.web.layout.EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE'
+
+ static String namespace = 'grailsLayout'
+ CodecLookup codecLookup
+
+ def captureTagContent(GrailsPrintWriter writer, String tagname, Map attrs, Object body, boolean noEndTagForEmpty=false) {
+ def content = null
+ if (body != null) {
+ if (body instanceof Closure) {
+ content = body()
+ }
+ else {
+ content = body
+ }
+ }
+
+ if (content instanceof StreamCharBuffer) {
+ content.setPreferSubChunkWhenWritingToOtherBuffer(true)
+ }
+ writer << '<'
+ writer << tagname
+ def useXmlClosingForEmptyTag = false
+ if (attrs) {
+ def xmlClosingString = attrs.remove(GrailsLayoutPreprocessor.XML_CLOSING_FOR_EMPTY_TAG_ATTRIBUTE_NAME)
+ if (xmlClosingString=='/') {
+ useXmlClosingForEmptyTag = true
+ }
+ Encoder htmlEncoder = codecLookup?.lookupEncoder('HTML')
+ attrs.each { k, v ->
+ writer << ' '
+ writer << k
+ writer << '="'
+ writer << (htmlEncoder != null ? htmlEncoder.encode(v) : v)
+ writer << '"'
+ }
+ }
+
+ if (content) {
+ writer << '>'
+ // the following row must be written separately (append StreamCharBuffer gets appended as subchunk)
+ writer << content
+ writer << '</'
+ writer << tagname
+ writer << '>'
+ }
+ else {
+ if (!useXmlClosingForEmptyTag) {
+ writer << '>'
+ // in valid HTML , closing of an empty tag depends on the element name
+ // for empty title, the tag must be closed properly
+ // for empty meta tag shouldn't be closed at all, see GRAILS-5696
+ if (!noEndTagForEmpty) {
+ writer << '</'
+ writer << tagname
+ writer << '>'
+ }
+ }
+ else {
+ // XML / XHTML empty tag
+ writer << '/>'
+ }
+ }
+ content
+ }
+
+ def StreamCharBuffer wrapContentInBuffer(Object content) {
+ if (content instanceof Closure) {
+ content = content()
+ }
+ if (!(content instanceof StreamCharBuffer)) {
+ // the body closure might be a string constant, so wrap it in a StreamCharBuffer in that case
+ FastStringWriter stringWriter=new FastStringWriter()
+ stringWriter.print((Object)content)
+ StreamCharBuffer newbuffer = stringWriter.buffer
+ newbuffer.setPreferSubChunkWhenWritingToOtherBuffer(true)
+ return newbuffer
+ } else {
+ return (StreamCharBuffer)content
+ }
+ }
+
+ /**
+ * Captures the <head> tag.
+ */
+ Closure captureHead = { Map attrs, body ->
+ def content = captureTagContent(out, 'head', attrs, body)
+
+ if (content != null) {
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
+ if (smpage) {
+ smpage.setHeadBuffer(wrapContentInBuffer(content))
+ }
+ }
+ }
+
+ protected GSPGrailsLayoutPage findGSPGrailsLayoutPage(HttpServletRequest request) {
+ (GSPGrailsLayoutPage)request.getAttribute(GSP_GRAILS_LAYOUT_PAGE)
+ }
+
+ /**
+ * Allows passing of parameters to Grails layout.<br/>
+ *
+ * <grailsLayout:parameter name="foo" value="bar" />
+ */
+ Closure parameter = { Map attrs, body ->
+ GSPGrailsLayoutPage smpage=findGSPGrailsLayoutPage(request)
+ def name = attrs.name?.toString()
+ def val = attrs.value?.toString()
+ if (smpage && name && val != null) {
+ smpage.addProperty("page.$name", val)
+ }
+ }
+
+ /**
+ * Captures the <body> tag.
+ */
+ Closure captureBody = { Map attrs, body ->
+ def content = captureTagContent(out, 'body', attrs, body)
+ if (content != null) {
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
+ if (smpage) {
+ smpage.setBodyBuffer(wrapContentInBuffer(content))
+ if (attrs) {
+ attrs.each { k, v ->
+ smpage.addProperty("body.${k?.toString()?.toLowerCase()}", v?.toString())
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Captures the individual <content> tags.
+ */
+ Closure captureContent = { Map attrs, body ->
+ if (body != null) {
+ GSPGrailsLayoutPage smpage=findGSPGrailsLayoutPage(request)
+ if (smpage && attrs.tag) {
+ smpage.setContentBuffer(attrs.tag as String, wrapContentInBuffer(body))
+ }
+ }
+ }
+
+ /**
+ * Captures the individual <meta> tags.
+ */
+ Closure captureMeta = { Map attrs, body ->
+ def content = captureTagContent(out, 'meta', attrs, body, true)
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
+ def val = attrs.content?.toString()
+ if (attrs && smpage && val != null) {
+ if (attrs.name) {
+ smpage.addProperty("meta.${attrs.name}", val)
+ smpage.addProperty("meta.${attrs.name.toString().toLowerCase()}", val)
+ }
+ else if (attrs['http-equiv']) {
+ String httpEquiv = attrs['http-equiv'] as String
+ def httpEquivFormats = [httpEquiv, httpEquiv.toLowerCase()]
+ if(httpEquiv.equalsIgnoreCase('content-type')) {
+ httpEquivFormats << 'Content-Type'
+ }
+ for (def httpEquivFormat : httpEquivFormats) {
+ smpage.addProperty("meta.http-equiv.${httpEquivFormat}", val)
+ }
+ }
+ }
+ }
+
+ /**
+ * Captures the <title> tag.
+ */
+ Closure captureTitle = { Map attrs, body ->
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
Review Comment:
`def`?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
Review Comment:
Why is `content` cast to `Object`, isn't it already typed as `Object`?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/EmbeddedGrailsLayoutView.java:
##########
@@ -0,0 +1,173 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import groovy.text.Template;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.grails.web.servlet.WrappedResponseHolder;
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.web.servlet.mvc.OutputAwareHttpServletResponse;
+import org.grails.web.servlet.view.AbstractGrailsView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.web.servlet.View;
+
+import com.opensymphony.module.sitemesh.RequestConstants;
+import com.opensymphony.sitemesh.Content;
+
+public class EmbeddedGrailsLayoutView extends AbstractGrailsView {
+ private static final Logger LOG = LoggerFactory.getLogger(EmbeddedGrailsLayoutView.class);
+ GroovyPageLayoutFinder groovyPageLayoutFinder;
+
+ protected View innerView;
+
+ public static final String GSP_GRAILS_LAYOUT_PAGE = EmbeddedGrailsLayoutView.class.getName() + ".GSP_GRAILS_LAYOUT_PAGE";
+
+ public EmbeddedGrailsLayoutView(GroovyPageLayoutFinder groovyPageLayoutFinder, View innerView) {
+ this.groovyPageLayoutFinder = groovyPageLayoutFinder;
+ this.innerView = innerView;
+ }
+
+ @Override
+ public String getContentType() {
+ return MediaType.ALL_VALUE;
+ }
+
+ @Override
+ protected void renderTemplate(Map<String, Object> model, GrailsWebRequest webRequest, HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+
+ boolean isCommitted = response.isCommitted() && (response instanceof OutputAwareHttpServletResponse) && !((OutputAwareHttpServletResponse) response).isWriterAvailable();
+ if( !isCommitted ) {
+
+ Content content = obtainContent(model, webRequest, request, response);
+ if (content != null) {
+
+ beforeDecorating(content, model, webRequest, request, response);
+ switch (request.getDispatcherType()) {
+ case INCLUDE:
+ break;
+ case ASYNC:
+ case ERROR:
+ case FORWARD:
+ case REQUEST:
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Finding layout for request and content" );
+ }
+ SpringMVCViewDecorator decorator = (SpringMVCViewDecorator) groovyPageLayoutFinder.findLayout(request, content);
+ if (decorator != null) {
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Found layout. Rendering content for layout and model {}", decorator.getPage(), model);
Review Comment:
Only one placeholder but two values supplied?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/GrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,239 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.RequestConstants
+import grails.artefact.TagLibrary
+import grails.gsp.TagLib
+import groovy.transform.CompileStatic
+import jakarta.servlet.http.HttpServletRequest
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.GrailsPrintWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.encoder.CodecLookup
+import org.grails.encoder.Encoder
+import org.grails.gsp.compiler.GrailsLayoutPreprocessor
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+
+/**
+ * Internal Grails Layout pre-processor tags.
+ *
+ * @author Graeme Rocher
+ * @since 1.2
+ */
+@CompileStatic
+@TagLib
+class GrailsLayoutTagLib implements RequestConstants, TagLibrary {
+ protected static final String GSP_GRAILS_LAYOUT_PAGE = 'org.apache.grails.web.layout.EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE'
+
+ static String namespace = 'grailsLayout'
+ CodecLookup codecLookup
+
+ def captureTagContent(GrailsPrintWriter writer, String tagname, Map attrs, Object body, boolean noEndTagForEmpty=false) {
+ def content = null
+ if (body != null) {
+ if (body instanceof Closure) {
+ content = body()
+ }
+ else {
+ content = body
+ }
+ }
+
+ if (content instanceof StreamCharBuffer) {
+ content.setPreferSubChunkWhenWritingToOtherBuffer(true)
+ }
+ writer << '<'
+ writer << tagname
+ def useXmlClosingForEmptyTag = false
+ if (attrs) {
+ def xmlClosingString = attrs.remove(GrailsLayoutPreprocessor.XML_CLOSING_FOR_EMPTY_TAG_ATTRIBUTE_NAME)
+ if (xmlClosingString=='/') {
+ useXmlClosingForEmptyTag = true
+ }
+ Encoder htmlEncoder = codecLookup?.lookupEncoder('HTML')
+ attrs.each { k, v ->
+ writer << ' '
+ writer << k
+ writer << '="'
+ writer << (htmlEncoder != null ? htmlEncoder.encode(v) : v)
+ writer << '"'
+ }
+ }
+
+ if (content) {
+ writer << '>'
+ // the following row must be written separately (append StreamCharBuffer gets appended as subchunk)
+ writer << content
+ writer << '</'
+ writer << tagname
+ writer << '>'
+ }
+ else {
+ if (!useXmlClosingForEmptyTag) {
+ writer << '>'
+ // in valid HTML , closing of an empty tag depends on the element name
+ // for empty title, the tag must be closed properly
+ // for empty meta tag shouldn't be closed at all, see GRAILS-5696
+ if (!noEndTagForEmpty) {
+ writer << '</'
+ writer << tagname
+ writer << '>'
+ }
+ }
+ else {
+ // XML / XHTML empty tag
+ writer << '/>'
+ }
+ }
+ content
+ }
+
+ def StreamCharBuffer wrapContentInBuffer(Object content) {
+ if (content instanceof Closure) {
+ content = content()
+ }
+ if (!(content instanceof StreamCharBuffer)) {
+ // the body closure might be a string constant, so wrap it in a StreamCharBuffer in that case
+ FastStringWriter stringWriter=new FastStringWriter()
+ stringWriter.print((Object)content)
+ StreamCharBuffer newbuffer = stringWriter.buffer
+ newbuffer.setPreferSubChunkWhenWritingToOtherBuffer(true)
+ return newbuffer
+ } else {
+ return (StreamCharBuffer)content
+ }
+ }
+
+ /**
+ * Captures the <head> tag.
+ */
+ Closure captureHead = { Map attrs, body ->
+ def content = captureTagContent(out, 'head', attrs, body)
+
+ if (content != null) {
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
Review Comment:
`def`?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
Review Comment:
No need for `return`?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
+ gspGrailsLayoutPage.setPageBuffer(stringWriter.buffer)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ }
+ }
+ finally {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage)
+ }
+ }
+ if (content == null) {
+ content = ''
+ }
+
+ Page page = null
+ if (!((TypeConvertingMap) attrs).boolean('parse') && gspGrailsLayoutPage != null && gspGrailsLayoutPage.isUsed()) {
+ page = gspGrailsLayoutPage
+ } else {
+ def parser = createPageParser(contentType)
+ char[] charArray
+ if (content instanceof StreamCharBuffer) {
+ charArray = ((StreamCharBuffer) content).toCharArray()
+ } else {
+ charArray = content.toString().toCharArray()
+ }
+ page = parser.parse(charArray)
+ }
+
+ def decorator = findDecorator(request, attrs.name as String)
+ if (decorator && decorator.page) {
+ pageParams.each { k, v ->
+ page.addProperty(k as String, v as String)
+ }
+ try {
+ request.setAttribute(PAGE, page)
+ Template template = findTemplate(decorator)
+ template.make(viewModel).writeTo(out)
+ }
+ finally {
+ request.setAttribute(PAGE, oldPage)
+ }
+ } else {
+ out << content
+ }
+ }
+
+ protected Template findTemplate(Decorator decorator) {
+ Template template
+ if (decorator instanceof SpringMVCViewDecorator) {
+ template = ((SpringMVCViewDecorator) decorator).getTemplate()
+ if (template instanceof GroovyPageTemplate) {
+ GroovyPageTemplate gpt = (GroovyPageTemplate) template
+ gpt = (GroovyPageTemplate) gpt.clone()
+ gpt.setAllowSettingContentType(false)
+ template = gpt
+ }
+ } else {
+ template = groovyPagesTemplateEngine.createTemplate(decorator.getPage())
+ }
+ return template
+ }
+
+ protected Decorator findDecorator(HttpServletRequest req, String layoutName) {
+ DecoratorMapper decoratorMapper = sitemeshFactory?.getDecoratorMapper()
+ Decorator d
+ if (decoratorMapper) {
+ d = decoratorMapper.getNamedDecorator(req, layoutName)
+ } else {
+ d = groovyPageLayoutFinder.getNamedDecorator(req, layoutName)
+ }
+ d
+ }
+
+ protected PageParser createPageParser(String contentType) {
+ PageParser parser = sitemeshFactory?.getPageParser(contentType)
+ if (parser == null) {
+ parser = new GrailsHTMLPageParser()
+ }
+ return parser
+ }
+
+ protected Factory getSitemeshFactory() {
+ return FactoryHolder.getGrailsLayoutFactory()
+ }
+
+ /**
+ * Used to retrieve a property of the decorated page.<br/>
+ *
+ * <g:pageProperty default="defaultValue" name="body.onload" /><br/>
+ *
+ * @emptyTag
+ *
+ * @attr REQUIRED name the property name
+ * @attr default the default value to use if the property is null
+ * @attr writeEntireProperty if true, writes the property in the form 'foo = "bar"', otherwise renders 'bar'
+ */
+ Closure pageProperty = { Map attrs ->
+ if (!attrs.name) {
+ throwTagError("Tag [pageProperty] is missing required attribute [name]")
+ }
+
+ String propertyName = attrs.name as String
+ def htmlPage = getPage()
Review Comment:
Use groovy getter?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/GrailsLayoutGrailsPlugin.groovy:
##########
@@ -0,0 +1,43 @@
+/*
+ * 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
+ *
+ * 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.plugins.web
+
+import org.grails.plugins.web.taglib.RenderGrailsLayoutTagLib
+import org.grails.plugins.web.taglib.GrailsLayoutTagLib
+
+import grails.plugins.Plugin
+import grails.util.GrailsUtil
+import groovy.util.logging.Slf4j
+
+/**
+ * Plugin responsible for Grails Layout specific configuration.
+ */
+@Slf4j
Review Comment:
Is there any logging going on here?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
Review Comment:
Align on no star imports?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
Review Comment:
Align on single quotes for strings?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/GrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,239 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.RequestConstants
+import grails.artefact.TagLibrary
+import grails.gsp.TagLib
+import groovy.transform.CompileStatic
+import jakarta.servlet.http.HttpServletRequest
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.GrailsPrintWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.encoder.CodecLookup
+import org.grails.encoder.Encoder
+import org.grails.gsp.compiler.GrailsLayoutPreprocessor
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+
+/**
+ * Internal Grails Layout pre-processor tags.
+ *
+ * @author Graeme Rocher
+ * @since 1.2
+ */
+@CompileStatic
+@TagLib
+class GrailsLayoutTagLib implements RequestConstants, TagLibrary {
+ protected static final String GSP_GRAILS_LAYOUT_PAGE = 'org.apache.grails.web.layout.EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE'
+
+ static String namespace = 'grailsLayout'
+ CodecLookup codecLookup
+
+ def captureTagContent(GrailsPrintWriter writer, String tagname, Map attrs, Object body, boolean noEndTagForEmpty=false) {
+ def content = null
+ if (body != null) {
+ if (body instanceof Closure) {
+ content = body()
+ }
+ else {
+ content = body
+ }
+ }
+
+ if (content instanceof StreamCharBuffer) {
+ content.setPreferSubChunkWhenWritingToOtherBuffer(true)
+ }
+ writer << '<'
+ writer << tagname
+ def useXmlClosingForEmptyTag = false
+ if (attrs) {
+ def xmlClosingString = attrs.remove(GrailsLayoutPreprocessor.XML_CLOSING_FOR_EMPTY_TAG_ATTRIBUTE_NAME)
+ if (xmlClosingString=='/') {
+ useXmlClosingForEmptyTag = true
+ }
+ Encoder htmlEncoder = codecLookup?.lookupEncoder('HTML')
+ attrs.each { k, v ->
+ writer << ' '
+ writer << k
+ writer << '="'
+ writer << (htmlEncoder != null ? htmlEncoder.encode(v) : v)
+ writer << '"'
+ }
+ }
+
+ if (content) {
+ writer << '>'
+ // the following row must be written separately (append StreamCharBuffer gets appended as subchunk)
+ writer << content
+ writer << '</'
+ writer << tagname
+ writer << '>'
+ }
+ else {
+ if (!useXmlClosingForEmptyTag) {
+ writer << '>'
+ // in valid HTML , closing of an empty tag depends on the element name
+ // for empty title, the tag must be closed properly
+ // for empty meta tag shouldn't be closed at all, see GRAILS-5696
+ if (!noEndTagForEmpty) {
+ writer << '</'
+ writer << tagname
+ writer << '>'
+ }
+ }
+ else {
+ // XML / XHTML empty tag
+ writer << '/>'
+ }
+ }
+ content
+ }
+
+ def StreamCharBuffer wrapContentInBuffer(Object content) {
+ if (content instanceof Closure) {
+ content = content()
+ }
+ if (!(content instanceof StreamCharBuffer)) {
+ // the body closure might be a string constant, so wrap it in a StreamCharBuffer in that case
+ FastStringWriter stringWriter=new FastStringWriter()
+ stringWriter.print((Object)content)
+ StreamCharBuffer newbuffer = stringWriter.buffer
+ newbuffer.setPreferSubChunkWhenWritingToOtherBuffer(true)
+ return newbuffer
+ } else {
+ return (StreamCharBuffer)content
+ }
+ }
+
+ /**
+ * Captures the <head> tag.
+ */
+ Closure captureHead = { Map attrs, body ->
+ def content = captureTagContent(out, 'head', attrs, body)
+
+ if (content != null) {
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
+ if (smpage) {
+ smpage.setHeadBuffer(wrapContentInBuffer(content))
+ }
+ }
+ }
+
+ protected GSPGrailsLayoutPage findGSPGrailsLayoutPage(HttpServletRequest request) {
+ (GSPGrailsLayoutPage)request.getAttribute(GSP_GRAILS_LAYOUT_PAGE)
+ }
+
+ /**
+ * Allows passing of parameters to Grails layout.<br/>
+ *
+ * <grailsLayout:parameter name="foo" value="bar" />
+ */
+ Closure parameter = { Map attrs, body ->
+ GSPGrailsLayoutPage smpage=findGSPGrailsLayoutPage(request)
+ def name = attrs.name?.toString()
+ def val = attrs.value?.toString()
+ if (smpage && name && val != null) {
+ smpage.addProperty("page.$name", val)
+ }
+ }
+
+ /**
+ * Captures the <body> tag.
+ */
+ Closure captureBody = { Map attrs, body ->
+ def content = captureTagContent(out, 'body', attrs, body)
+ if (content != null) {
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
+ if (smpage) {
+ smpage.setBodyBuffer(wrapContentInBuffer(content))
+ if (attrs) {
+ attrs.each { k, v ->
+ smpage.addProperty("body.${k?.toString()?.toLowerCase()}", v?.toString())
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Captures the individual <content> tags.
+ */
+ Closure captureContent = { Map attrs, body ->
+ if (body != null) {
+ GSPGrailsLayoutPage smpage=findGSPGrailsLayoutPage(request)
Review Comment:
`def`?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/GrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,239 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.RequestConstants
+import grails.artefact.TagLibrary
+import grails.gsp.TagLib
+import groovy.transform.CompileStatic
+import jakarta.servlet.http.HttpServletRequest
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.GrailsPrintWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.encoder.CodecLookup
+import org.grails.encoder.Encoder
+import org.grails.gsp.compiler.GrailsLayoutPreprocessor
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+
+/**
+ * Internal Grails Layout pre-processor tags.
+ *
+ * @author Graeme Rocher
+ * @since 1.2
+ */
+@CompileStatic
+@TagLib
+class GrailsLayoutTagLib implements RequestConstants, TagLibrary {
+ protected static final String GSP_GRAILS_LAYOUT_PAGE = 'org.apache.grails.web.layout.EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE'
+
+ static String namespace = 'grailsLayout'
+ CodecLookup codecLookup
+
+ def captureTagContent(GrailsPrintWriter writer, String tagname, Map attrs, Object body, boolean noEndTagForEmpty=false) {
+ def content = null
+ if (body != null) {
+ if (body instanceof Closure) {
+ content = body()
+ }
+ else {
+ content = body
+ }
+ }
+
+ if (content instanceof StreamCharBuffer) {
+ content.setPreferSubChunkWhenWritingToOtherBuffer(true)
+ }
+ writer << '<'
+ writer << tagname
+ def useXmlClosingForEmptyTag = false
+ if (attrs) {
+ def xmlClosingString = attrs.remove(GrailsLayoutPreprocessor.XML_CLOSING_FOR_EMPTY_TAG_ATTRIBUTE_NAME)
+ if (xmlClosingString=='/') {
+ useXmlClosingForEmptyTag = true
+ }
+ Encoder htmlEncoder = codecLookup?.lookupEncoder('HTML')
+ attrs.each { k, v ->
+ writer << ' '
+ writer << k
+ writer << '="'
+ writer << (htmlEncoder != null ? htmlEncoder.encode(v) : v)
+ writer << '"'
+ }
+ }
+
+ if (content) {
+ writer << '>'
+ // the following row must be written separately (append StreamCharBuffer gets appended as subchunk)
+ writer << content
+ writer << '</'
+ writer << tagname
+ writer << '>'
+ }
+ else {
+ if (!useXmlClosingForEmptyTag) {
+ writer << '>'
+ // in valid HTML , closing of an empty tag depends on the element name
+ // for empty title, the tag must be closed properly
+ // for empty meta tag shouldn't be closed at all, see GRAILS-5696
+ if (!noEndTagForEmpty) {
+ writer << '</'
+ writer << tagname
+ writer << '>'
+ }
+ }
+ else {
+ // XML / XHTML empty tag
+ writer << '/>'
+ }
+ }
+ content
+ }
+
+ def StreamCharBuffer wrapContentInBuffer(Object content) {
+ if (content instanceof Closure) {
+ content = content()
+ }
+ if (!(content instanceof StreamCharBuffer)) {
+ // the body closure might be a string constant, so wrap it in a StreamCharBuffer in that case
+ FastStringWriter stringWriter=new FastStringWriter()
+ stringWriter.print((Object)content)
+ StreamCharBuffer newbuffer = stringWriter.buffer
+ newbuffer.setPreferSubChunkWhenWritingToOtherBuffer(true)
+ return newbuffer
+ } else {
+ return (StreamCharBuffer)content
+ }
+ }
+
+ /**
+ * Captures the <head> tag.
+ */
+ Closure captureHead = { Map attrs, body ->
+ def content = captureTagContent(out, 'head', attrs, body)
+
+ if (content != null) {
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
+ if (smpage) {
+ smpage.setHeadBuffer(wrapContentInBuffer(content))
+ }
+ }
+ }
+
+ protected GSPGrailsLayoutPage findGSPGrailsLayoutPage(HttpServletRequest request) {
+ (GSPGrailsLayoutPage)request.getAttribute(GSP_GRAILS_LAYOUT_PAGE)
+ }
+
+ /**
+ * Allows passing of parameters to Grails layout.<br/>
+ *
+ * <grailsLayout:parameter name="foo" value="bar" />
+ */
+ Closure parameter = { Map attrs, body ->
+ GSPGrailsLayoutPage smpage=findGSPGrailsLayoutPage(request)
+ def name = attrs.name?.toString()
+ def val = attrs.value?.toString()
+ if (smpage && name && val != null) {
+ smpage.addProperty("page.$name", val)
+ }
+ }
+
+ /**
+ * Captures the <body> tag.
+ */
+ Closure captureBody = { Map attrs, body ->
+ def content = captureTagContent(out, 'body', attrs, body)
+ if (content != null) {
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
+ if (smpage) {
+ smpage.setBodyBuffer(wrapContentInBuffer(content))
+ if (attrs) {
+ attrs.each { k, v ->
+ smpage.addProperty("body.${k?.toString()?.toLowerCase()}", v?.toString())
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Captures the individual <content> tags.
+ */
+ Closure captureContent = { Map attrs, body ->
+ if (body != null) {
+ GSPGrailsLayoutPage smpage=findGSPGrailsLayoutPage(request)
+ if (smpage && attrs.tag) {
+ smpage.setContentBuffer(attrs.tag as String, wrapContentInBuffer(body))
+ }
+ }
+ }
+
+ /**
+ * Captures the individual <meta> tags.
+ */
+ Closure captureMeta = { Map attrs, body ->
+ def content = captureTagContent(out, 'meta', attrs, body, true)
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
Review Comment:
`def`?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/GrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,239 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.RequestConstants
+import grails.artefact.TagLibrary
+import grails.gsp.TagLib
+import groovy.transform.CompileStatic
+import jakarta.servlet.http.HttpServletRequest
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.GrailsPrintWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.encoder.CodecLookup
+import org.grails.encoder.Encoder
+import org.grails.gsp.compiler.GrailsLayoutPreprocessor
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+
+/**
+ * Internal Grails Layout pre-processor tags.
+ *
+ * @author Graeme Rocher
+ * @since 1.2
+ */
+@CompileStatic
+@TagLib
+class GrailsLayoutTagLib implements RequestConstants, TagLibrary {
+ protected static final String GSP_GRAILS_LAYOUT_PAGE = 'org.apache.grails.web.layout.EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE'
+
+ static String namespace = 'grailsLayout'
+ CodecLookup codecLookup
+
+ def captureTagContent(GrailsPrintWriter writer, String tagname, Map attrs, Object body, boolean noEndTagForEmpty=false) {
+ def content = null
+ if (body != null) {
+ if (body instanceof Closure) {
+ content = body()
+ }
+ else {
+ content = body
+ }
+ }
+
+ if (content instanceof StreamCharBuffer) {
+ content.setPreferSubChunkWhenWritingToOtherBuffer(true)
+ }
+ writer << '<'
+ writer << tagname
+ def useXmlClosingForEmptyTag = false
+ if (attrs) {
+ def xmlClosingString = attrs.remove(GrailsLayoutPreprocessor.XML_CLOSING_FOR_EMPTY_TAG_ATTRIBUTE_NAME)
+ if (xmlClosingString=='/') {
+ useXmlClosingForEmptyTag = true
+ }
+ Encoder htmlEncoder = codecLookup?.lookupEncoder('HTML')
+ attrs.each { k, v ->
+ writer << ' '
+ writer << k
+ writer << '="'
+ writer << (htmlEncoder != null ? htmlEncoder.encode(v) : v)
+ writer << '"'
+ }
+ }
+
+ if (content) {
+ writer << '>'
+ // the following row must be written separately (append StreamCharBuffer gets appended as subchunk)
+ writer << content
+ writer << '</'
+ writer << tagname
+ writer << '>'
+ }
+ else {
+ if (!useXmlClosingForEmptyTag) {
+ writer << '>'
+ // in valid HTML , closing of an empty tag depends on the element name
+ // for empty title, the tag must be closed properly
+ // for empty meta tag shouldn't be closed at all, see GRAILS-5696
+ if (!noEndTagForEmpty) {
+ writer << '</'
+ writer << tagname
+ writer << '>'
+ }
+ }
+ else {
+ // XML / XHTML empty tag
+ writer << '/>'
+ }
+ }
+ content
+ }
+
+ def StreamCharBuffer wrapContentInBuffer(Object content) {
+ if (content instanceof Closure) {
+ content = content()
+ }
+ if (!(content instanceof StreamCharBuffer)) {
+ // the body closure might be a string constant, so wrap it in a StreamCharBuffer in that case
+ FastStringWriter stringWriter=new FastStringWriter()
+ stringWriter.print((Object)content)
+ StreamCharBuffer newbuffer = stringWriter.buffer
+ newbuffer.setPreferSubChunkWhenWritingToOtherBuffer(true)
+ return newbuffer
+ } else {
+ return (StreamCharBuffer)content
+ }
+ }
+
+ /**
+ * Captures the <head> tag.
+ */
+ Closure captureHead = { Map attrs, body ->
+ def content = captureTagContent(out, 'head', attrs, body)
+
+ if (content != null) {
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
+ if (smpage) {
+ smpage.setHeadBuffer(wrapContentInBuffer(content))
+ }
+ }
+ }
+
+ protected GSPGrailsLayoutPage findGSPGrailsLayoutPage(HttpServletRequest request) {
+ (GSPGrailsLayoutPage)request.getAttribute(GSP_GRAILS_LAYOUT_PAGE)
+ }
+
+ /**
+ * Allows passing of parameters to Grails layout.<br/>
+ *
+ * <grailsLayout:parameter name="foo" value="bar" />
+ */
+ Closure parameter = { Map attrs, body ->
+ GSPGrailsLayoutPage smpage=findGSPGrailsLayoutPage(request)
+ def name = attrs.name?.toString()
+ def val = attrs.value?.toString()
+ if (smpage && name && val != null) {
+ smpage.addProperty("page.$name", val)
+ }
+ }
+
+ /**
+ * Captures the <body> tag.
+ */
+ Closure captureBody = { Map attrs, body ->
+ def content = captureTagContent(out, 'body', attrs, body)
+ if (content != null) {
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
+ if (smpage) {
+ smpage.setBodyBuffer(wrapContentInBuffer(content))
+ if (attrs) {
+ attrs.each { k, v ->
+ smpage.addProperty("body.${k?.toString()?.toLowerCase()}", v?.toString())
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Captures the individual <content> tags.
+ */
+ Closure captureContent = { Map attrs, body ->
+ if (body != null) {
+ GSPGrailsLayoutPage smpage=findGSPGrailsLayoutPage(request)
+ if (smpage && attrs.tag) {
+ smpage.setContentBuffer(attrs.tag as String, wrapContentInBuffer(body))
+ }
+ }
+ }
+
+ /**
+ * Captures the individual <meta> tags.
+ */
+ Closure captureMeta = { Map attrs, body ->
+ def content = captureTagContent(out, 'meta', attrs, body, true)
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
+ def val = attrs.content?.toString()
+ if (attrs && smpage && val != null) {
+ if (attrs.name) {
+ smpage.addProperty("meta.${attrs.name}", val)
+ smpage.addProperty("meta.${attrs.name.toString().toLowerCase()}", val)
+ }
+ else if (attrs['http-equiv']) {
+ String httpEquiv = attrs['http-equiv'] as String
+ def httpEquivFormats = [httpEquiv, httpEquiv.toLowerCase()]
+ if(httpEquiv.equalsIgnoreCase('content-type')) {
+ httpEquivFormats << 'Content-Type'
+ }
+ for (def httpEquivFormat : httpEquivFormats) {
+ smpage.addProperty("meta.http-equiv.${httpEquivFormat}", val)
+ }
+ }
+ }
+ }
+
+ /**
+ * Captures the <title> tag.
+ */
+ Closure captureTitle = { Map attrs, body ->
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
+ def content = captureTagContent(out, 'title', attrs, body)
+ if (smpage && content != null) {
+ smpage.addProperty('title', content?.toString())
+ smpage.setTitleCaptured(true)
+ }
+ }
+
+ /**
+ * Wraps the title tag so that the buffer can be cleared out from the head buffer
+ */
+ Closure wrapTitleTag = { Map attrs, body ->
+ if (body != null) {
+ GSPGrailsLayoutPage smpage=findGSPGrailsLayoutPage(request)
Review Comment:
`def`?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
+ gspGrailsLayoutPage.setPageBuffer(stringWriter.buffer)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ }
+ }
+ finally {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage)
+ }
+ }
+ if (content == null) {
+ content = ''
+ }
+
+ Page page = null
+ if (!((TypeConvertingMap) attrs).boolean('parse') && gspGrailsLayoutPage != null && gspGrailsLayoutPage.isUsed()) {
+ page = gspGrailsLayoutPage
+ } else {
+ def parser = createPageParser(contentType)
+ char[] charArray
+ if (content instanceof StreamCharBuffer) {
+ charArray = ((StreamCharBuffer) content).toCharArray()
+ } else {
+ charArray = content.toString().toCharArray()
+ }
+ page = parser.parse(charArray)
+ }
+
+ def decorator = findDecorator(request, attrs.name as String)
+ if (decorator && decorator.page) {
+ pageParams.each { k, v ->
+ page.addProperty(k as String, v as String)
+ }
+ try {
+ request.setAttribute(PAGE, page)
+ Template template = findTemplate(decorator)
+ template.make(viewModel).writeTo(out)
+ }
+ finally {
+ request.setAttribute(PAGE, oldPage)
+ }
+ } else {
+ out << content
+ }
+ }
+
+ protected Template findTemplate(Decorator decorator) {
+ Template template
+ if (decorator instanceof SpringMVCViewDecorator) {
+ template = ((SpringMVCViewDecorator) decorator).getTemplate()
+ if (template instanceof GroovyPageTemplate) {
+ GroovyPageTemplate gpt = (GroovyPageTemplate) template
+ gpt = (GroovyPageTemplate) gpt.clone()
+ gpt.setAllowSettingContentType(false)
+ template = gpt
+ }
+ } else {
+ template = groovyPagesTemplateEngine.createTemplate(decorator.getPage())
Review Comment:
Use groovy getter?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
Review Comment:
Use groovy setter?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/GrailsLayoutGrailsPlugin.groovy:
##########
@@ -0,0 +1,43 @@
+/*
+ * 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
+ *
+ * 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.plugins.web
+
+import org.grails.plugins.web.taglib.RenderGrailsLayoutTagLib
+import org.grails.plugins.web.taglib.GrailsLayoutTagLib
+
+import grails.plugins.Plugin
+import grails.util.GrailsUtil
+import groovy.util.logging.Slf4j
+
+/**
+ * Plugin responsible for Grails Layout specific configuration.
+ */
+@Slf4j
+class GrailsLayoutGrailsPlugin extends Plugin {
+ def grailsVersion = "6.0.0 > *"
Review Comment:
- Align on single quotes for strings?
- Update to `7.0.0`?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
+ gspGrailsLayoutPage.setPageBuffer(stringWriter.buffer)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ }
+ }
+ finally {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage)
+ }
+ }
+ if (content == null) {
+ content = ''
+ }
+
+ Page page = null
+ if (!((TypeConvertingMap) attrs).boolean('parse') && gspGrailsLayoutPage != null && gspGrailsLayoutPage.isUsed()) {
+ page = gspGrailsLayoutPage
+ } else {
+ def parser = createPageParser(contentType)
+ char[] charArray
+ if (content instanceof StreamCharBuffer) {
+ charArray = ((StreamCharBuffer) content).toCharArray()
+ } else {
+ charArray = content.toString().toCharArray()
+ }
+ page = parser.parse(charArray)
+ }
+
+ def decorator = findDecorator(request, attrs.name as String)
+ if (decorator && decorator.page) {
+ pageParams.each { k, v ->
+ page.addProperty(k as String, v as String)
+ }
+ try {
+ request.setAttribute(PAGE, page)
+ Template template = findTemplate(decorator)
+ template.make(viewModel).writeTo(out)
+ }
+ finally {
+ request.setAttribute(PAGE, oldPage)
+ }
+ } else {
+ out << content
+ }
+ }
+
+ protected Template findTemplate(Decorator decorator) {
+ Template template
+ if (decorator instanceof SpringMVCViewDecorator) {
+ template = ((SpringMVCViewDecorator) decorator).getTemplate()
+ if (template instanceof GroovyPageTemplate) {
+ GroovyPageTemplate gpt = (GroovyPageTemplate) template
+ gpt = (GroovyPageTemplate) gpt.clone()
+ gpt.setAllowSettingContentType(false)
+ template = gpt
+ }
+ } else {
+ template = groovyPagesTemplateEngine.createTemplate(decorator.getPage())
+ }
+ return template
+ }
+
+ protected Decorator findDecorator(HttpServletRequest req, String layoutName) {
+ DecoratorMapper decoratorMapper = sitemeshFactory?.getDecoratorMapper()
+ Decorator d
+ if (decoratorMapper) {
+ d = decoratorMapper.getNamedDecorator(req, layoutName)
+ } else {
+ d = groovyPageLayoutFinder.getNamedDecorator(req, layoutName)
+ }
+ d
+ }
+
+ protected PageParser createPageParser(String contentType) {
+ PageParser parser = sitemeshFactory?.getPageParser(contentType)
+ if (parser == null) {
+ parser = new GrailsHTMLPageParser()
+ }
+ return parser
+ }
+
+ protected Factory getSitemeshFactory() {
+ return FactoryHolder.getGrailsLayoutFactory()
Review Comment:
- No need for `return `?
- Use groovy getter?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
Review Comment:
Align on single quotes for strings?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
Review Comment:
No need for `return`?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
+ gspGrailsLayoutPage.setPageBuffer(stringWriter.buffer)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ }
+ }
+ finally {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage)
+ }
+ }
+ if (content == null) {
+ content = ''
+ }
+
+ Page page = null
+ if (!((TypeConvertingMap) attrs).boolean('parse') && gspGrailsLayoutPage != null && gspGrailsLayoutPage.isUsed()) {
+ page = gspGrailsLayoutPage
+ } else {
+ def parser = createPageParser(contentType)
+ char[] charArray
+ if (content instanceof StreamCharBuffer) {
+ charArray = ((StreamCharBuffer) content).toCharArray()
+ } else {
+ charArray = content.toString().toCharArray()
+ }
+ page = parser.parse(charArray)
+ }
+
+ def decorator = findDecorator(request, attrs.name as String)
+ if (decorator && decorator.page) {
+ pageParams.each { k, v ->
+ page.addProperty(k as String, v as String)
+ }
+ try {
+ request.setAttribute(PAGE, page)
+ Template template = findTemplate(decorator)
+ template.make(viewModel).writeTo(out)
+ }
+ finally {
+ request.setAttribute(PAGE, oldPage)
+ }
+ } else {
+ out << content
+ }
+ }
+
+ protected Template findTemplate(Decorator decorator) {
+ Template template
+ if (decorator instanceof SpringMVCViewDecorator) {
+ template = ((SpringMVCViewDecorator) decorator).getTemplate()
+ if (template instanceof GroovyPageTemplate) {
+ GroovyPageTemplate gpt = (GroovyPageTemplate) template
+ gpt = (GroovyPageTemplate) gpt.clone()
+ gpt.setAllowSettingContentType(false)
+ template = gpt
+ }
+ } else {
+ template = groovyPagesTemplateEngine.createTemplate(decorator.getPage())
+ }
+ return template
+ }
+
+ protected Decorator findDecorator(HttpServletRequest req, String layoutName) {
+ DecoratorMapper decoratorMapper = sitemeshFactory?.getDecoratorMapper()
+ Decorator d
+ if (decoratorMapper) {
+ d = decoratorMapper.getNamedDecorator(req, layoutName)
+ } else {
+ d = groovyPageLayoutFinder.getNamedDecorator(req, layoutName)
+ }
+ d
+ }
+
+ protected PageParser createPageParser(String contentType) {
+ PageParser parser = sitemeshFactory?.getPageParser(contentType)
+ if (parser == null) {
+ parser = new GrailsHTMLPageParser()
+ }
+ return parser
+ }
+
+ protected Factory getSitemeshFactory() {
+ return FactoryHolder.getGrailsLayoutFactory()
+ }
+
+ /**
+ * Used to retrieve a property of the decorated page.<br/>
+ *
+ * <g:pageProperty default="defaultValue" name="body.onload" /><br/>
+ *
+ * @emptyTag
+ *
+ * @attr REQUIRED name the property name
+ * @attr default the default value to use if the property is null
+ * @attr writeEntireProperty if true, writes the property in the form 'foo = "bar"', otherwise renders 'bar'
+ */
+ Closure pageProperty = { Map attrs ->
+ if (!attrs.name) {
+ throwTagError("Tag [pageProperty] is missing required attribute [name]")
+ }
+
+ String propertyName = attrs.name as String
+ def htmlPage = getPage()
+ def propertyValue
Review Comment:
Assign `null` to `propertyValue?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/GrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,239 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.RequestConstants
+import grails.artefact.TagLibrary
+import grails.gsp.TagLib
+import groovy.transform.CompileStatic
+import jakarta.servlet.http.HttpServletRequest
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.GrailsPrintWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.encoder.CodecLookup
+import org.grails.encoder.Encoder
+import org.grails.gsp.compiler.GrailsLayoutPreprocessor
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+
+/**
+ * Internal Grails Layout pre-processor tags.
+ *
+ * @author Graeme Rocher
+ * @since 1.2
+ */
+@CompileStatic
+@TagLib
+class GrailsLayoutTagLib implements RequestConstants, TagLibrary {
+ protected static final String GSP_GRAILS_LAYOUT_PAGE = 'org.apache.grails.web.layout.EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE'
+
+ static String namespace = 'grailsLayout'
+ CodecLookup codecLookup
+
+ def captureTagContent(GrailsPrintWriter writer, String tagname, Map attrs, Object body, boolean noEndTagForEmpty=false) {
+ def content = null
+ if (body != null) {
+ if (body instanceof Closure) {
+ content = body()
+ }
+ else {
+ content = body
+ }
+ }
+
+ if (content instanceof StreamCharBuffer) {
+ content.setPreferSubChunkWhenWritingToOtherBuffer(true)
+ }
+ writer << '<'
+ writer << tagname
+ def useXmlClosingForEmptyTag = false
+ if (attrs) {
+ def xmlClosingString = attrs.remove(GrailsLayoutPreprocessor.XML_CLOSING_FOR_EMPTY_TAG_ATTRIBUTE_NAME)
+ if (xmlClosingString=='/') {
+ useXmlClosingForEmptyTag = true
+ }
+ Encoder htmlEncoder = codecLookup?.lookupEncoder('HTML')
+ attrs.each { k, v ->
+ writer << ' '
+ writer << k
+ writer << '="'
+ writer << (htmlEncoder != null ? htmlEncoder.encode(v) : v)
+ writer << '"'
+ }
+ }
+
+ if (content) {
+ writer << '>'
+ // the following row must be written separately (append StreamCharBuffer gets appended as subchunk)
+ writer << content
+ writer << '</'
+ writer << tagname
+ writer << '>'
+ }
+ else {
+ if (!useXmlClosingForEmptyTag) {
+ writer << '>'
+ // in valid HTML , closing of an empty tag depends on the element name
+ // for empty title, the tag must be closed properly
+ // for empty meta tag shouldn't be closed at all, see GRAILS-5696
+ if (!noEndTagForEmpty) {
+ writer << '</'
+ writer << tagname
+ writer << '>'
+ }
+ }
+ else {
+ // XML / XHTML empty tag
+ writer << '/>'
+ }
+ }
+ content
+ }
+
+ def StreamCharBuffer wrapContentInBuffer(Object content) {
+ if (content instanceof Closure) {
+ content = content()
+ }
+ if (!(content instanceof StreamCharBuffer)) {
+ // the body closure might be a string constant, so wrap it in a StreamCharBuffer in that case
+ FastStringWriter stringWriter=new FastStringWriter()
+ stringWriter.print((Object)content)
+ StreamCharBuffer newbuffer = stringWriter.buffer
+ newbuffer.setPreferSubChunkWhenWritingToOtherBuffer(true)
+ return newbuffer
+ } else {
+ return (StreamCharBuffer)content
+ }
+ }
+
+ /**
+ * Captures the <head> tag.
+ */
+ Closure captureHead = { Map attrs, body ->
+ def content = captureTagContent(out, 'head', attrs, body)
+
+ if (content != null) {
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
+ if (smpage) {
+ smpage.setHeadBuffer(wrapContentInBuffer(content))
+ }
+ }
+ }
+
+ protected GSPGrailsLayoutPage findGSPGrailsLayoutPage(HttpServletRequest request) {
+ (GSPGrailsLayoutPage)request.getAttribute(GSP_GRAILS_LAYOUT_PAGE)
+ }
+
+ /**
+ * Allows passing of parameters to Grails layout.<br/>
+ *
+ * <grailsLayout:parameter name="foo" value="bar" />
+ */
+ Closure parameter = { Map attrs, body ->
+ GSPGrailsLayoutPage smpage=findGSPGrailsLayoutPage(request)
+ def name = attrs.name?.toString()
+ def val = attrs.value?.toString()
+ if (smpage && name && val != null) {
+ smpage.addProperty("page.$name", val)
+ }
+ }
+
+ /**
+ * Captures the <body> tag.
+ */
+ Closure captureBody = { Map attrs, body ->
+ def content = captureTagContent(out, 'body', attrs, body)
+ if (content != null) {
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
+ if (smpage) {
+ smpage.setBodyBuffer(wrapContentInBuffer(content))
+ if (attrs) {
+ attrs.each { k, v ->
+ smpage.addProperty("body.${k?.toString()?.toLowerCase()}", v?.toString())
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Captures the individual <content> tags.
+ */
+ Closure captureContent = { Map attrs, body ->
+ if (body != null) {
+ GSPGrailsLayoutPage smpage=findGSPGrailsLayoutPage(request)
+ if (smpage && attrs.tag) {
+ smpage.setContentBuffer(attrs.tag as String, wrapContentInBuffer(body))
+ }
+ }
+ }
+
+ /**
+ * Captures the individual <meta> tags.
+ */
+ Closure captureMeta = { Map attrs, body ->
+ def content = captureTagContent(out, 'meta', attrs, body, true)
+ GSPGrailsLayoutPage smpage = findGSPGrailsLayoutPage(request)
+ def val = attrs.content?.toString()
+ if (attrs && smpage && val != null) {
+ if (attrs.name) {
+ smpage.addProperty("meta.${attrs.name}", val)
+ smpage.addProperty("meta.${attrs.name.toString().toLowerCase()}", val)
+ }
+ else if (attrs['http-equiv']) {
+ String httpEquiv = attrs['http-equiv'] as String
+ def httpEquivFormats = [httpEquiv, httpEquiv.toLowerCase()]
+ if(httpEquiv.equalsIgnoreCase('content-type')) {
Review Comment:
Align on space after `if`?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
Review Comment:
`def`?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
Review Comment:
Align on single quotes for strings?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
+ gspGrailsLayoutPage.setPageBuffer(stringWriter.buffer)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ }
+ }
+ finally {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage)
+ }
+ }
+ if (content == null) {
+ content = ''
+ }
+
+ Page page = null
+ if (!((TypeConvertingMap) attrs).boolean('parse') && gspGrailsLayoutPage != null && gspGrailsLayoutPage.isUsed()) {
+ page = gspGrailsLayoutPage
+ } else {
+ def parser = createPageParser(contentType)
+ char[] charArray
+ if (content instanceof StreamCharBuffer) {
+ charArray = ((StreamCharBuffer) content).toCharArray()
+ } else {
+ charArray = content.toString().toCharArray()
+ }
+ page = parser.parse(charArray)
+ }
+
+ def decorator = findDecorator(request, attrs.name as String)
+ if (decorator && decorator.page) {
+ pageParams.each { k, v ->
+ page.addProperty(k as String, v as String)
+ }
+ try {
+ request.setAttribute(PAGE, page)
+ Template template = findTemplate(decorator)
+ template.make(viewModel).writeTo(out)
+ }
+ finally {
+ request.setAttribute(PAGE, oldPage)
+ }
+ } else {
+ out << content
+ }
+ }
+
+ protected Template findTemplate(Decorator decorator) {
+ Template template
+ if (decorator instanceof SpringMVCViewDecorator) {
+ template = ((SpringMVCViewDecorator) decorator).getTemplate()
+ if (template instanceof GroovyPageTemplate) {
+ GroovyPageTemplate gpt = (GroovyPageTemplate) template
+ gpt = (GroovyPageTemplate) gpt.clone()
+ gpt.setAllowSettingContentType(false)
+ template = gpt
+ }
+ } else {
+ template = groovyPagesTemplateEngine.createTemplate(decorator.getPage())
+ }
+ return template
+ }
+
+ protected Decorator findDecorator(HttpServletRequest req, String layoutName) {
+ DecoratorMapper decoratorMapper = sitemeshFactory?.getDecoratorMapper()
Review Comment:
- `def`?
- Use groovy getter?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
+ gspGrailsLayoutPage.setPageBuffer(stringWriter.buffer)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ }
+ }
+ finally {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage)
+ }
+ }
+ if (content == null) {
+ content = ''
+ }
+
+ Page page = null
+ if (!((TypeConvertingMap) attrs).boolean('parse') && gspGrailsLayoutPage != null && gspGrailsLayoutPage.isUsed()) {
+ page = gspGrailsLayoutPage
+ } else {
+ def parser = createPageParser(contentType)
+ char[] charArray
+ if (content instanceof StreamCharBuffer) {
+ charArray = ((StreamCharBuffer) content).toCharArray()
+ } else {
+ charArray = content.toString().toCharArray()
+ }
+ page = parser.parse(charArray)
+ }
+
+ def decorator = findDecorator(request, attrs.name as String)
+ if (decorator && decorator.page) {
+ pageParams.each { k, v ->
+ page.addProperty(k as String, v as String)
+ }
+ try {
+ request.setAttribute(PAGE, page)
+ Template template = findTemplate(decorator)
+ template.make(viewModel).writeTo(out)
+ }
+ finally {
+ request.setAttribute(PAGE, oldPage)
+ }
+ } else {
+ out << content
+ }
+ }
+
+ protected Template findTemplate(Decorator decorator) {
+ Template template
+ if (decorator instanceof SpringMVCViewDecorator) {
+ template = ((SpringMVCViewDecorator) decorator).getTemplate()
+ if (template instanceof GroovyPageTemplate) {
+ GroovyPageTemplate gpt = (GroovyPageTemplate) template
+ gpt = (GroovyPageTemplate) gpt.clone()
+ gpt.setAllowSettingContentType(false)
+ template = gpt
+ }
+ } else {
+ template = groovyPagesTemplateEngine.createTemplate(decorator.getPage())
+ }
+ return template
+ }
+
+ protected Decorator findDecorator(HttpServletRequest req, String layoutName) {
+ DecoratorMapper decoratorMapper = sitemeshFactory?.getDecoratorMapper()
+ Decorator d
+ if (decoratorMapper) {
+ d = decoratorMapper.getNamedDecorator(req, layoutName)
+ } else {
+ d = groovyPageLayoutFinder.getNamedDecorator(req, layoutName)
+ }
+ d
+ }
+
+ protected PageParser createPageParser(String contentType) {
+ PageParser parser = sitemeshFactory?.getPageParser(contentType)
Review Comment:
`def`?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
+ gspGrailsLayoutPage.setPageBuffer(stringWriter.buffer)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ }
+ }
+ finally {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage)
+ }
+ }
+ if (content == null) {
+ content = ''
+ }
+
+ Page page = null
+ if (!((TypeConvertingMap) attrs).boolean('parse') && gspGrailsLayoutPage != null && gspGrailsLayoutPage.isUsed()) {
+ page = gspGrailsLayoutPage
+ } else {
+ def parser = createPageParser(contentType)
+ char[] charArray
+ if (content instanceof StreamCharBuffer) {
+ charArray = ((StreamCharBuffer) content).toCharArray()
+ } else {
+ charArray = content.toString().toCharArray()
+ }
+ page = parser.parse(charArray)
+ }
+
+ def decorator = findDecorator(request, attrs.name as String)
+ if (decorator && decorator.page) {
+ pageParams.each { k, v ->
+ page.addProperty(k as String, v as String)
+ }
+ try {
+ request.setAttribute(PAGE, page)
+ Template template = findTemplate(decorator)
+ template.make(viewModel).writeTo(out)
+ }
+ finally {
+ request.setAttribute(PAGE, oldPage)
+ }
+ } else {
+ out << content
+ }
+ }
+
+ protected Template findTemplate(Decorator decorator) {
+ Template template
+ if (decorator instanceof SpringMVCViewDecorator) {
+ template = ((SpringMVCViewDecorator) decorator).getTemplate()
+ if (template instanceof GroovyPageTemplate) {
+ GroovyPageTemplate gpt = (GroovyPageTemplate) template
+ gpt = (GroovyPageTemplate) gpt.clone()
+ gpt.setAllowSettingContentType(false)
Review Comment:
Use groovy setter?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
+ gspGrailsLayoutPage.setPageBuffer(stringWriter.buffer)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ }
+ }
+ finally {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage)
+ }
+ }
+ if (content == null) {
+ content = ''
+ }
+
+ Page page = null
+ if (!((TypeConvertingMap) attrs).boolean('parse') && gspGrailsLayoutPage != null && gspGrailsLayoutPage.isUsed()) {
+ page = gspGrailsLayoutPage
+ } else {
+ def parser = createPageParser(contentType)
+ char[] charArray
+ if (content instanceof StreamCharBuffer) {
+ charArray = ((StreamCharBuffer) content).toCharArray()
+ } else {
+ charArray = content.toString().toCharArray()
+ }
+ page = parser.parse(charArray)
+ }
+
+ def decorator = findDecorator(request, attrs.name as String)
+ if (decorator && decorator.page) {
+ pageParams.each { k, v ->
+ page.addProperty(k as String, v as String)
+ }
+ try {
+ request.setAttribute(PAGE, page)
+ Template template = findTemplate(decorator)
+ template.make(viewModel).writeTo(out)
+ }
+ finally {
+ request.setAttribute(PAGE, oldPage)
+ }
+ } else {
+ out << content
+ }
+ }
+
+ protected Template findTemplate(Decorator decorator) {
+ Template template
+ if (decorator instanceof SpringMVCViewDecorator) {
+ template = ((SpringMVCViewDecorator) decorator).getTemplate()
+ if (template instanceof GroovyPageTemplate) {
+ GroovyPageTemplate gpt = (GroovyPageTemplate) template
+ gpt = (GroovyPageTemplate) gpt.clone()
+ gpt.setAllowSettingContentType(false)
+ template = gpt
+ }
+ } else {
+ template = groovyPagesTemplateEngine.createTemplate(decorator.getPage())
+ }
+ return template
+ }
+
+ protected Decorator findDecorator(HttpServletRequest req, String layoutName) {
+ DecoratorMapper decoratorMapper = sitemeshFactory?.getDecoratorMapper()
+ Decorator d
+ if (decoratorMapper) {
+ d = decoratorMapper.getNamedDecorator(req, layoutName)
+ } else {
+ d = groovyPageLayoutFinder.getNamedDecorator(req, layoutName)
+ }
+ d
+ }
+
+ protected PageParser createPageParser(String contentType) {
+ PageParser parser = sitemeshFactory?.getPageParser(contentType)
+ if (parser == null) {
+ parser = new GrailsHTMLPageParser()
+ }
+ return parser
+ }
+
+ protected Factory getSitemeshFactory() {
+ return FactoryHolder.getGrailsLayoutFactory()
+ }
+
+ /**
+ * Used to retrieve a property of the decorated page.<br/>
+ *
+ * <g:pageProperty default="defaultValue" name="body.onload" /><br/>
+ *
+ * @emptyTag
+ *
+ * @attr REQUIRED name the property name
+ * @attr default the default value to use if the property is null
+ * @attr writeEntireProperty if true, writes the property in the form 'foo = "bar"', otherwise renders 'bar'
+ */
+ Closure pageProperty = { Map attrs ->
+ if (!attrs.name) {
+ throwTagError("Tag [pageProperty] is missing required attribute [name]")
+ }
+
+ String propertyName = attrs.name as String
+ def htmlPage = getPage()
+ def propertyValue
+
+ if (htmlPage instanceof GSPGrailsLayoutPage) {
+ // check if there is an component content buffer
+ propertyValue = ((GSPGrailsLayoutPage) htmlPage).getContentBuffer(propertyName)
+ }
+
+ if (!propertyValue) {
+ propertyValue = htmlPage.getProperty(propertyName)
+ }
+
+ if (!propertyValue) {
+ propertyValue = attrs.'default'
+ }
+
+ if (propertyValue) {
+ if (attrs.writeEntireProperty) {
+ out << ' '
+ out << propertyName.substring(propertyName.lastIndexOf('.') + 1)
+ out << "=\""
+ out << propertyValue
+ out << "\""
+ } else {
+ out << propertyValue
+ }
+ }
+ }
+
+ /**
+ * Invokes the body of this tag if the page property exists:<br/>
+ *
+ * <g:ifPageProperty name="meta.index">body to invoke</g:ifPageProperty><br/>
+ *
+ * or it equals a certain value:<br/>
+ *
+ * <g:ifPageProperty name="meta.index" equals="blah">body to invoke</g:ifPageProperty>
+ *
+ * @attr name REQUIRED the property name
+ * @attr equals optional value to test against
+ */
+ Closure ifPageProperty = { Map attrs, body ->
+ if (!attrs.name) {
+ return
+ }
+
+ def htmlPage = getPage()
+ List names = ((attrs.name instanceof List) ? (List) attrs.name : [attrs.name])
+
+ def invokeBody = true
+ for (i in 0..<names.size()) {
+ String propertyName = names[i] as String
+ def propertyValue = null
+ if (htmlPage instanceof GSPGrailsLayoutPage) {
+ // check if there is an component content buffer
+ propertyValue = htmlPage.getContentBuffer(propertyName)
Review Comment:
Cast to `GSPGrailsLayoutPage`?
```groovy
propertyValue = ((GSPGrailsLayoutPage) htmlPage).getContentBuffer(propertyName)
```
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
+ gspGrailsLayoutPage.setPageBuffer(stringWriter.buffer)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ }
+ }
+ finally {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage)
+ }
+ }
+ if (content == null) {
+ content = ''
+ }
+
+ Page page = null
+ if (!((TypeConvertingMap) attrs).boolean('parse') && gspGrailsLayoutPage != null && gspGrailsLayoutPage.isUsed()) {
Review Comment:
Use groovy getter?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
+ gspGrailsLayoutPage.setPageBuffer(stringWriter.buffer)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ }
+ }
+ finally {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage)
+ }
+ }
+ if (content == null) {
+ content = ''
+ }
+
+ Page page = null
+ if (!((TypeConvertingMap) attrs).boolean('parse') && gspGrailsLayoutPage != null && gspGrailsLayoutPage.isUsed()) {
+ page = gspGrailsLayoutPage
+ } else {
+ def parser = createPageParser(contentType)
+ char[] charArray
+ if (content instanceof StreamCharBuffer) {
+ charArray = ((StreamCharBuffer) content).toCharArray()
+ } else {
+ charArray = content.toString().toCharArray()
+ }
+ page = parser.parse(charArray)
+ }
+
+ def decorator = findDecorator(request, attrs.name as String)
+ if (decorator && decorator.page) {
+ pageParams.each { k, v ->
+ page.addProperty(k as String, v as String)
+ }
+ try {
+ request.setAttribute(PAGE, page)
+ Template template = findTemplate(decorator)
+ template.make(viewModel).writeTo(out)
+ }
+ finally {
+ request.setAttribute(PAGE, oldPage)
+ }
+ } else {
+ out << content
+ }
+ }
+
+ protected Template findTemplate(Decorator decorator) {
+ Template template
+ if (decorator instanceof SpringMVCViewDecorator) {
+ template = ((SpringMVCViewDecorator) decorator).getTemplate()
+ if (template instanceof GroovyPageTemplate) {
+ GroovyPageTemplate gpt = (GroovyPageTemplate) template
+ gpt = (GroovyPageTemplate) gpt.clone()
+ gpt.setAllowSettingContentType(false)
+ template = gpt
+ }
+ } else {
+ template = groovyPagesTemplateEngine.createTemplate(decorator.getPage())
+ }
+ return template
+ }
+
+ protected Decorator findDecorator(HttpServletRequest req, String layoutName) {
+ DecoratorMapper decoratorMapper = sitemeshFactory?.getDecoratorMapper()
+ Decorator d
+ if (decoratorMapper) {
+ d = decoratorMapper.getNamedDecorator(req, layoutName)
+ } else {
+ d = groovyPageLayoutFinder.getNamedDecorator(req, layoutName)
+ }
+ d
+ }
+
+ protected PageParser createPageParser(String contentType) {
+ PageParser parser = sitemeshFactory?.getPageParser(contentType)
+ if (parser == null) {
+ parser = new GrailsHTMLPageParser()
+ }
+ return parser
+ }
+
+ protected Factory getSitemeshFactory() {
+ return FactoryHolder.getGrailsLayoutFactory()
+ }
+
+ /**
+ * Used to retrieve a property of the decorated page.<br/>
+ *
+ * <g:pageProperty default="defaultValue" name="body.onload" /><br/>
+ *
+ * @emptyTag
+ *
+ * @attr REQUIRED name the property name
+ * @attr default the default value to use if the property is null
+ * @attr writeEntireProperty if true, writes the property in the form 'foo = "bar"', otherwise renders 'bar'
+ */
+ Closure pageProperty = { Map attrs ->
+ if (!attrs.name) {
+ throwTagError("Tag [pageProperty] is missing required attribute [name]")
+ }
+
+ String propertyName = attrs.name as String
+ def htmlPage = getPage()
+ def propertyValue
+
+ if (htmlPage instanceof GSPGrailsLayoutPage) {
+ // check if there is an component content buffer
+ propertyValue = ((GSPGrailsLayoutPage) htmlPage).getContentBuffer(propertyName)
+ }
+
+ if (!propertyValue) {
+ propertyValue = htmlPage.getProperty(propertyName)
+ }
+
+ if (!propertyValue) {
+ propertyValue = attrs.'default'
+ }
+
+ if (propertyValue) {
+ if (attrs.writeEntireProperty) {
+ out << ' '
+ out << propertyName.substring(propertyName.lastIndexOf('.') + 1)
+ out << "=\""
+ out << propertyValue
+ out << "\""
+ } else {
+ out << propertyValue
+ }
+ }
+ }
+
+ /**
+ * Invokes the body of this tag if the page property exists:<br/>
+ *
+ * <g:ifPageProperty name="meta.index">body to invoke</g:ifPageProperty><br/>
+ *
+ * or it equals a certain value:<br/>
+ *
+ * <g:ifPageProperty name="meta.index" equals="blah">body to invoke</g:ifPageProperty>
+ *
+ * @attr name REQUIRED the property name
+ * @attr equals optional value to test against
+ */
+ Closure ifPageProperty = { Map attrs, body ->
+ if (!attrs.name) {
+ return
+ }
+
+ def htmlPage = getPage()
+ List names = ((attrs.name instanceof List) ? (List) attrs.name : [attrs.name])
+
+ def invokeBody = true
+ for (i in 0..<names.size()) {
+ String propertyName = names[i] as String
+ def propertyValue = null
+ if (htmlPage instanceof GSPGrailsLayoutPage) {
+ // check if there is an component content buffer
+ propertyValue = htmlPage.getContentBuffer(propertyName)
+ }
+
+ if (!propertyValue) {
+ propertyValue = htmlPage.getProperty(propertyName)
+ }
+
+ if (propertyValue) {
+ if (attrs.containsKey('equals')) {
+ if (attrs.equals instanceof List) {
+ invokeBody = ((List) attrs.equals)[i] == propertyValue
+ } else {
+ invokeBody = attrs.equals == propertyValue
+ }
+ }
+ } else {
+ invokeBody = false
+ break
+ }
+ }
+ if (invokeBody && body instanceof Closure) {
+ out << body()
+ }
+ }
+
+ /**
+ * Used in layouts to render the page title from the SiteMesh page.<br/>
+ *
+ * <g:layoutTitle default="The Default title" />
+ *
+ * @emptyTag
+ *
+ * @attr default the value to use if the title isn't specified in the GSP
+ */
+ Closure layoutTitle = { Map attrs ->
+ String title = page.title
+ if (!title && attrs.'default') title = attrs.'default'
+ if (title) out << title
+ }
+
+ /**
+ * Used in layouts to render the body of a SiteMesh layout.<br/>
+ *
+ * <g:layoutBody />
+ *
+ * @emptyTag
+ */
+ Closure layoutBody = { Map attrs ->
+ getPage().writeBody(out)
+ }
+
+ /**
+ * Used in layouts to render the head of a SiteMesh layout.<br/>
+ *
+ * <g:layoutHead />
+ *
+ * @emptyTag
+ */
+ Closure layoutHead = { Map attrs ->
+ getPage().writeHead(out)
Review Comment:
Use groovy getter?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
+ gspGrailsLayoutPage.setPageBuffer(stringWriter.buffer)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ }
+ }
+ finally {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage)
+ }
+ }
+ if (content == null) {
+ content = ''
+ }
+
+ Page page = null
+ if (!((TypeConvertingMap) attrs).boolean('parse') && gspGrailsLayoutPage != null && gspGrailsLayoutPage.isUsed()) {
+ page = gspGrailsLayoutPage
+ } else {
+ def parser = createPageParser(contentType)
+ char[] charArray
+ if (content instanceof StreamCharBuffer) {
+ charArray = ((StreamCharBuffer) content).toCharArray()
+ } else {
+ charArray = content.toString().toCharArray()
+ }
+ page = parser.parse(charArray)
+ }
+
+ def decorator = findDecorator(request, attrs.name as String)
+ if (decorator && decorator.page) {
+ pageParams.each { k, v ->
+ page.addProperty(k as String, v as String)
+ }
+ try {
+ request.setAttribute(PAGE, page)
+ Template template = findTemplate(decorator)
+ template.make(viewModel).writeTo(out)
+ }
+ finally {
+ request.setAttribute(PAGE, oldPage)
+ }
+ } else {
+ out << content
+ }
+ }
+
+ protected Template findTemplate(Decorator decorator) {
+ Template template
+ if (decorator instanceof SpringMVCViewDecorator) {
+ template = ((SpringMVCViewDecorator) decorator).getTemplate()
+ if (template instanceof GroovyPageTemplate) {
+ GroovyPageTemplate gpt = (GroovyPageTemplate) template
+ gpt = (GroovyPageTemplate) gpt.clone()
+ gpt.setAllowSettingContentType(false)
+ template = gpt
+ }
+ } else {
+ template = groovyPagesTemplateEngine.createTemplate(decorator.getPage())
+ }
+ return template
+ }
+
+ protected Decorator findDecorator(HttpServletRequest req, String layoutName) {
+ DecoratorMapper decoratorMapper = sitemeshFactory?.getDecoratorMapper()
+ Decorator d
+ if (decoratorMapper) {
+ d = decoratorMapper.getNamedDecorator(req, layoutName)
+ } else {
+ d = groovyPageLayoutFinder.getNamedDecorator(req, layoutName)
+ }
+ d
+ }
+
+ protected PageParser createPageParser(String contentType) {
+ PageParser parser = sitemeshFactory?.getPageParser(contentType)
+ if (parser == null) {
+ parser = new GrailsHTMLPageParser()
+ }
+ return parser
+ }
+
+ protected Factory getSitemeshFactory() {
+ return FactoryHolder.getGrailsLayoutFactory()
+ }
+
+ /**
+ * Used to retrieve a property of the decorated page.<br/>
+ *
+ * <g:pageProperty default="defaultValue" name="body.onload" /><br/>
+ *
+ * @emptyTag
+ *
+ * @attr REQUIRED name the property name
+ * @attr default the default value to use if the property is null
+ * @attr writeEntireProperty if true, writes the property in the form 'foo = "bar"', otherwise renders 'bar'
+ */
+ Closure pageProperty = { Map attrs ->
+ if (!attrs.name) {
+ throwTagError("Tag [pageProperty] is missing required attribute [name]")
+ }
+
+ String propertyName = attrs.name as String
+ def htmlPage = getPage()
+ def propertyValue
+
+ if (htmlPage instanceof GSPGrailsLayoutPage) {
+ // check if there is an component content buffer
+ propertyValue = ((GSPGrailsLayoutPage) htmlPage).getContentBuffer(propertyName)
+ }
+
+ if (!propertyValue) {
+ propertyValue = htmlPage.getProperty(propertyName)
+ }
+
+ if (!propertyValue) {
+ propertyValue = attrs.'default'
+ }
+
+ if (propertyValue) {
+ if (attrs.writeEntireProperty) {
+ out << ' '
+ out << propertyName.substring(propertyName.lastIndexOf('.') + 1)
+ out << "=\""
+ out << propertyValue
+ out << "\""
+ } else {
+ out << propertyValue
+ }
+ }
+ }
+
+ /**
+ * Invokes the body of this tag if the page property exists:<br/>
+ *
+ * <g:ifPageProperty name="meta.index">body to invoke</g:ifPageProperty><br/>
+ *
+ * or it equals a certain value:<br/>
+ *
+ * <g:ifPageProperty name="meta.index" equals="blah">body to invoke</g:ifPageProperty>
+ *
+ * @attr name REQUIRED the property name
+ * @attr equals optional value to test against
+ */
+ Closure ifPageProperty = { Map attrs, body ->
+ if (!attrs.name) {
+ return
+ }
+
+ def htmlPage = getPage()
+ List names = ((attrs.name instanceof List) ? (List) attrs.name : [attrs.name])
+
+ def invokeBody = true
+ for (i in 0..<names.size()) {
+ String propertyName = names[i] as String
+ def propertyValue = null
+ if (htmlPage instanceof GSPGrailsLayoutPage) {
+ // check if there is an component content buffer
+ propertyValue = htmlPage.getContentBuffer(propertyName)
+ }
+
+ if (!propertyValue) {
+ propertyValue = htmlPage.getProperty(propertyName)
+ }
+
+ if (propertyValue) {
+ if (attrs.containsKey('equals')) {
+ if (attrs.equals instanceof List) {
+ invokeBody = ((List) attrs.equals)[i] == propertyValue
+ } else {
+ invokeBody = attrs.equals == propertyValue
+ }
+ }
+ } else {
+ invokeBody = false
+ break
+ }
+ }
+ if (invokeBody && body instanceof Closure) {
+ out << body()
+ }
+ }
+
+ /**
+ * Used in layouts to render the page title from the SiteMesh page.<br/>
+ *
+ * <g:layoutTitle default="The Default title" />
+ *
+ * @emptyTag
+ *
+ * @attr default the value to use if the title isn't specified in the GSP
+ */
+ Closure layoutTitle = { Map attrs ->
+ String title = page.title
+ if (!title && attrs.'default') title = attrs.'default'
+ if (title) out << title
+ }
+
+ /**
+ * Used in layouts to render the body of a SiteMesh layout.<br/>
+ *
+ * <g:layoutBody />
+ *
+ * @emptyTag
+ */
+ Closure layoutBody = { Map attrs ->
+ getPage().writeBody(out)
Review Comment:
Use groovy getter?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
+ gspGrailsLayoutPage.setPageBuffer(stringWriter.buffer)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ }
+ }
+ finally {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage)
+ }
+ }
+ if (content == null) {
+ content = ''
+ }
+
+ Page page = null
+ if (!((TypeConvertingMap) attrs).boolean('parse') && gspGrailsLayoutPage != null && gspGrailsLayoutPage.isUsed()) {
+ page = gspGrailsLayoutPage
+ } else {
+ def parser = createPageParser(contentType)
+ char[] charArray
+ if (content instanceof StreamCharBuffer) {
+ charArray = ((StreamCharBuffer) content).toCharArray()
+ } else {
+ charArray = content.toString().toCharArray()
+ }
+ page = parser.parse(charArray)
+ }
+
+ def decorator = findDecorator(request, attrs.name as String)
+ if (decorator && decorator.page) {
+ pageParams.each { k, v ->
+ page.addProperty(k as String, v as String)
+ }
+ try {
+ request.setAttribute(PAGE, page)
+ Template template = findTemplate(decorator)
+ template.make(viewModel).writeTo(out)
+ }
+ finally {
+ request.setAttribute(PAGE, oldPage)
+ }
+ } else {
+ out << content
+ }
+ }
+
+ protected Template findTemplate(Decorator decorator) {
+ Template template
+ if (decorator instanceof SpringMVCViewDecorator) {
+ template = ((SpringMVCViewDecorator) decorator).getTemplate()
+ if (template instanceof GroovyPageTemplate) {
+ GroovyPageTemplate gpt = (GroovyPageTemplate) template
+ gpt = (GroovyPageTemplate) gpt.clone()
+ gpt.setAllowSettingContentType(false)
+ template = gpt
+ }
+ } else {
+ template = groovyPagesTemplateEngine.createTemplate(decorator.getPage())
+ }
+ return template
+ }
+
+ protected Decorator findDecorator(HttpServletRequest req, String layoutName) {
+ DecoratorMapper decoratorMapper = sitemeshFactory?.getDecoratorMapper()
+ Decorator d
+ if (decoratorMapper) {
+ d = decoratorMapper.getNamedDecorator(req, layoutName)
+ } else {
+ d = groovyPageLayoutFinder.getNamedDecorator(req, layoutName)
+ }
+ d
Review Comment:
Use return on methods with logic?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
+ gspGrailsLayoutPage.setPageBuffer(stringWriter.buffer)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ }
+ }
+ finally {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage)
+ }
+ }
+ if (content == null) {
+ content = ''
+ }
+
+ Page page = null
+ if (!((TypeConvertingMap) attrs).boolean('parse') && gspGrailsLayoutPage != null && gspGrailsLayoutPage.isUsed()) {
+ page = gspGrailsLayoutPage
+ } else {
+ def parser = createPageParser(contentType)
+ char[] charArray
+ if (content instanceof StreamCharBuffer) {
+ charArray = ((StreamCharBuffer) content).toCharArray()
+ } else {
+ charArray = content.toString().toCharArray()
+ }
+ page = parser.parse(charArray)
+ }
+
+ def decorator = findDecorator(request, attrs.name as String)
+ if (decorator && decorator.page) {
+ pageParams.each { k, v ->
+ page.addProperty(k as String, v as String)
+ }
+ try {
+ request.setAttribute(PAGE, page)
+ Template template = findTemplate(decorator)
+ template.make(viewModel).writeTo(out)
+ }
+ finally {
+ request.setAttribute(PAGE, oldPage)
+ }
+ } else {
+ out << content
+ }
+ }
+
+ protected Template findTemplate(Decorator decorator) {
+ Template template
+ if (decorator instanceof SpringMVCViewDecorator) {
+ template = ((SpringMVCViewDecorator) decorator).getTemplate()
+ if (template instanceof GroovyPageTemplate) {
+ GroovyPageTemplate gpt = (GroovyPageTemplate) template
+ gpt = (GroovyPageTemplate) gpt.clone()
+ gpt.setAllowSettingContentType(false)
+ template = gpt
+ }
+ } else {
+ template = groovyPagesTemplateEngine.createTemplate(decorator.getPage())
+ }
+ return template
+ }
+
+ protected Decorator findDecorator(HttpServletRequest req, String layoutName) {
+ DecoratorMapper decoratorMapper = sitemeshFactory?.getDecoratorMapper()
+ Decorator d
+ if (decoratorMapper) {
+ d = decoratorMapper.getNamedDecorator(req, layoutName)
+ } else {
+ d = groovyPageLayoutFinder.getNamedDecorator(req, layoutName)
+ }
+ d
+ }
+
+ protected PageParser createPageParser(String contentType) {
+ PageParser parser = sitemeshFactory?.getPageParser(contentType)
+ if (parser == null) {
+ parser = new GrailsHTMLPageParser()
+ }
+ return parser
+ }
+
+ protected Factory getSitemeshFactory() {
+ return FactoryHolder.getGrailsLayoutFactory()
+ }
+
+ /**
+ * Used to retrieve a property of the decorated page.<br/>
+ *
+ * <g:pageProperty default="defaultValue" name="body.onload" /><br/>
+ *
+ * @emptyTag
+ *
+ * @attr REQUIRED name the property name
+ * @attr default the default value to use if the property is null
+ * @attr writeEntireProperty if true, writes the property in the form 'foo = "bar"', otherwise renders 'bar'
+ */
+ Closure pageProperty = { Map attrs ->
+ if (!attrs.name) {
+ throwTagError("Tag [pageProperty] is missing required attribute [name]")
+ }
+
+ String propertyName = attrs.name as String
+ def htmlPage = getPage()
+ def propertyValue
+
+ if (htmlPage instanceof GSPGrailsLayoutPage) {
+ // check if there is an component content buffer
+ propertyValue = ((GSPGrailsLayoutPage) htmlPage).getContentBuffer(propertyName)
+ }
+
+ if (!propertyValue) {
+ propertyValue = htmlPage.getProperty(propertyName)
+ }
+
+ if (!propertyValue) {
+ propertyValue = attrs.'default'
+ }
+
+ if (propertyValue) {
+ if (attrs.writeEntireProperty) {
+ out << ' '
+ out << propertyName.substring(propertyName.lastIndexOf('.') + 1)
+ out << "=\""
+ out << propertyValue
+ out << "\""
Review Comment:
Use single quotes for strings?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
+ gspGrailsLayoutPage.setPageBuffer(stringWriter.buffer)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ }
+ }
+ finally {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage)
+ }
+ }
+ if (content == null) {
+ content = ''
+ }
+
+ Page page = null
+ if (!((TypeConvertingMap) attrs).boolean('parse') && gspGrailsLayoutPage != null && gspGrailsLayoutPage.isUsed()) {
+ page = gspGrailsLayoutPage
+ } else {
+ def parser = createPageParser(contentType)
+ char[] charArray
+ if (content instanceof StreamCharBuffer) {
+ charArray = ((StreamCharBuffer) content).toCharArray()
+ } else {
+ charArray = content.toString().toCharArray()
+ }
+ page = parser.parse(charArray)
+ }
+
+ def decorator = findDecorator(request, attrs.name as String)
+ if (decorator && decorator.page) {
+ pageParams.each { k, v ->
+ page.addProperty(k as String, v as String)
+ }
+ try {
+ request.setAttribute(PAGE, page)
+ Template template = findTemplate(decorator)
+ template.make(viewModel).writeTo(out)
+ }
+ finally {
+ request.setAttribute(PAGE, oldPage)
+ }
+ } else {
+ out << content
+ }
+ }
+
+ protected Template findTemplate(Decorator decorator) {
+ Template template
+ if (decorator instanceof SpringMVCViewDecorator) {
+ template = ((SpringMVCViewDecorator) decorator).getTemplate()
+ if (template instanceof GroovyPageTemplate) {
+ GroovyPageTemplate gpt = (GroovyPageTemplate) template
+ gpt = (GroovyPageTemplate) gpt.clone()
+ gpt.setAllowSettingContentType(false)
+ template = gpt
+ }
+ } else {
+ template = groovyPagesTemplateEngine.createTemplate(decorator.getPage())
+ }
+ return template
+ }
+
+ protected Decorator findDecorator(HttpServletRequest req, String layoutName) {
+ DecoratorMapper decoratorMapper = sitemeshFactory?.getDecoratorMapper()
+ Decorator d
+ if (decoratorMapper) {
+ d = decoratorMapper.getNamedDecorator(req, layoutName)
+ } else {
+ d = groovyPageLayoutFinder.getNamedDecorator(req, layoutName)
+ }
+ d
+ }
+
+ protected PageParser createPageParser(String contentType) {
+ PageParser parser = sitemeshFactory?.getPageParser(contentType)
+ if (parser == null) {
+ parser = new GrailsHTMLPageParser()
+ }
+ return parser
+ }
+
+ protected Factory getSitemeshFactory() {
+ return FactoryHolder.getGrailsLayoutFactory()
+ }
+
+ /**
+ * Used to retrieve a property of the decorated page.<br/>
+ *
+ * <g:pageProperty default="defaultValue" name="body.onload" /><br/>
+ *
+ * @emptyTag
+ *
+ * @attr REQUIRED name the property name
+ * @attr default the default value to use if the property is null
+ * @attr writeEntireProperty if true, writes the property in the form 'foo = "bar"', otherwise renders 'bar'
+ */
+ Closure pageProperty = { Map attrs ->
+ if (!attrs.name) {
+ throwTagError("Tag [pageProperty] is missing required attribute [name]")
Review Comment:
Align on single quotes for strings?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
+ gspGrailsLayoutPage.setPageBuffer(stringWriter.buffer)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
Review Comment:
Use groovy setters?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "codeconsole (via GitHub)" <gi...@apache.org>.
codeconsole commented on PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#issuecomment-3028841244
no, it has to do with references to sitemesh logic in traits that are compiled into a lot of plugin classes. When you try to use them in a future version, you get an exception. The only workaround at that point is re-releasing every plugin.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#issuecomment-3033952244
Almost all of this feedback was on the original code that I restored without changing. I agree with cleaning it up, but I originally hadn't to reduce the potential for problems. My last commit contains all of the feedback.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on code in PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#discussion_r2183934929
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
+ gspGrailsLayoutPage.setPageBuffer(stringWriter.buffer)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ }
+ }
+ finally {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage)
+ }
+ }
+ if (content == null) {
+ content = ''
+ }
+
+ Page page = null
+ if (!((TypeConvertingMap) attrs).boolean('parse') && gspGrailsLayoutPage != null && gspGrailsLayoutPage.isUsed()) {
+ page = gspGrailsLayoutPage
+ } else {
+ def parser = createPageParser(contentType)
+ char[] charArray
+ if (content instanceof StreamCharBuffer) {
+ charArray = ((StreamCharBuffer) content).toCharArray()
+ } else {
+ charArray = content.toString().toCharArray()
+ }
+ page = parser.parse(charArray)
+ }
+
+ def decorator = findDecorator(request, attrs.name as String)
+ if (decorator && decorator.page) {
+ pageParams.each { k, v ->
+ page.addProperty(k as String, v as String)
+ }
+ try {
+ request.setAttribute(PAGE, page)
+ Template template = findTemplate(decorator)
+ template.make(viewModel).writeTo(out)
+ }
+ finally {
+ request.setAttribute(PAGE, oldPage)
+ }
+ } else {
+ out << content
+ }
+ }
+
+ protected Template findTemplate(Decorator decorator) {
+ Template template
+ if (decorator instanceof SpringMVCViewDecorator) {
+ template = ((SpringMVCViewDecorator) decorator).getTemplate()
+ if (template instanceof GroovyPageTemplate) {
+ GroovyPageTemplate gpt = (GroovyPageTemplate) template
+ gpt = (GroovyPageTemplate) gpt.clone()
+ gpt.setAllowSettingContentType(false)
+ template = gpt
+ }
+ } else {
+ template = groovyPagesTemplateEngine.createTemplate(decorator.getPage())
+ }
+ return template
+ }
+
+ protected Decorator findDecorator(HttpServletRequest req, String layoutName) {
+ DecoratorMapper decoratorMapper = sitemeshFactory?.getDecoratorMapper()
+ Decorator d
+ if (decoratorMapper) {
+ d = decoratorMapper.getNamedDecorator(req, layoutName)
+ } else {
+ d = groovyPageLayoutFinder.getNamedDecorator(req, layoutName)
+ }
+ d
+ }
+
+ protected PageParser createPageParser(String contentType) {
+ PageParser parser = sitemeshFactory?.getPageParser(contentType)
+ if (parser == null) {
+ parser = new GrailsHTMLPageParser()
+ }
+ return parser
+ }
+
+ protected Factory getSitemeshFactory() {
+ return FactoryHolder.getGrailsLayoutFactory()
+ }
+
+ /**
+ * Used to retrieve a property of the decorated page.<br/>
+ *
+ * <g:pageProperty default="defaultValue" name="body.onload" /><br/>
+ *
+ * @emptyTag
+ *
+ * @attr REQUIRED name the property name
+ * @attr default the default value to use if the property is null
+ * @attr writeEntireProperty if true, writes the property in the form 'foo = "bar"', otherwise renders 'bar'
+ */
+ Closure pageProperty = { Map attrs ->
+ if (!attrs.name) {
+ throwTagError("Tag [pageProperty] is missing required attribute [name]")
+ }
+
+ String propertyName = attrs.name as String
+ def htmlPage = getPage()
+ def propertyValue
Review Comment:
Since there's an if checking if it's not set, set to null, it would warn the assignment is not used. I think we should leave this.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#issuecomment-3035793768
This PR now is only a single commit with only the sitemesh2 revert.
https://github.com/apache/grails-core/pull/14876 contains all of the feedback / cleanup related commits prior to the sitemesh revert.
This will allow us to revert one commit to have sitemesh3
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#issuecomment-3028797516
Can you please help me understand what would cause a plugin to break? I assume it's the GSP compiled logic? I think we may have to deal with that since it's the same as 6.x. We're open to suggestions on how to reduce this. The core issue is the architecture difference and not integrating into the Spring DispatcherServlet.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on code in PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#discussion_r2183908644
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
Review Comment:
I've gone ahead and replaced every instance in the code base with StandardCharsets.UTF_8 where possible, and StandardCharsets.UTF_8.name() where not.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "matrei (via GitHub)" <gi...@apache.org>.
matrei commented on PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#issuecomment-3034801874
> I had to revert the cast that shows an error in intellij. Otherwise, a large amount of tests fail with a cast exception.
That is interesting considering the cast is made inside an `if (content instanceof StreamCharBuffer)` condition.
Doing a proper cast seems to work better:
```groovy
gspGrailsLayoutPage.pageBuffer = (StreamCharBuffer) content
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "codeconsole (via GitHub)" <gi...@apache.org>.
codeconsole commented on PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#issuecomment-3028767893
If I remember correctly, going back to 2.x has major negative consequences. It basically compiles Sitmesh 2.x code into everything so any future upgrades will result in breaking every plugin.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "codeconsole (via GitHub)" <gi...@apache.org>.
codeconsole commented on PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#issuecomment-3028950776
Well, you can start by looking at your changes to the ResponseRenderer trait and look at all the locations that gets compiled into.
I don't see the need to push this back into the dark ages. Don't most of these issues have workarounds? Why not just use decorator chaining if you want to apply multiple layouts?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#issuecomment-3035380819
I'm going to open another PR and split this one between just the sitemesh2 change and all of the clean up / test pollution fixes / and test case restores
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "codeconsole (via GitHub)" <gi...@apache.org>.
codeconsole commented on PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#issuecomment-3028670368
Why not just submit a PR to Sitemesh 3 if any fixes are needed? Sitemesh 2.6.x was just a quick patch for a codebase abandoned in 2009. If you were to revert to Sitemesh 2, the proper course of action would be Sitemesh 2.7.x
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on code in PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#discussion_r2188286356
##########
grails-gsp/grails-web-gsp/build.gradle:
##########
@@ -39,6 +39,7 @@ dependencies {
api project(':grails-gsp-core')
api project(':grails-web-common')
api project(':grails-web-taglib')
+ compileOnly project(":grails-web-layout")
Review Comment:
I believe this was originally meant to prevent a circular dependency but with the other decoupling I did it seems to not be needed now. Will switch to implementation.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#issuecomment-3042701345
Had to rebase this PR to pickup the other upgrade doc changes. No further changes were made.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on code in PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#discussion_r2188246564
##########
grails-gsp/grails-layout/build.gradle:
##########
@@ -0,0 +1,58 @@
+/*
+ * 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
+ *
+ * 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.
+ */
+plugins {
+ id 'groovy'
+ id 'java-library'
+ id 'project-report'
+ id 'org.apache.grails.gradle.grails-plugin'
+}
+
+version = projectVersion
+group = 'org.apache.grails.views'
+
+ext {
+ pomTitle = 'Layout Grails Plugin'
+ pomDescription = 'Provides layout & decoration features for the Grails Framework.'
+}
+
+dependencies {
+ astImplementation platform(project(':grails-bom'))
+ //astImplementation project(':grails-web-core') // TODO: What ast transforms are needed on the tag lib?
+
+ implementation platform(project(':grails-bom'))
+ implementation project(':grails-common') // for groovy
Review Comment:
grails-common is what exports groovy ,so that's why I included it. Would you rather me include groovy directly?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on code in PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#discussion_r2188383065
##########
grails-gsp/plugin/build.gradle:
##########
@@ -84,7 +84,8 @@ dependencies {
exclude group: 'org.apache.grails', module: 'grails-encoder'
exclude group: 'org.springframework', module: 'spring-core'
}
- implementation project(':grails-web-gsp-taglib'), { // RenderTagLib
+
+ api project(':grails-web-gsp-taglib'), { // RenderTagLib
Review Comment:
Updated, tests pass locally so we should be good with this PR & the other.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on code in PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#discussion_r2183932507
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
Review Comment:
This is what the code did before when i restored it. There are 2 forms of the method, one that takes a string and one that takes an object. I'm guessing it wants the object one so I'm inclined to leave this for now.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#issuecomment-3035038452
Thanks @matrei , that cast seems to work.
I'm going to restructure the commits in this PR so that it's easier to revert sitemesh 2. This way we can keep branches merged cleanly.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on code in PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#discussion_r2188307154
##########
grails-gsp/plugin/build.gradle:
##########
@@ -84,7 +84,8 @@ dependencies {
exclude group: 'org.apache.grails', module: 'grails-encoder'
exclude group: 'org.springframework', module: 'spring-core'
}
- implementation project(':grails-web-gsp-taglib'), { // RenderTagLib
+
+ api project(':grails-web-gsp-taglib'), { // RenderTagLib
Review Comment:
Without this being API, this fails to compile. This is one example of it.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty merged PR #14875:
URL: https://github.com/apache/grails-core/pull/14875
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "matrei (via GitHub)" <gi...@apache.org>.
matrei commented on code in PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#discussion_r2188367324
##########
grails-gsp/plugin/build.gradle:
##########
@@ -84,7 +84,8 @@ dependencies {
exclude group: 'org.apache.grails', module: 'grails-encoder'
exclude group: 'org.springframework', module: 'spring-core'
}
- implementation project(':grails-web-gsp-taglib'), { // RenderTagLib
+
+ api project(':grails-web-gsp-taglib'), { // RenderTagLib
Review Comment:
As `grails-web-jsp` is a direct dependency of `grails-testing-support-web`, I think we should add this to `grails-testing-support-web` instead:
```groovy
implementation project(':grails-web-jsp'), {
// impl: TagLibraryResolverImpl
}
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "sbglasius (via GitHub)" <gi...@apache.org>.
sbglasius commented on code in PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#discussion_r2189148093
##########
grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageParser.java:
##########
@@ -163,12 +165,14 @@ public class GroovyPageParser implements Tokens {
private boolean precompileMode;
private Boolean compileStaticMode;
private boolean modelFieldsMode;
+ private boolean grailsLayoutPreprocessMode = false;
Review Comment:
`= false` is not really needed.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on code in PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#discussion_r2183866084
##########
gradle/test-config.gradle:
##########
@@ -73,3 +73,31 @@ tasks.withType(Test).configureEach {
}
}
+if(hasProperty('testBisect')) {
+ tasks.withType(Test).configureEach {
+ maxParallelForks = 1
+ forkEvery = 0
+
+ develocity {
+ testDistribution {
+ enabled.set(false)
+ retryInSameJvm.set(true)
+ }
Review Comment:
I believe in my testing, this is what caused it to run a single test runner so yes I think it's required. I'm not sure if it's a side effect of this change or if there's a better way.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "matrei (via GitHub)" <gi...@apache.org>.
matrei commented on code in PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#discussion_r2183115856
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
+ gspGrailsLayoutPage.setPageBuffer(stringWriter.buffer)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ }
+ }
+ finally {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage)
+ }
+ }
+ if (content == null) {
+ content = ''
+ }
+
+ Page page = null
+ if (!((TypeConvertingMap) attrs).boolean('parse') && gspGrailsLayoutPage != null && gspGrailsLayoutPage.isUsed()) {
+ page = gspGrailsLayoutPage
+ } else {
+ def parser = createPageParser(contentType)
+ char[] charArray
+ if (content instanceof StreamCharBuffer) {
+ charArray = ((StreamCharBuffer) content).toCharArray()
+ } else {
+ charArray = content.toString().toCharArray()
+ }
+ page = parser.parse(charArray)
+ }
+
+ def decorator = findDecorator(request, attrs.name as String)
+ if (decorator && decorator.page) {
+ pageParams.each { k, v ->
+ page.addProperty(k as String, v as String)
+ }
+ try {
+ request.setAttribute(PAGE, page)
+ Template template = findTemplate(decorator)
+ template.make(viewModel).writeTo(out)
+ }
+ finally {
+ request.setAttribute(PAGE, oldPage)
+ }
+ } else {
+ out << content
+ }
+ }
+
+ protected Template findTemplate(Decorator decorator) {
+ Template template
+ if (decorator instanceof SpringMVCViewDecorator) {
+ template = ((SpringMVCViewDecorator) decorator).getTemplate()
+ if (template instanceof GroovyPageTemplate) {
+ GroovyPageTemplate gpt = (GroovyPageTemplate) template
+ gpt = (GroovyPageTemplate) gpt.clone()
+ gpt.setAllowSettingContentType(false)
+ template = gpt
+ }
+ } else {
+ template = groovyPagesTemplateEngine.createTemplate(decorator.getPage())
+ }
+ return template
+ }
+
+ protected Decorator findDecorator(HttpServletRequest req, String layoutName) {
+ DecoratorMapper decoratorMapper = sitemeshFactory?.getDecoratorMapper()
+ Decorator d
+ if (decoratorMapper) {
+ d = decoratorMapper.getNamedDecorator(req, layoutName)
+ } else {
+ d = groovyPageLayoutFinder.getNamedDecorator(req, layoutName)
+ }
+ d
+ }
+
+ protected PageParser createPageParser(String contentType) {
+ PageParser parser = sitemeshFactory?.getPageParser(contentType)
+ if (parser == null) {
+ parser = new GrailsHTMLPageParser()
+ }
+ return parser
+ }
+
+ protected Factory getSitemeshFactory() {
+ return FactoryHolder.getGrailsLayoutFactory()
+ }
+
+ /**
+ * Used to retrieve a property of the decorated page.<br/>
+ *
+ * <g:pageProperty default="defaultValue" name="body.onload" /><br/>
+ *
+ * @emptyTag
+ *
+ * @attr REQUIRED name the property name
+ * @attr default the default value to use if the property is null
+ * @attr writeEntireProperty if true, writes the property in the form 'foo = "bar"', otherwise renders 'bar'
+ */
+ Closure pageProperty = { Map attrs ->
+ if (!attrs.name) {
+ throwTagError("Tag [pageProperty] is missing required attribute [name]")
+ }
+
+ String propertyName = attrs.name as String
+ def htmlPage = getPage()
+ def propertyValue
Review Comment:
Assign `null` to `propertyValue`?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on code in PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#discussion_r2188307001
##########
grails-gsp/plugin/build.gradle:
##########
@@ -84,7 +84,8 @@ dependencies {
exclude group: 'org.apache.grails', module: 'grails-encoder'
exclude group: 'org.springframework', module: 'spring-core'
}
- implementation project(':grails-web-gsp-taglib'), { // RenderTagLib
+
+ api project(':grails-web-gsp-taglib'), { // RenderTagLib
Review Comment:
> Task :grails-testing-support-web:compileGroovy FAILED
startup failed:
/Users/jdaugherty/sandbox/external/grails-projects/grails-core/grails-testing-support-web/src/main/groovy/org/grails/testing/spock/WebSetupSpecInterceptor.groovy: 35: unable to resolve class org.grails.gsp.jsp.TagLibraryResolverImpl
@ line 35, column 1.
import org.grails.gsp.jsp.TagLibraryResolverImpl
^
1 error
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "codeconsole (via GitHub)" <gi...@apache.org>.
codeconsole commented on PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#issuecomment-3034308376
I think a more rational decision would be to fork 8 before this is merged and keep 8 moving forward.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#issuecomment-3028725387
@codeconsole It's a timing problem. We're running out of time to get these issues fixed so going to 2.6.0 is a stop gap since it makes us compatible with 6. I think it's reasonable to consider 2.7.0 for 7.1. Especially, if we have help or volunteers to do the upgrade. The only reason we're trying to move forward here is because of the blocker it's had on the 7 release.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#issuecomment-3037063632
@codeconsole @matrei I think I've decoupled anything that would break a plugin. Let me know if you find anything else in the sitemesh2 revert.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on code in PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#discussion_r2188258549
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/SpringMVCViewDecorator.java:
##########
@@ -0,0 +1,114 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import groovy.text.Template;
+
+import java.util.Collections;
+import java.util.Map;
+
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.grails.web.util.GrailsApplicationAttributes;
+import org.grails.web.servlet.view.AbstractGrailsView;
+import org.grails.web.util.WebUtils;
+import org.springframework.web.servlet.View;
+import org.springframework.web.servlet.view.AbstractUrlBasedView;
+
+import com.opensymphony.module.sitemesh.HTMLPage;
+import com.opensymphony.module.sitemesh.RequestConstants;
+import com.opensymphony.module.sitemesh.mapper.DefaultDecorator;
+import com.opensymphony.sitemesh.Content;
+import com.opensymphony.sitemesh.SiteMeshContext;
+import com.opensymphony.sitemesh.webapp.SiteMeshWebAppContext;
+
+/**
+ * Encapsulates the logic for rendering a layout.
+ *
+ * @author Graeme Rocher
+ * @since 2.0
+ */
+public class SpringMVCViewDecorator extends DefaultDecorator implements com.opensymphony.sitemesh.Decorator {
+ private View view;
Review Comment:
FYI: I increased the class header blank lines to 1 in my intellij code style. Just documenting that this isn't the default with intellij's code formatting. We really need to setup spotless to just prevent this going forward.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on code in PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#discussion_r2188250199
##########
grails-doc/src/en/guide/upgrading/upgrading60x.adoc:
##########
@@ -294,6 +294,27 @@ The previous method using WebDriver binaries remains supported for backward comp
The asset pipeline has a new https://github.com/wondrify/asset-pipeline[home]. Version 5.0.12 and forward is compatible with Grails 7.x.
+====== 12.12 Layout Configuration
+
+There are several changes related to layout related configuration:
+
+Package Changes:
+* grails.web.sitemesh -> org.apache.grails.web.layout
+* org.grails.web.sitemesh -> org.apache.grails.views.gsp.layout
+
+Notable Class Moves:
+* org.grails.web.servlet.view.GrailsLayoutViewResolver -> org.grails.web.servlet.view.EmbeddedGrailsLayoutViewResolver
+* org.grails.web.servlet.view.SitemeshLayoutViewResolver -> org.grails.web.servlet.view.GrailsLayoutViewResolver
+* org.grails.web.sitemesh.GrailsLayoutView -> org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+* org.grails.web.sitemesh.SitemeshLayoutView -> org.apache.grails.web.layout.GrailsLayoutView
+
+Property Changes:
+* grails.sitemesh.default.layout -> grails.views.gsp.layout.default
+* grails.sitemesh.enable.nongsp -> grails.views.gsp.layout.enable.nongsp
Review Comment:
I'll move them to just views.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "jdaugherty (via GitHub)" <gi...@apache.org>.
jdaugherty commented on code in PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#discussion_r2188266902
##########
grails-gsp/plugin/src/test/groovy/org/apache/grails/views/gsp/layout/FullGrailsLayoutLifeCycleTests.groovy:
##########
Review Comment:
You should vote for https://youtrack.jetbrains.com/issue/IDEA-260579/Groovy-formatting-Add-missing-method-call-parentheses =)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
Re: [PR] Revert to Sitemesh 2 for Grails 7 [grails-core]
Posted by "matrei (via GitHub)" <gi...@apache.org>.
matrei commented on code in PR #14875:
URL: https://github.com/apache/grails-core/pull/14875#discussion_r2188098303
##########
grails-gsp/grails-web-layout/build.gradle:
##########
@@ -0,0 +1,59 @@
+/*
+ * 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
+ *
+ * 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.
+ */
+plugins {
+ id 'groovy'
+ id 'java-library'
+ id 'project-report'
+ id 'org.apache.grails.gradle.grails-plugin'
+}
+
+version = projectVersion
+group = 'org.apache.grails.views'
+
+ext {
+ pomTitle = 'Layout Grails Plugin'
+ pomDescription = 'Provides layout & decoration features for the Grails Framework.'
+}
+
+dependencies {
+ implementation platform(project(':grails-bom'))
+ implementation project(':grails-common') // for groovy
Review Comment:
How is `grails-common` used in this `grails-web-layout`?
##########
grails-gsp/grails-layout/build.gradle:
##########
@@ -0,0 +1,58 @@
+/*
+ * 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
+ *
+ * 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.
+ */
+plugins {
+ id 'groovy'
+ id 'java-library'
+ id 'project-report'
+ id 'org.apache.grails.gradle.grails-plugin'
+}
+
+version = projectVersion
+group = 'org.apache.grails.views'
+
+ext {
+ pomTitle = 'Layout Grails Plugin'
+ pomDescription = 'Provides layout & decoration features for the Grails Framework.'
+}
+
+dependencies {
+ astImplementation platform(project(':grails-bom'))
+ //astImplementation project(':grails-web-core') // TODO: What ast transforms are needed on the tag lib?
+
+ implementation platform(project(':grails-bom'))
+ implementation project(':grails-common') // for groovy
Review Comment:
How is `grails-common` used in `grails-layout`?
##########
grails-gsp/grails-web-gsp/src/main/groovy/org/grails/web/servlet/view/EmbeddedGrailsLayoutViewResolver.java:
##########
@@ -0,0 +1,112 @@
+/*
+ * 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
+ *
+ * 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.servlet.view;
+
+import java.util.Locale;
+
+import jakarta.servlet.ServletContext;
+
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView;
+import org.apache.grails.web.layout.GroovyPageLayoutFinder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.Ordered;
+import org.springframework.web.context.ServletContextAware;
+import org.springframework.web.servlet.SmartView;
+import org.springframework.web.servlet.View;
+import org.springframework.web.servlet.ViewResolver;
+
+public class EmbeddedGrailsLayoutViewResolver implements LayoutViewResolver, Ordered, ServletContextAware, ApplicationContextAware {
+ private static final Logger LOG = LoggerFactory.getLogger(EmbeddedGrailsLayoutViewResolver.class);
+
+ protected ViewResolver innerViewResolver;
+ protected GroovyPageLayoutFinder groovyPageLayoutFinder;
+ private int order = Ordered.LOWEST_PRECEDENCE - 30;
+ protected ServletContext servletContext;
+
+ public EmbeddedGrailsLayoutViewResolver(ViewResolver innerViewResolver, GroovyPageLayoutFinder groovyPageLayoutFinder) {
+ this.innerViewResolver = innerViewResolver;
+ this.groovyPageLayoutFinder = groovyPageLayoutFinder;
+ }
+
+ public EmbeddedGrailsLayoutViewResolver() {
+
Review Comment:
Spacing?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/GrailsLayoutGrailsPlugin.groovy:
##########
@@ -0,0 +1,42 @@
+/*
+ * 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
+ *
+ * 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.plugins.web
+
+import org.grails.plugins.web.taglib.RenderGrailsLayoutTagLib
+import org.grails.plugins.web.taglib.GrailsLayoutTagLib
+
+import grails.plugins.Plugin
+import grails.util.GrailsUtil
+import groovy.util.logging.Slf4j
+
+/**
+ * Plugin responsible for Grails Layout specific configuration.
+ */
+class GrailsLayoutGrailsPlugin extends Plugin {
+ def grailsVersion = "7.0.0-SNAPSHOT > *"
Review Comment:
Align on single quotes for strings?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/EmbeddedGrailsLayoutView.java:
##########
@@ -0,0 +1,173 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import groovy.text.Template;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.grails.web.servlet.WrappedResponseHolder;
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.web.servlet.mvc.OutputAwareHttpServletResponse;
+import org.grails.web.servlet.view.AbstractGrailsView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.web.servlet.View;
+
+import com.opensymphony.module.sitemesh.RequestConstants;
+import com.opensymphony.sitemesh.Content;
+
+public class EmbeddedGrailsLayoutView extends AbstractGrailsView {
+ private static final Logger LOG = LoggerFactory.getLogger(EmbeddedGrailsLayoutView.class);
+ GroovyPageLayoutFinder groovyPageLayoutFinder;
+
+ protected View innerView;
+
+ public static final String GSP_GRAILS_LAYOUT_PAGE = EmbeddedGrailsLayoutView.class.getName() + ".GSP_GRAILS_LAYOUT_PAGE";
+
+ public EmbeddedGrailsLayoutView(GroovyPageLayoutFinder groovyPageLayoutFinder, View innerView) {
+ this.groovyPageLayoutFinder = groovyPageLayoutFinder;
+ this.innerView = innerView;
+ }
+
+ @Override
+ public String getContentType() {
+ return MediaType.ALL_VALUE;
+ }
+
+ @Override
+ protected void renderTemplate(Map<String, Object> model, GrailsWebRequest webRequest, HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+
+ boolean isCommitted = response.isCommitted() && (response instanceof OutputAwareHttpServletResponse) && !((OutputAwareHttpServletResponse) response).isWriterAvailable();
+ if( !isCommitted ) {
Review Comment:
- Align on space after `if`?
- Spacing?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/EmbeddedGrailsLayoutView.java:
##########
@@ -0,0 +1,173 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import groovy.text.Template;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.grails.web.servlet.WrappedResponseHolder;
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.web.servlet.mvc.OutputAwareHttpServletResponse;
+import org.grails.web.servlet.view.AbstractGrailsView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.web.servlet.View;
+
+import com.opensymphony.module.sitemesh.RequestConstants;
+import com.opensymphony.sitemesh.Content;
+
+public class EmbeddedGrailsLayoutView extends AbstractGrailsView {
+ private static final Logger LOG = LoggerFactory.getLogger(EmbeddedGrailsLayoutView.class);
+ GroovyPageLayoutFinder groovyPageLayoutFinder;
+
+ protected View innerView;
+
+ public static final String GSP_GRAILS_LAYOUT_PAGE = EmbeddedGrailsLayoutView.class.getName() + ".GSP_GRAILS_LAYOUT_PAGE";
+
+ public EmbeddedGrailsLayoutView(GroovyPageLayoutFinder groovyPageLayoutFinder, View innerView) {
+ this.groovyPageLayoutFinder = groovyPageLayoutFinder;
+ this.innerView = innerView;
+ }
+
+ @Override
+ public String getContentType() {
+ return MediaType.ALL_VALUE;
+ }
+
+ @Override
+ protected void renderTemplate(Map<String, Object> model, GrailsWebRequest webRequest, HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+
+ boolean isCommitted = response.isCommitted() && (response instanceof OutputAwareHttpServletResponse) && !((OutputAwareHttpServletResponse) response).isWriterAvailable();
+ if( !isCommitted ) {
+
+ Content content = obtainContent(model, webRequest, request, response);
+ if (content != null) {
+
+ beforeDecorating(content, model, webRequest, request, response);
+ switch (request.getDispatcherType()) {
+ case INCLUDE:
+ break;
+ case ASYNC:
+ case ERROR:
+ case FORWARD:
+ case REQUEST:
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Finding layout for request and content" );
+ }
+ SpringMVCViewDecorator decorator = (SpringMVCViewDecorator) groovyPageLayoutFinder.findLayout(request, content);
+ if (decorator != null) {
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Found layout. Rendering content for layout {} and model {}", decorator.getPage(), model);
+ }
+
+ decorator.render(content, model, request, response, webRequest.getServletContext());
+ return;
+ }
+ break;
+ }
+ PrintWriter writer = response.getWriter();
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Layout not applicable to response, writing original content");
+ }
+ content.writeOriginal(writer);
+ if (!response.isCommitted()) {
+ writer.flush();
+ }
+ }
+ }
+
+
+ }
+
+ protected void beforeDecorating(Content content, Map<String, Object> model, GrailsWebRequest webRequest,
+ HttpServletRequest request, HttpServletResponse response) {
+ applyMetaHttpEquivContentType(content, response);
+ }
+
+ protected void applyMetaHttpEquivContentType(Content content, HttpServletResponse response) {
+ String contentType = content.getProperty("meta.http-equiv.Content-Type");
+ if (contentType != null && "text/html".equals(response.getContentType())) {
+ response.setContentType(contentType);
+ }
+ }
+
+ protected Content obtainContent(Map<String, Object> model, GrailsWebRequest webRequest, HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+ Object oldPage = request.getAttribute(RequestConstants.PAGE);
+ request.removeAttribute(RequestConstants.PAGE);
+ Object oldGspGrailsLayoutPage=request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE);
+ HttpServletResponse previousResponse = webRequest.getWrappedResponse();
+ HttpServletResponse previousWrappedResponse = WrappedResponseHolder.getWrappedResponse();
+ try {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, new GSPGrailsLayoutPage());
+
+ GrailsContentBufferingResponse contentBufferingResponse = createContentBufferingResponse(model, webRequest, request, response);
+ webRequest.setWrappedResponse(contentBufferingResponse);
+ WrappedResponseHolder.setWrappedResponse(contentBufferingResponse);
+
+ renderInnerView(model, webRequest, request, response, contentBufferingResponse);
+
+ return contentBufferingResponse.getContent();
+ }
+ finally {
+ if (oldGspGrailsLayoutPage != null) {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage);
+ }
+ if (oldPage != null) {
+ request.setAttribute(RequestConstants.PAGE, oldPage);
+ }
+ webRequest.setWrappedResponse(previousResponse);
+ WrappedResponseHolder.setWrappedResponse(previousWrappedResponse);
+ }
+ }
+
+ protected void renderInnerView(Map<String, Object> model, GrailsWebRequest webRequest, HttpServletRequest request,
+ HttpServletResponse response,
+ GrailsContentBufferingResponse contentBufferingResponse) throws Exception {
+ if(LOG.isDebugEnabled()) {
Review Comment:
Align on space after `if`?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GSPGrailsLayoutPage.java:
##########
@@ -0,0 +1,239 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import com.opensymphony.module.sitemesh.HTMLPage;
+import com.opensymphony.module.sitemesh.parser.AbstractHTMLPage;
+import com.opensymphony.sitemesh.Content;
+import com.opensymphony.sitemesh.compatability.Content2HTMLPage;
+import org.grails.buffer.StreamCharBuffer;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Grails/GSP specific implementation of Sitemesh's AbstractHTMLPage
+ *
+ * g:capture* tags in RenderTagLib are used to capture head, meta, title, component and body contents.
+ * No html parsing is required for templating since capture tags are added at GSP compilation time.
+ *
+ * @author Lari Hotari, Sagire Software Oy
+ */
+public class GSPGrailsLayoutPage extends AbstractHTMLPage implements Content{
+ StreamCharBuffer headBuffer;
+ StreamCharBuffer bodyBuffer;
+ StreamCharBuffer pageBuffer;
+ StreamCharBuffer titleBuffer;
+ boolean used;
+ boolean titleCaptured;
+ Map<String, StreamCharBuffer> contentBuffers;
+ private boolean renderingLayout;
+
+ public GSPGrailsLayoutPage() {
+ this(false);
+ }
+
+ public GSPGrailsLayoutPage(boolean renderingLayout) {
+ reset();
+ this.renderingLayout=renderingLayout;
+ }
+
+ public void reset() {
+ headBuffer=null;
+ bodyBuffer=null;
+ pageBuffer=null;
+ titleBuffer=null;
+ used = false;
+ titleCaptured = false;
+ contentBuffers = null;
+ renderingLayout = false;
+ }
+
+ public void addProperty(String name, Object value) {
+ addProperty(name, (value == null ? null : String.valueOf(value)));
+ }
+
+ @Override
+ public void addProperty(String name, String value) {
+ super.addProperty(name, value);
+ used = true;
+ }
+
+ @Override
+ public void writeHead(Writer out) throws IOException {
+ if (headBuffer == null) {
+ return;
+ }
+
+ if (titleCaptured) {
+ if (titleBuffer != null) {
+ int headlen = headBuffer.length();
+ titleBuffer.clear();
+ if (headBuffer.length() < headlen) {
+ headBuffer.writeTo(out);
+ return;
+ }
+ }
+ String headAsString = headBuffer.toString();
+ // strip out title for Grails Layout version of <head>
+ out.write(headAsString.replaceFirst("(?is)<title(\\s[^>]*)?>(.*?)</title>",""));
Review Comment:
Spacing?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GrailsLayoutRenderViewMutator.groovy:
##########
@@ -0,0 +1,50 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout
+
+import grails.web.pages.GrailsRenderViewMutator
+import org.springframework.beans.factory.NoSuchBeanDefinitionException
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.web.servlet.View
+
+class GrailsLayoutRenderViewMutator implements GrailsRenderViewMutator {
+ @Autowired
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+
+ @Override
+ View mutateView(boolean renderWithLayout, String templateUri, Locale locale, View existingView) {
+ View toReturn = existingView
+
+ // if automatic decoration occurred unwrap, since this is a partial
+ if(toReturn instanceof EmbeddedGrailsLayoutView) {
+ toReturn = ((EmbeddedGrailsLayoutView)toReturn).getInnerView()
+ }
+
+ if(renderWithLayout) {
Review Comment:
Align on space after `if`?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GrailsPageResponseWrapper.java:
##########
@@ -0,0 +1,390 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
+
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.buffer.GrailsPrintWriterAdapter;
+import org.grails.buffer.StreamByteBuffer;
+import org.grails.buffer.StreamCharBuffer;
+import org.grails.web.util.WebUtils;
+
+import com.opensymphony.module.sitemesh.Page;
+import com.opensymphony.module.sitemesh.PageParser;
+import com.opensymphony.module.sitemesh.PageParserSelector;
+import com.opensymphony.module.sitemesh.filter.HttpContentType;
+import com.opensymphony.module.sitemesh.filter.RoutableServletOutputStream;
+import com.opensymphony.module.sitemesh.filter.TextEncoder;
+
+/**
+ * @author Graeme Rocher
+ * @since 1.0.4
+ */
+public class GrailsPageResponseWrapper extends HttpServletResponseWrapper {
+
+ private final GrailsRoutablePrintWriter routablePrintWriter;
+ private final RoutableServletOutputStream routableServletOutputStream;
+ private final PageParserSelector parserSelector;
+ private final HttpServletRequest request;
+
+ private GrailsBuffer buffer;
+ private boolean parseablePage = false;
+ private GSPGrailsLayoutPage gspGrailsLayoutPage;
+
+ public GrailsPageResponseWrapper(final HttpServletRequest request, final HttpServletResponse response,
+ PageParserSelector parserSelector) {
+ super(response);
+ this.parserSelector = parserSelector;
+
+ routablePrintWriter = GrailsRoutablePrintWriter.newInstance(new GrailsRoutablePrintWriter.DestinationFactory() {
+ public PrintWriter activateDestination() throws IOException {
+ return response.getWriter();
+ }
+ });
+ routableServletOutputStream = new RoutableServletOutputStream(new RoutableServletOutputStream.DestinationFactory() {
+ public ServletOutputStream create() throws IOException {
+ return response.getOutputStream();
+ }
+ });
+
+ this.request = request;
+
+ gspGrailsLayoutPage = (GSPGrailsLayoutPage) request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE);
+
+ applyContentType(response.getContentType());
+ }
+
+ @Override
+ public void sendError(int sc) throws IOException {
+ clearBuffer();
+ GrailsWebRequest webRequest = GrailsWebRequest.lookup();
+ try {
+ super.sendError(sc);
+ } finally {
+ if (webRequest != null)
+ WebUtils.storeGrailsWebRequest(webRequest);
+ }
+ }
+
+ @Override
+ public void sendError(int sc, String msg) throws IOException {
+ clearBuffer();
+ GrailsWebRequest webRequest = GrailsWebRequest.lookup();
+ try {
+ super.sendError(sc, msg);
+ } finally {
+ if (webRequest != null)
+ WebUtils.storeGrailsWebRequest(webRequest);
+ }
+ }
+
+ /**
+ * Set the content-type of the request and store it so it can
+ * be passed to the {@link com.opensymphony.module.sitemesh.PageParser}.
+ */
+ @Override
+ public void setContentType(String type) {
+ super.setContentType(type);
+
+ applyContentType(type);
+ }
+
+ protected void applyContentType(String type) {
+ if (type == null) {
+ return;
+ }
+
+ HttpContentType httpContentType = new HttpContentType(type);
+
+ if (parserSelector.shouldParsePage(httpContentType.getType())) {
+ activateGrailsLayout(httpContentType.getType(), httpContentType.getEncoding());
+ } else {
+ deactivateGrailsLayout();
+ }
+ }
+
+ public void activateGrailsLayout(String contentType, String encoding) {
+ if (parseablePage) {
+ return; // already activated
+ }
+
+ buffer = new GrailsBuffer(parserSelector, contentType, encoding, gspGrailsLayoutPage);
+ routablePrintWriter.updateDestination(new GrailsRoutablePrintWriter.DestinationFactory() {
+ public PrintWriter activateDestination() {
+ return buffer.getWriter();
+ }
+ });
+ routablePrintWriter.blockFlushAndClose();
+ routableServletOutputStream.updateDestination(new RoutableServletOutputStream.DestinationFactory() {
+ public ServletOutputStream create() {
+ return buffer.getOutputStream();
+ }
+ });
+ parseablePage = true;
+ }
+
+ public void deactivateGrailsLayout() {
+ parseablePage = false;
+ buffer = null;
+ if (gspGrailsLayoutPage != null) {
+ gspGrailsLayoutPage.reset();
+ }
+ routablePrintWriter.updateDestination(new GrailsRoutablePrintWriter.DestinationFactory() {
+ public PrintWriter activateDestination() throws IOException {
+ return getResponse().getWriter();
+ }
+ });
+ routablePrintWriter.unBlockFlushAndClose();
+ routableServletOutputStream.updateDestination(new RoutableServletOutputStream.DestinationFactory() {
+ public ServletOutputStream create() throws IOException {
+ return getResponse().getOutputStream();
+ }
+ });
+ }
+
+ /**
+ * Prevent content-length being set if page is parseable.
+ */
+ @Override
+ public void setContentLength(int contentLength) {
+ if (!parseablePage) super.setContentLength(contentLength);
+ }
+
+ /**
+ * Prevent buffer from being flushed if this is a page being parsed.
+ */
+ @Override
+ public void flushBuffer() throws IOException {
+ if (!parseablePage) super.flushBuffer();
+ }
+
+ /**
+ * Prevent content-length being set if page is parseable.
+ */
+ @Override
+ public void setHeader(String name, String value) {
+ if (name.toLowerCase().equals("content-type")) { // ensure ContentType is always set through setContentType()
+ setContentType(value);
+ } else if (!parseablePage || !name.toLowerCase().equals("content-length")) {
+ super.setHeader(name, value);
+ }
+ }
+
+ /**
+ * Prevent content-length being set if page is parseable.
+ */
+ @Override
+ public void addHeader(String name, String value) {
+ if (name.toLowerCase().equals("content-type")) { // ensure ContentType is always set through setContentType()
Review Comment:
Use `equalsIgnoreCase`?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GroovyPageLayoutFinder.java:
##########
@@ -0,0 +1,315 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import grails.util.Environment;
+import grails.util.GrailsNameUtils;
+import grails.web.pages.GroovyPagesUriService;
+import groovy.lang.GroovyObject;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+import grails.util.GrailsClassUtils;
+import grails.util.GrailsStringUtils;
+import org.grails.core.artefact.ControllerArtefactHandler;
+import org.grails.io.support.GrailsResourceUtils;
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.web.util.GrailsApplicationAttributes;
+import org.grails.web.servlet.view.AbstractGrailsView;
+import org.grails.web.servlet.view.GrailsViewResolver;
+import org.grails.web.servlet.view.LayoutViewResolver;
+import org.grails.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.core.Ordered;
+import org.springframework.web.servlet.View;
+import org.springframework.web.servlet.ViewResolver;
+
+import com.opensymphony.module.sitemesh.Decorator;
+import com.opensymphony.module.sitemesh.Page;
+import com.opensymphony.sitemesh.Content;
+
+/**
+ * Provides the logic for GrailsLayoutDecoratorMapper without so many ties to
+ * the Sitemesh API.
+ *
+ * @author Graeme Rocher
+ * @since 2.0
+ */
+public class GroovyPageLayoutFinder implements ApplicationListener<ContextRefreshedEvent>, Ordered {
+ private static final Logger LOG = LoggerFactory.getLogger(GrailsLayoutDecoratorMapper.class);
Review Comment:
Align on blank line after class definition?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GroovyPageLayoutFinder.java:
##########
@@ -0,0 +1,315 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import grails.util.Environment;
+import grails.util.GrailsNameUtils;
+import grails.web.pages.GroovyPagesUriService;
+import groovy.lang.GroovyObject;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+import grails.util.GrailsClassUtils;
+import grails.util.GrailsStringUtils;
+import org.grails.core.artefact.ControllerArtefactHandler;
+import org.grails.io.support.GrailsResourceUtils;
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.web.util.GrailsApplicationAttributes;
+import org.grails.web.servlet.view.AbstractGrailsView;
+import org.grails.web.servlet.view.GrailsViewResolver;
+import org.grails.web.servlet.view.LayoutViewResolver;
+import org.grails.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.core.Ordered;
+import org.springframework.web.servlet.View;
+import org.springframework.web.servlet.ViewResolver;
+
+import com.opensymphony.module.sitemesh.Decorator;
+import com.opensymphony.module.sitemesh.Page;
+import com.opensymphony.sitemesh.Content;
+
+/**
+ * Provides the logic for GrailsLayoutDecoratorMapper without so many ties to
+ * the Sitemesh API.
+ *
+ * @author Graeme Rocher
+ * @since 2.0
+ */
+public class GroovyPageLayoutFinder implements ApplicationListener<ContextRefreshedEvent>, Ordered {
+ private static final Logger LOG = LoggerFactory.getLogger(GrailsLayoutDecoratorMapper.class);
+ private static final long LAYOUT_CACHE_EXPIRATION_MILLIS = Long.getLong("grails.gsp.reload.interval", 5000);
+ private static final String LAYOUTS_PATH = "/layouts";
+
+ private static final int ORDER = Ordered.LOWEST_PRECEDENCE - 1;
+
+ private Map<String, DecoratorCacheValue> decoratorCache = new ConcurrentHashMap<String, DecoratorCacheValue>();
+ private Map<LayoutCacheKey, DecoratorCacheValue> layoutDecoratorCache = new ConcurrentHashMap<LayoutCacheKey, DecoratorCacheValue>();
+
+ private String defaultDecoratorName;
+ private boolean gspReloadEnabled;
+ private boolean cacheEnabled = (Environment.getCurrent() != Environment.DEVELOPMENT);
+ private ViewResolver viewResolver;
+ private boolean enableNonGspViews = false;
+
+ @Override
+ public int getOrder() {
+ return ORDER;
+ }
+
+ public void setDefaultDecoratorName(String defaultDecoratorName) {
+ this.defaultDecoratorName = defaultDecoratorName;
+ }
+
+ public void setEnableNonGspViews(boolean enableNonGspViews) {
+ this.enableNonGspViews = enableNonGspViews;
+ }
+
+ public void setGspReloadEnabled(boolean gspReloadEnabled) {
+ this.gspReloadEnabled = gspReloadEnabled;
+ }
+
+ public void setCacheEnabled(boolean cacheEnabled) {
+ this.cacheEnabled = cacheEnabled;
+ }
+
+ public void setViewResolver(ViewResolver viewResolver) {
+ if(viewResolver instanceof LayoutViewResolver) {
+ this.viewResolver = ((LayoutViewResolver)viewResolver).getInnerViewResolver();
Review Comment:
Align on space after cast?
##########
grails-gsp/grails-layout/build.gradle:
##########
@@ -0,0 +1,58 @@
+/*
+ * 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
+ *
+ * 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.
+ */
+plugins {
+ id 'groovy'
+ id 'java-library'
+ id 'project-report'
+ id 'org.apache.grails.gradle.grails-plugin'
+}
+
+version = projectVersion
+group = 'org.apache.grails.views'
+
+ext {
+ pomTitle = 'Layout Grails Plugin'
+ pomDescription = 'Provides layout & decoration features for the Grails Framework.'
+}
+
+dependencies {
+ astImplementation platform(project(':grails-bom'))
+ //astImplementation project(':grails-web-core') // TODO: What ast transforms are needed on the tag lib?
+
+ implementation platform(project(':grails-bom'))
+ implementation project(':grails-common') // for groovy
+ implementation project(':grails-core') // for plugin
+ implementation project(':grails-gsp-core') // for grails layout preprocessor
+ api project(':grails-web-taglib')
+ runtimeOnly project(':grails-web-jsp')
+
+ api project(":grails-web-layout")
Review Comment:
Align on single quotes for strings?
##########
grails-doc/src/en/guide/upgrading/upgrading60x.adoc:
##########
@@ -294,6 +294,27 @@ The previous method using WebDriver binaries remains supported for backward comp
The asset pipeline has a new https://github.com/wondrify/asset-pipeline[home]. Version 5.0.12 and forward is compatible with Grails 7.x.
+====== 12.12 Layout Configuration
+
+There are several changes related to layout related configuration:
+
+Package Changes:
+* grails.web.sitemesh -> org.apache.grails.web.layout
+* org.grails.web.sitemesh -> org.apache.grails.views.gsp.layout
+
+Notable Class Moves:
+* org.grails.web.servlet.view.GrailsLayoutViewResolver -> org.grails.web.servlet.view.EmbeddedGrailsLayoutViewResolver
+* org.grails.web.servlet.view.SitemeshLayoutViewResolver -> org.grails.web.servlet.view.GrailsLayoutViewResolver
+* org.grails.web.sitemesh.GrailsLayoutView -> org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+* org.grails.web.sitemesh.SitemeshLayoutView -> org.apache.grails.web.layout.GrailsLayoutView
+
+Property Changes:
+* grails.sitemesh.default.layout -> grails.views.gsp.layout.default
+* grails.sitemesh.enable.nongsp -> grails.views.gsp.layout.enable.nongsp
Review Comment:
Should these properties be in the `gsp` group if layouts can be used without GSP?
##########
grails-doc/src/en/guide/theWebLayer/gsp/layouts.adoc:
##########
@@ -144,7 +143,7 @@ With that property in place, the application default layout will be `grails-app/
==== Inline Layouts
-Grails' also supports Sitemesh's concept of inline layouts with the xref:../../../ref/Tags%20-%20GSP/applyLayout.adoc[applyLayout] tag. This can be used to apply a layout to a template, URL or arbitrary section of content. This lets you even further modularize your view structure by "decorating" your template includes.
+Grails' also supports inline layouts with the xref:../../../ref/Tags%20-%20GSP/applyLayout.adoc[applyLayout] tag. This can be used to apply a layout to a template, URL or arbitrary section of content. This lets you even further modularize your view structure by "decorating" your template includes.
Review Comment:
Suggestion:
Grails also supports ...
##########
grails-gsp/grails-web-gsp/build.gradle:
##########
@@ -39,6 +39,7 @@ dependencies {
api project(':grails-gsp-core')
api project(':grails-web-common')
api project(':grails-web-taglib')
+ compileOnly project(":grails-web-layout")
Review Comment:
- How can this be `compileOnly` when classes from `grails-web-layout` is used in the implementation of `grails-web-gsp`?
- `opensymphony:sitemesh` is a direct dependency of this module and should preferably be declared as such?
##########
grails-doc/src/en/guide/conf/config/builtInOptions.adoc:
##########
@@ -32,6 +32,6 @@ On the runtime front, i.e. `grails-app/conf/application.yml`, there are quite a
* `grails.mime.file.extensions` - Whether to use the file extension to dictate the mime type in link:theWebLayer.html#contentNegotiation[Content Negotiation] (default: true).
* `grails.mime.types` - A map of supported mime types used for link:theWebLayer.html#contentNegotiation[Content Negotiation].
* `grails.serverURL` - A string specifying the server URL portion of absolute links, including server name e.g. grails.serverURL="http://my.yourportal.com". See link:{gspdocs}/ref/Tags/createLink.html[createLink]. Also used by redirects.
-* `grails.views.gsp.sitemesh.preprocess` - Determines whether SiteMesh preprocessing happens. Disabling this slows down page rendering, but if you need SiteMesh to parse the generated HTML from a GSP view then disabling it is the right option. Don't worry if you don't understand this advanced property: leave it set to true.
+* `grails.views.gsp.layout.preprocess` - Determines whether layout preprocessing happens. Disabling this slows down page rendering, but if you need Grail's layout implementation to parse the generated HTML from a GSP view then disabling it is the right option. Don't worry if you don't understand this advanced property: leave it set to true.
Review Comment:
Suggestion:
Disabling this slows down page rendering, but if you need Grails' layout implementation to parse the generated HTML from a GSP view, then disabling it is the right option.
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -693,6 +739,33 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertEquals 'my/contenttype', response.getContentType()
}
+ void testApplyLayout() {
+ GrailsLayoutDecoratorMapper decoratorMapper = new GrailsLayoutDecoratorMapper()
+ decoratorMapper.groovyPageLayoutFinder = appCtx.groovyPageLayoutFinder
+ FactoryHolder.setFactory([getDecoratorMapper: { -> decoratorMapper
+ }] as com.opensymphony.module.sitemesh.Factory)
Review Comment:
Suggestion:
```groovy
FactoryHolder.setFactory([
getDecoratorMapper: { -> decoratorMapper }
] as com.opensymphony.module.sitemesh.Factory)
```
##########
grails-gsp/plugin/src/test/groovy/org/apache/grails/views/gsp/layout/FactoryHolderTests.groovy:
##########
Review Comment:
Align on parentheses for method calls?
##########
grails-gsp/grails-web-gsp/src/main/groovy/org/grails/web/servlet/view/EmbeddedGrailsLayoutViewResolver.java:
##########
@@ -0,0 +1,112 @@
+/*
+ * 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
+ *
+ * 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.servlet.view;
+
+import java.util.Locale;
+
+import jakarta.servlet.ServletContext;
+
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView;
+import org.apache.grails.web.layout.GroovyPageLayoutFinder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.core.Ordered;
+import org.springframework.web.context.ServletContextAware;
+import org.springframework.web.servlet.SmartView;
+import org.springframework.web.servlet.View;
+import org.springframework.web.servlet.ViewResolver;
+
+public class EmbeddedGrailsLayoutViewResolver implements LayoutViewResolver, Ordered, ServletContextAware, ApplicationContextAware {
+ private static final Logger LOG = LoggerFactory.getLogger(EmbeddedGrailsLayoutViewResolver.class);
Review Comment:
Align on empty line after class definition for readability?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/AbstractGrailsTagTests.groovy:
##########
Review Comment:
Align on parentheses for method calls?
##########
grails-gsp/grails-web-gsp/src/main/groovy/org/grails/web/servlet/view/GrailsLayoutViewResolver.java:
##########
@@ -0,0 +1,147 @@
+/*
+ * 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
+ *
+ * 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.servlet.view;
+
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Collections;
+
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletContext;
+
+import grails.core.GrailsApplication;
+import grails.core.support.GrailsApplicationAware;
+import org.apache.grails.web.layout.FactoryHolder;
+import org.apache.grails.web.layout.GroovyPageLayoutFinder;
+import org.apache.grails.web.layout.GrailsLayoutView;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.core.Ordered;
+import org.springframework.web.servlet.View;
+import org.springframework.web.servlet.ViewResolver;
+
+import com.opensymphony.module.sitemesh.Config;
+import com.opensymphony.module.sitemesh.Factory;
+import com.opensymphony.module.sitemesh.factory.DefaultFactory;
+import com.opensymphony.sitemesh.ContentProcessor;
+import com.opensymphony.sitemesh.compatability.PageParser2ContentProcessor;
+
+public class GrailsLayoutViewResolver extends EmbeddedGrailsLayoutViewResolver implements GrailsApplicationAware, DisposableBean, Ordered, ApplicationListener<ContextRefreshedEvent>{
+ private static final String FACTORY_SERVLET_CONTEXT_ATTRIBUTE = "grails.layout.factory";
+ private ContentProcessor contentProcessor;
+ protected GrailsApplication grailsApplication;
+ private boolean grailsLayoutConfigLoaded = false;
+ private int order = Ordered.LOWEST_PRECEDENCE - 50;
+
+ public GrailsLayoutViewResolver() {
+ super();
+ }
+
+ public GrailsLayoutViewResolver(ViewResolver innerViewResolver, GroovyPageLayoutFinder groovyPageLayoutFinder) {
+ super(innerViewResolver, groovyPageLayoutFinder);
+ }
+
+ @Override
+ protected View createLayoutView(View innerView) {
+ return new GrailsLayoutView(groovyPageLayoutFinder, innerView, contentProcessor);
+ }
+
+ public void init() {
+ if(servletContext == null) return;
+
+ Factory grailsLayoutFactory = (Factory)servletContext.getAttribute(FACTORY_SERVLET_CONTEXT_ATTRIBUTE);
+ if(grailsLayoutFactory==null) {
+ grailsLayoutFactory = loadGrailsLayoutConfig();
+ }
+ contentProcessor = new PageParser2ContentProcessor(grailsLayoutFactory);
+ }
+
+ protected Factory loadGrailsLayoutConfig() {
+ FilterConfig filterConfig=new FilterConfig() {
+ private Map<String, String> customConfig =
+ Collections.singletonMap("configFile",
+ "classpath:org/apache/grails/web/layout/grails-layout-default.xml");
+
+ @Override
+ public ServletContext getServletContext() {
+ return servletContext;
+ }
+
+ @Override
+ public Enumeration<String> getInitParameterNames() {
+ return Collections.enumeration(customConfig.keySet());
+ }
+
+ @Override
+ public String getInitParameter(String name) {
+ return customConfig.get(name);
+ }
+
+ @Override
+ public String getFilterName() {
+ return null;
+ }
+ };
+ Config config = new Config(filterConfig);
+
+ DefaultFactory grailsLayoutFactory = new DefaultFactory(config);
+ if(servletContext != null) {
Review Comment:
Align on space after `if`?
##########
grails-gsp/grails-web-gsp/src/main/groovy/org/grails/web/servlet/view/GrailsLayoutViewResolver.java:
##########
@@ -0,0 +1,147 @@
+/*
+ * 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
+ *
+ * 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.servlet.view;
+
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Collections;
+
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletContext;
+
+import grails.core.GrailsApplication;
+import grails.core.support.GrailsApplicationAware;
+import org.apache.grails.web.layout.FactoryHolder;
+import org.apache.grails.web.layout.GroovyPageLayoutFinder;
+import org.apache.grails.web.layout.GrailsLayoutView;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.core.Ordered;
+import org.springframework.web.servlet.View;
+import org.springframework.web.servlet.ViewResolver;
+
+import com.opensymphony.module.sitemesh.Config;
+import com.opensymphony.module.sitemesh.Factory;
+import com.opensymphony.module.sitemesh.factory.DefaultFactory;
+import com.opensymphony.sitemesh.ContentProcessor;
+import com.opensymphony.sitemesh.compatability.PageParser2ContentProcessor;
+
+public class GrailsLayoutViewResolver extends EmbeddedGrailsLayoutViewResolver implements GrailsApplicationAware, DisposableBean, Ordered, ApplicationListener<ContextRefreshedEvent>{
+ private static final String FACTORY_SERVLET_CONTEXT_ATTRIBUTE = "grails.layout.factory";
+ private ContentProcessor contentProcessor;
+ protected GrailsApplication grailsApplication;
+ private boolean grailsLayoutConfigLoaded = false;
+ private int order = Ordered.LOWEST_PRECEDENCE - 50;
+
+ public GrailsLayoutViewResolver() {
+ super();
+ }
+
+ public GrailsLayoutViewResolver(ViewResolver innerViewResolver, GroovyPageLayoutFinder groovyPageLayoutFinder) {
+ super(innerViewResolver, groovyPageLayoutFinder);
+ }
+
+ @Override
+ protected View createLayoutView(View innerView) {
+ return new GrailsLayoutView(groovyPageLayoutFinder, innerView, contentProcessor);
+ }
+
+ public void init() {
+ if(servletContext == null) return;
+
+ Factory grailsLayoutFactory = (Factory)servletContext.getAttribute(FACTORY_SERVLET_CONTEXT_ATTRIBUTE);
+ if(grailsLayoutFactory==null) {
Review Comment:
- Align on space after `if`?
- Spacing?
##########
grails-gsp/plugin/build.gradle:
##########
@@ -84,7 +84,8 @@ dependencies {
exclude group: 'org.apache.grails', module: 'grails-encoder'
exclude group: 'org.springframework', module: 'spring-core'
}
- implementation project(':grails-web-gsp-taglib'), { // RenderTagLib
+
+ api project(':grails-web-gsp-taglib'), { // RenderTagLib
Review Comment:
What caused this change?
##########
grails-gsp/grails-web-gsp/src/main/groovy/org/grails/web/servlet/view/GrailsLayoutViewResolver.java:
##########
@@ -0,0 +1,147 @@
+/*
+ * 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
+ *
+ * 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.servlet.view;
+
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Collections;
+
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletContext;
+
+import grails.core.GrailsApplication;
+import grails.core.support.GrailsApplicationAware;
+import org.apache.grails.web.layout.FactoryHolder;
+import org.apache.grails.web.layout.GroovyPageLayoutFinder;
+import org.apache.grails.web.layout.GrailsLayoutView;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.core.Ordered;
+import org.springframework.web.servlet.View;
+import org.springframework.web.servlet.ViewResolver;
+
+import com.opensymphony.module.sitemesh.Config;
+import com.opensymphony.module.sitemesh.Factory;
+import com.opensymphony.module.sitemesh.factory.DefaultFactory;
+import com.opensymphony.sitemesh.ContentProcessor;
+import com.opensymphony.sitemesh.compatability.PageParser2ContentProcessor;
+
+public class GrailsLayoutViewResolver extends EmbeddedGrailsLayoutViewResolver implements GrailsApplicationAware, DisposableBean, Ordered, ApplicationListener<ContextRefreshedEvent>{
+ private static final String FACTORY_SERVLET_CONTEXT_ATTRIBUTE = "grails.layout.factory";
+ private ContentProcessor contentProcessor;
+ protected GrailsApplication grailsApplication;
+ private boolean grailsLayoutConfigLoaded = false;
+ private int order = Ordered.LOWEST_PRECEDENCE - 50;
+
+ public GrailsLayoutViewResolver() {
+ super();
+ }
+
+ public GrailsLayoutViewResolver(ViewResolver innerViewResolver, GroovyPageLayoutFinder groovyPageLayoutFinder) {
+ super(innerViewResolver, groovyPageLayoutFinder);
+ }
+
+ @Override
+ protected View createLayoutView(View innerView) {
+ return new GrailsLayoutView(groovyPageLayoutFinder, innerView, contentProcessor);
+ }
+
+ public void init() {
+ if(servletContext == null) return;
+
+ Factory grailsLayoutFactory = (Factory)servletContext.getAttribute(FACTORY_SERVLET_CONTEXT_ATTRIBUTE);
+ if(grailsLayoutFactory==null) {
+ grailsLayoutFactory = loadGrailsLayoutConfig();
+ }
+ contentProcessor = new PageParser2ContentProcessor(grailsLayoutFactory);
+ }
+
+ protected Factory loadGrailsLayoutConfig() {
+ FilterConfig filterConfig=new FilterConfig() {
+ private Map<String, String> customConfig =
+ Collections.singletonMap("configFile",
+ "classpath:org/apache/grails/web/layout/grails-layout-default.xml");
+
+ @Override
+ public ServletContext getServletContext() {
+ return servletContext;
+ }
+
+ @Override
+ public Enumeration<String> getInitParameterNames() {
+ return Collections.enumeration(customConfig.keySet());
+ }
+
+ @Override
+ public String getInitParameter(String name) {
+ return customConfig.get(name);
+ }
+
+ @Override
+ public String getFilterName() {
+ return null;
+ }
+ };
+ Config config = new Config(filterConfig);
+
+ DefaultFactory grailsLayoutFactory = new DefaultFactory(config);
+ if(servletContext != null) {
+ servletContext.setAttribute(FACTORY_SERVLET_CONTEXT_ATTRIBUTE, grailsLayoutFactory);
+ }
+ grailsLayoutFactory.refresh();
+ FactoryHolder.setFactory(grailsLayoutFactory);
+ grailsLayoutConfigLoaded = true;
+ return grailsLayoutFactory;
+ }
+
+ @Override
+ public void setGrailsApplication(GrailsApplication grailsApplication) {
+ this.grailsApplication = grailsApplication;
+ }
+
+ @Override
+ public void destroy() throws Exception {
+ clearGrailsLayoutConfig();
+ }
+
+ protected void clearGrailsLayoutConfig() {
+ if(servletContext == null) return;
+ if(grailsLayoutConfigLoaded) {
+ FactoryHolder.setFactory(null);
+ if(servletContext != null) {
+ servletContext.removeAttribute(FACTORY_SERVLET_CONTEXT_ATTRIBUTE);
+ }
+ grailsLayoutConfigLoaded = false;
+ }
+ }
Review Comment:
Align on space after `if`?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/EmbeddedGrailsLayoutView.java:
##########
@@ -0,0 +1,173 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import groovy.text.Template;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.grails.web.servlet.WrappedResponseHolder;
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.web.servlet.mvc.OutputAwareHttpServletResponse;
+import org.grails.web.servlet.view.AbstractGrailsView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.web.servlet.View;
+
+import com.opensymphony.module.sitemesh.RequestConstants;
+import com.opensymphony.sitemesh.Content;
+
+public class EmbeddedGrailsLayoutView extends AbstractGrailsView {
+ private static final Logger LOG = LoggerFactory.getLogger(EmbeddedGrailsLayoutView.class);
+ GroovyPageLayoutFinder groovyPageLayoutFinder;
+
+ protected View innerView;
+
+ public static final String GSP_GRAILS_LAYOUT_PAGE = EmbeddedGrailsLayoutView.class.getName() + ".GSP_GRAILS_LAYOUT_PAGE";
+
+ public EmbeddedGrailsLayoutView(GroovyPageLayoutFinder groovyPageLayoutFinder, View innerView) {
+ this.groovyPageLayoutFinder = groovyPageLayoutFinder;
+ this.innerView = innerView;
+ }
+
+ @Override
+ public String getContentType() {
+ return MediaType.ALL_VALUE;
+ }
+
+ @Override
+ protected void renderTemplate(Map<String, Object> model, GrailsWebRequest webRequest, HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+
+ boolean isCommitted = response.isCommitted() && (response instanceof OutputAwareHttpServletResponse) && !((OutputAwareHttpServletResponse) response).isWriterAvailable();
+ if( !isCommitted ) {
+
+ Content content = obtainContent(model, webRequest, request, response);
+ if (content != null) {
+
+ beforeDecorating(content, model, webRequest, request, response);
+ switch (request.getDispatcherType()) {
+ case INCLUDE:
+ break;
+ case ASYNC:
+ case ERROR:
+ case FORWARD:
+ case REQUEST:
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Finding layout for request and content" );
Review Comment:
Spacing?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -247,6 +255,44 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertOutputEquals('<th id="1" class="sortable" ><a href="/custompathCustomNamespace/mockcontroller/list?sort=id&order=asc">ID</a></th>', template)
}
+ void testPageProperty() {
+
+ def template = '<g:pageProperty name="foo.bar" />'
+
+ def head = ""
+ def page = new TokenizedHTMLPage([] as char[], new CharArray(0), new CharArray(0))
+ request[RequestConstants.PAGE] = page
+
+ page.addProperty("foo.bar", "good")
+
+ assertOutputEquals("good", template)
+
+ template = '<g:pageProperty name="foo.bar" writeEntireProperty="true" />'
+ assertOutputEquals(" bar=\"good\"", template)
Review Comment:
Align on single quotes for strings?
##########
grails-gsp/grails-web-gsp/src/main/groovy/org/grails/web/servlet/view/GrailsLayoutViewResolver.java:
##########
@@ -0,0 +1,147 @@
+/*
+ * 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
+ *
+ * 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.servlet.view;
+
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Collections;
+
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletContext;
+
+import grails.core.GrailsApplication;
+import grails.core.support.GrailsApplicationAware;
+import org.apache.grails.web.layout.FactoryHolder;
+import org.apache.grails.web.layout.GroovyPageLayoutFinder;
+import org.apache.grails.web.layout.GrailsLayoutView;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.core.Ordered;
+import org.springframework.web.servlet.View;
+import org.springframework.web.servlet.ViewResolver;
+
+import com.opensymphony.module.sitemesh.Config;
+import com.opensymphony.module.sitemesh.Factory;
+import com.opensymphony.module.sitemesh.factory.DefaultFactory;
+import com.opensymphony.sitemesh.ContentProcessor;
+import com.opensymphony.sitemesh.compatability.PageParser2ContentProcessor;
+
+public class GrailsLayoutViewResolver extends EmbeddedGrailsLayoutViewResolver implements GrailsApplicationAware, DisposableBean, Ordered, ApplicationListener<ContextRefreshedEvent>{
+ private static final String FACTORY_SERVLET_CONTEXT_ATTRIBUTE = "grails.layout.factory";
+ private ContentProcessor contentProcessor;
+ protected GrailsApplication grailsApplication;
+ private boolean grailsLayoutConfigLoaded = false;
+ private int order = Ordered.LOWEST_PRECEDENCE - 50;
+
+ public GrailsLayoutViewResolver() {
+ super();
+ }
+
+ public GrailsLayoutViewResolver(ViewResolver innerViewResolver, GroovyPageLayoutFinder groovyPageLayoutFinder) {
+ super(innerViewResolver, groovyPageLayoutFinder);
+ }
+
+ @Override
+ protected View createLayoutView(View innerView) {
+ return new GrailsLayoutView(groovyPageLayoutFinder, innerView, contentProcessor);
+ }
+
+ public void init() {
+ if(servletContext == null) return;
Review Comment:
Align on space after `if`?
##########
grails-gsp/grails-web-gsp/src/main/groovy/org/grails/web/servlet/view/GrailsLayoutViewResolver.java:
##########
@@ -0,0 +1,147 @@
+/*
+ * 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
+ *
+ * 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.servlet.view;
+
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.Collections;
+
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletContext;
+
+import grails.core.GrailsApplication;
+import grails.core.support.GrailsApplicationAware;
+import org.apache.grails.web.layout.FactoryHolder;
+import org.apache.grails.web.layout.GroovyPageLayoutFinder;
+import org.apache.grails.web.layout.GrailsLayoutView;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.core.Ordered;
+import org.springframework.web.servlet.View;
+import org.springframework.web.servlet.ViewResolver;
+
+import com.opensymphony.module.sitemesh.Config;
+import com.opensymphony.module.sitemesh.Factory;
+import com.opensymphony.module.sitemesh.factory.DefaultFactory;
+import com.opensymphony.sitemesh.ContentProcessor;
+import com.opensymphony.sitemesh.compatability.PageParser2ContentProcessor;
+
+public class GrailsLayoutViewResolver extends EmbeddedGrailsLayoutViewResolver implements GrailsApplicationAware, DisposableBean, Ordered, ApplicationListener<ContextRefreshedEvent>{
+ private static final String FACTORY_SERVLET_CONTEXT_ATTRIBUTE = "grails.layout.factory";
+ private ContentProcessor contentProcessor;
+ protected GrailsApplication grailsApplication;
+ private boolean grailsLayoutConfigLoaded = false;
+ private int order = Ordered.LOWEST_PRECEDENCE - 50;
+
+ public GrailsLayoutViewResolver() {
+ super();
+ }
+
+ public GrailsLayoutViewResolver(ViewResolver innerViewResolver, GroovyPageLayoutFinder groovyPageLayoutFinder) {
+ super(innerViewResolver, groovyPageLayoutFinder);
+ }
+
+ @Override
+ protected View createLayoutView(View innerView) {
+ return new GrailsLayoutView(groovyPageLayoutFinder, innerView, contentProcessor);
+ }
+
+ public void init() {
+ if(servletContext == null) return;
+
+ Factory grailsLayoutFactory = (Factory)servletContext.getAttribute(FACTORY_SERVLET_CONTEXT_ATTRIBUTE);
+ if(grailsLayoutFactory==null) {
+ grailsLayoutFactory = loadGrailsLayoutConfig();
+ }
+ contentProcessor = new PageParser2ContentProcessor(grailsLayoutFactory);
+ }
+
+ protected Factory loadGrailsLayoutConfig() {
+ FilterConfig filterConfig=new FilterConfig() {
Review Comment:
Spacing?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/EmbeddedGrailsLayoutView.java:
##########
@@ -0,0 +1,173 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import groovy.text.Template;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.grails.web.servlet.WrappedResponseHolder;
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.web.servlet.mvc.OutputAwareHttpServletResponse;
+import org.grails.web.servlet.view.AbstractGrailsView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.web.servlet.View;
+
+import com.opensymphony.module.sitemesh.RequestConstants;
+import com.opensymphony.sitemesh.Content;
+
+public class EmbeddedGrailsLayoutView extends AbstractGrailsView {
+ private static final Logger LOG = LoggerFactory.getLogger(EmbeddedGrailsLayoutView.class);
+ GroovyPageLayoutFinder groovyPageLayoutFinder;
+
+ protected View innerView;
+
+ public static final String GSP_GRAILS_LAYOUT_PAGE = EmbeddedGrailsLayoutView.class.getName() + ".GSP_GRAILS_LAYOUT_PAGE";
+
+ public EmbeddedGrailsLayoutView(GroovyPageLayoutFinder groovyPageLayoutFinder, View innerView) {
+ this.groovyPageLayoutFinder = groovyPageLayoutFinder;
+ this.innerView = innerView;
+ }
+
+ @Override
+ public String getContentType() {
+ return MediaType.ALL_VALUE;
+ }
+
+ @Override
+ protected void renderTemplate(Map<String, Object> model, GrailsWebRequest webRequest, HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+
+ boolean isCommitted = response.isCommitted() && (response instanceof OutputAwareHttpServletResponse) && !((OutputAwareHttpServletResponse) response).isWriterAvailable();
+ if( !isCommitted ) {
+
+ Content content = obtainContent(model, webRequest, request, response);
+ if (content != null) {
+
+ beforeDecorating(content, model, webRequest, request, response);
+ switch (request.getDispatcherType()) {
+ case INCLUDE:
+ break;
+ case ASYNC:
+ case ERROR:
+ case FORWARD:
+ case REQUEST:
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Finding layout for request and content" );
+ }
+ SpringMVCViewDecorator decorator = (SpringMVCViewDecorator) groovyPageLayoutFinder.findLayout(request, content);
+ if (decorator != null) {
+ if(LOG.isDebugEnabled()) {
Review Comment:
Align on space after `if`?
##########
grails-gsp/grails-web-layout/src/test/groovy/org/apache/grails/web/layout/GroovyPageLayoutFinderSpec.groovy:
##########
@@ -0,0 +1,54 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout
+
+import org.grails.web.servlet.view.GrailsViewResolver
+import org.springframework.mock.web.MockHttpServletRequest
+import spock.lang.Issue
+import spock.lang.Specification
+import spock.lang.Unroll
+import jakarta.servlet.http.HttpServletRequest
+
+class GroovyPageLayoutFinderSpec extends Specification {
+ @Issue("https://github.com/apache/grails-core/issues/10371")
Review Comment:
Align on blank line after class definition?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/TokenizedHTMLPage2Content.java:
##########
@@ -0,0 +1,78 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import com.opensymphony.sitemesh.Content;
+import com.opensymphony.module.sitemesh.parser.TokenizedHTMLPage;
+
+final class TokenizedHTMLPage2Content implements Content {
+ private final TokenizedHTMLPage page;
Review Comment:
Align on blank line after class definition?
##########
grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/GroovyPagesGrailsPlugin.groovy:
##########
Review Comment:
Align on single quotes for strings?
##########
grails-gsp/grails-web-layout/build.gradle:
##########
@@ -0,0 +1,59 @@
+/*
+ * 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
+ *
+ * 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.
+ */
+plugins {
+ id 'groovy'
+ id 'java-library'
+ id 'project-report'
+ id 'org.apache.grails.gradle.grails-plugin'
+}
+
+version = projectVersion
+group = 'org.apache.grails.views'
+
+ext {
+ pomTitle = 'Layout Grails Plugin'
+ pomDescription = 'Provides layout & decoration features for the Grails Framework.'
+}
+
+dependencies {
+ implementation platform(project(':grails-bom'))
+ implementation project(':grails-common') // for groovy
+
+ api "opensymphony:sitemesh"
Review Comment:
Align on single quotes for strings?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/EmbeddedGrailsLayoutView.java:
##########
@@ -0,0 +1,173 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import groovy.text.Template;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.grails.web.servlet.WrappedResponseHolder;
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.web.servlet.mvc.OutputAwareHttpServletResponse;
+import org.grails.web.servlet.view.AbstractGrailsView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.web.servlet.View;
+
+import com.opensymphony.module.sitemesh.RequestConstants;
+import com.opensymphony.sitemesh.Content;
+
+public class EmbeddedGrailsLayoutView extends AbstractGrailsView {
+ private static final Logger LOG = LoggerFactory.getLogger(EmbeddedGrailsLayoutView.class);
+ GroovyPageLayoutFinder groovyPageLayoutFinder;
+
+ protected View innerView;
+
+ public static final String GSP_GRAILS_LAYOUT_PAGE = EmbeddedGrailsLayoutView.class.getName() + ".GSP_GRAILS_LAYOUT_PAGE";
+
+ public EmbeddedGrailsLayoutView(GroovyPageLayoutFinder groovyPageLayoutFinder, View innerView) {
+ this.groovyPageLayoutFinder = groovyPageLayoutFinder;
+ this.innerView = innerView;
+ }
+
+ @Override
+ public String getContentType() {
+ return MediaType.ALL_VALUE;
+ }
+
+ @Override
+ protected void renderTemplate(Map<String, Object> model, GrailsWebRequest webRequest, HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+
+ boolean isCommitted = response.isCommitted() && (response instanceof OutputAwareHttpServletResponse) && !((OutputAwareHttpServletResponse) response).isWriterAvailable();
+ if( !isCommitted ) {
+
+ Content content = obtainContent(model, webRequest, request, response);
+ if (content != null) {
+
+ beforeDecorating(content, model, webRequest, request, response);
+ switch (request.getDispatcherType()) {
+ case INCLUDE:
+ break;
+ case ASYNC:
+ case ERROR:
+ case FORWARD:
+ case REQUEST:
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Finding layout for request and content" );
+ }
+ SpringMVCViewDecorator decorator = (SpringMVCViewDecorator) groovyPageLayoutFinder.findLayout(request, content);
+ if (decorator != null) {
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Found layout. Rendering content for layout {} and model {}", decorator.getPage(), model);
+ }
+
+ decorator.render(content, model, request, response, webRequest.getServletContext());
+ return;
+ }
+ break;
+ }
+ PrintWriter writer = response.getWriter();
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Layout not applicable to response, writing original content");
+ }
+ content.writeOriginal(writer);
+ if (!response.isCommitted()) {
+ writer.flush();
+ }
+ }
+ }
+
+
+ }
+
+ protected void beforeDecorating(Content content, Map<String, Object> model, GrailsWebRequest webRequest,
+ HttpServletRequest request, HttpServletResponse response) {
+ applyMetaHttpEquivContentType(content, response);
+ }
+
+ protected void applyMetaHttpEquivContentType(Content content, HttpServletResponse response) {
+ String contentType = content.getProperty("meta.http-equiv.Content-Type");
+ if (contentType != null && "text/html".equals(response.getContentType())) {
+ response.setContentType(contentType);
+ }
+ }
+
+ protected Content obtainContent(Map<String, Object> model, GrailsWebRequest webRequest, HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+ Object oldPage = request.getAttribute(RequestConstants.PAGE);
+ request.removeAttribute(RequestConstants.PAGE);
+ Object oldGspGrailsLayoutPage=request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE);
+ HttpServletResponse previousResponse = webRequest.getWrappedResponse();
+ HttpServletResponse previousWrappedResponse = WrappedResponseHolder.getWrappedResponse();
+ try {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, new GSPGrailsLayoutPage());
+
+ GrailsContentBufferingResponse contentBufferingResponse = createContentBufferingResponse(model, webRequest, request, response);
+ webRequest.setWrappedResponse(contentBufferingResponse);
+ WrappedResponseHolder.setWrappedResponse(contentBufferingResponse);
+
+ renderInnerView(model, webRequest, request, response, contentBufferingResponse);
+
+ return contentBufferingResponse.getContent();
+ }
+ finally {
+ if (oldGspGrailsLayoutPage != null) {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage);
+ }
+ if (oldPage != null) {
+ request.setAttribute(RequestConstants.PAGE, oldPage);
+ }
+ webRequest.setWrappedResponse(previousResponse);
+ WrappedResponseHolder.setWrappedResponse(previousWrappedResponse);
+ }
+ }
+
+ protected void renderInnerView(Map<String, Object> model, GrailsWebRequest webRequest, HttpServletRequest request,
+ HttpServletResponse response,
+ GrailsContentBufferingResponse contentBufferingResponse) throws Exception {
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Rendering inner view for layout and model {}", model);
+ }
+ innerView.render(model, request, contentBufferingResponse);
+ }
+
+ protected GrailsContentBufferingResponse createContentBufferingResponse(Map<String, Object> model, GrailsWebRequest webRequest, HttpServletRequest request,
+ HttpServletResponse response) {
+ return new GrailsViewBufferingResponse(request, response);
+ }
+
+ @Override
+ public Template getTemplate() {
+ if(innerView instanceof AbstractGrailsView) {
Review Comment:
Align on space after `if`?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GrailsHTMLPageParser.java:
##########
@@ -0,0 +1,86 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import java.io.IOException;
+
+import com.opensymphony.module.sitemesh.Page;
+import com.opensymphony.module.sitemesh.html.HTMLProcessor;
+import com.opensymphony.module.sitemesh.html.State;
+import com.opensymphony.module.sitemesh.html.StateTransitionRule;
+import com.opensymphony.module.sitemesh.html.rules.BodyTagRule;
+import com.opensymphony.module.sitemesh.html.rules.ContentBlockExtractingRule;
+import com.opensymphony.module.sitemesh.html.rules.FramesetRule;
+import com.opensymphony.module.sitemesh.html.rules.HeadExtractingRule;
+import com.opensymphony.module.sitemesh.html.rules.HtmlAttributesRule;
+import com.opensymphony.module.sitemesh.html.rules.MSOfficeDocumentPropertiesRule;
+import com.opensymphony.module.sitemesh.html.rules.MetaTagRule;
+import com.opensymphony.module.sitemesh.html.rules.PageBuilder;
+import com.opensymphony.module.sitemesh.html.rules.ParameterExtractingRule;
+import com.opensymphony.module.sitemesh.html.rules.TitleExtractingRule;
+import com.opensymphony.module.sitemesh.html.util.CharArray;
+import com.opensymphony.module.sitemesh.parser.HTMLPageParser;
+import com.opensymphony.module.sitemesh.parser.TokenizedHTMLPage;
+import com.opensymphony.sitemesh.Content;
+
+public class GrailsHTMLPageParser extends HTMLPageParser {
+ @Override
Review Comment:
Align on blank line after class definition?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/EmbeddedGrailsLayoutView.java:
##########
@@ -0,0 +1,173 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import groovy.text.Template;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.grails.web.servlet.WrappedResponseHolder;
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.web.servlet.mvc.OutputAwareHttpServletResponse;
+import org.grails.web.servlet.view.AbstractGrailsView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.web.servlet.View;
+
+import com.opensymphony.module.sitemesh.RequestConstants;
+import com.opensymphony.sitemesh.Content;
+
+public class EmbeddedGrailsLayoutView extends AbstractGrailsView {
+ private static final Logger LOG = LoggerFactory.getLogger(EmbeddedGrailsLayoutView.class);
+ GroovyPageLayoutFinder groovyPageLayoutFinder;
+
+ protected View innerView;
+
+ public static final String GSP_GRAILS_LAYOUT_PAGE = EmbeddedGrailsLayoutView.class.getName() + ".GSP_GRAILS_LAYOUT_PAGE";
+
+ public EmbeddedGrailsLayoutView(GroovyPageLayoutFinder groovyPageLayoutFinder, View innerView) {
+ this.groovyPageLayoutFinder = groovyPageLayoutFinder;
+ this.innerView = innerView;
+ }
+
+ @Override
+ public String getContentType() {
+ return MediaType.ALL_VALUE;
+ }
+
+ @Override
+ protected void renderTemplate(Map<String, Object> model, GrailsWebRequest webRequest, HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+
+ boolean isCommitted = response.isCommitted() && (response instanceof OutputAwareHttpServletResponse) && !((OutputAwareHttpServletResponse) response).isWriterAvailable();
+ if( !isCommitted ) {
+
+ Content content = obtainContent(model, webRequest, request, response);
+ if (content != null) {
+
+ beforeDecorating(content, model, webRequest, request, response);
+ switch (request.getDispatcherType()) {
+ case INCLUDE:
+ break;
+ case ASYNC:
+ case ERROR:
+ case FORWARD:
+ case REQUEST:
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Finding layout for request and content" );
+ }
+ SpringMVCViewDecorator decorator = (SpringMVCViewDecorator) groovyPageLayoutFinder.findLayout(request, content);
+ if (decorator != null) {
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Found layout. Rendering content for layout {} and model {}", decorator.getPage(), model);
+ }
+
+ decorator.render(content, model, request, response, webRequest.getServletContext());
+ return;
+ }
+ break;
+ }
+ PrintWriter writer = response.getWriter();
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Layout not applicable to response, writing original content");
+ }
+ content.writeOriginal(writer);
+ if (!response.isCommitted()) {
+ writer.flush();
+ }
+ }
+ }
+
+
Review Comment:
Spacing?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GrailsLayoutRenderViewMutator.groovy:
##########
@@ -0,0 +1,50 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout
+
+import grails.web.pages.GrailsRenderViewMutator
+import org.springframework.beans.factory.NoSuchBeanDefinitionException
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.web.servlet.View
+
+class GrailsLayoutRenderViewMutator implements GrailsRenderViewMutator {
+ @Autowired
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+
+ @Override
+ View mutateView(boolean renderWithLayout, String templateUri, Locale locale, View existingView) {
+ View toReturn = existingView
+
+ // if automatic decoration occurred unwrap, since this is a partial
+ if(toReturn instanceof EmbeddedGrailsLayoutView) {
Review Comment:
Align on space after `if`?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -247,6 +255,44 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertOutputEquals('<th id="1" class="sortable" ><a href="/custompathCustomNamespace/mockcontroller/list?sort=id&order=asc">ID</a></th>', template)
}
+ void testPageProperty() {
+
+ def template = '<g:pageProperty name="foo.bar" />'
+
+ def head = ""
+ def page = new TokenizedHTMLPage([] as char[], new CharArray(0), new CharArray(0))
+ request[RequestConstants.PAGE] = page
+
+ page.addProperty("foo.bar", "good")
Review Comment:
Align on single quotes for strings?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -693,6 +739,33 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertEquals 'my/contenttype', response.getContentType()
}
+ void testApplyLayout() {
+ GrailsLayoutDecoratorMapper decoratorMapper = new GrailsLayoutDecoratorMapper()
+ decoratorMapper.groovyPageLayoutFinder = appCtx.groovyPageLayoutFinder
+ FactoryHolder.setFactory([getDecoratorMapper: { -> decoratorMapper
+ }] as com.opensymphony.module.sitemesh.Factory)
+ def resourceLoader = new MockStringResourceLoader()
+ resourceLoader.registerMockResource('/layouts/layout.gsp', '<layoutapplied><g:layoutTitle /> - <g:layoutBody/></layoutapplied>')
+ appCtx.groovyPagesTemplateEngine.groovyPageLocator.addResourceLoader(resourceLoader)
+ def template = '<g:applyLayout name="layout"><html><head><title>title here</title></head><body>Hello world!</body></html></g:applyLayout>'
+ assertOutputEquals '<layoutapplied>title here - Hello world!</layoutapplied>', template
+ }
+
+ void testApplyLayoutParse() {
+ GrailsLayoutDecoratorMapper decoratorMapper = new GrailsLayoutDecoratorMapper()
+ decoratorMapper.groovyPageLayoutFinder = appCtx.groovyPageLayoutFinder
+ FactoryHolder.setFactory([getDecoratorMapper: { -> decoratorMapper
+ }, getPageParser : { String contentType -> new HTMLPageParser() }] as com.opensymphony.module.sitemesh.Factory)
Review Comment:
Suggestion:
```groovy
FactoryHolder.setFactory([
getDecoratorMapper: { -> decoratorMapper },
getPageParser: { String contentType -> new HTMLPageParser() }
] as com.opensymphony.module.sitemesh.Factory)
```
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GSPGrailsLayoutPage.java:
##########
@@ -0,0 +1,239 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import com.opensymphony.module.sitemesh.HTMLPage;
+import com.opensymphony.module.sitemesh.parser.AbstractHTMLPage;
+import com.opensymphony.sitemesh.Content;
+import com.opensymphony.sitemesh.compatability.Content2HTMLPage;
+import org.grails.buffer.StreamCharBuffer;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Grails/GSP specific implementation of Sitemesh's AbstractHTMLPage
+ *
+ * g:capture* tags in RenderTagLib are used to capture head, meta, title, component and body contents.
+ * No html parsing is required for templating since capture tags are added at GSP compilation time.
+ *
+ * @author Lari Hotari, Sagire Software Oy
+ */
+public class GSPGrailsLayoutPage extends AbstractHTMLPage implements Content{
+ StreamCharBuffer headBuffer;
+ StreamCharBuffer bodyBuffer;
+ StreamCharBuffer pageBuffer;
+ StreamCharBuffer titleBuffer;
+ boolean used;
+ boolean titleCaptured;
+ Map<String, StreamCharBuffer> contentBuffers;
+ private boolean renderingLayout;
+
+ public GSPGrailsLayoutPage() {
+ this(false);
+ }
+
+ public GSPGrailsLayoutPage(boolean renderingLayout) {
+ reset();
+ this.renderingLayout=renderingLayout;
+ }
+
+ public void reset() {
+ headBuffer=null;
+ bodyBuffer=null;
+ pageBuffer=null;
+ titleBuffer=null;
Review Comment:
Spacing?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/EmbeddedGrailsLayoutView.java:
##########
@@ -0,0 +1,173 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import groovy.text.Template;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.grails.web.servlet.WrappedResponseHolder;
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.web.servlet.mvc.OutputAwareHttpServletResponse;
+import org.grails.web.servlet.view.AbstractGrailsView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.web.servlet.View;
+
+import com.opensymphony.module.sitemesh.RequestConstants;
+import com.opensymphony.sitemesh.Content;
+
+public class EmbeddedGrailsLayoutView extends AbstractGrailsView {
+ private static final Logger LOG = LoggerFactory.getLogger(EmbeddedGrailsLayoutView.class);
+ GroovyPageLayoutFinder groovyPageLayoutFinder;
+
+ protected View innerView;
+
+ public static final String GSP_GRAILS_LAYOUT_PAGE = EmbeddedGrailsLayoutView.class.getName() + ".GSP_GRAILS_LAYOUT_PAGE";
+
+ public EmbeddedGrailsLayoutView(GroovyPageLayoutFinder groovyPageLayoutFinder, View innerView) {
+ this.groovyPageLayoutFinder = groovyPageLayoutFinder;
+ this.innerView = innerView;
+ }
+
+ @Override
+ public String getContentType() {
+ return MediaType.ALL_VALUE;
+ }
+
+ @Override
+ protected void renderTemplate(Map<String, Object> model, GrailsWebRequest webRequest, HttpServletRequest request,
+ HttpServletResponse response) throws Exception {
+
+ boolean isCommitted = response.isCommitted() && (response instanceof OutputAwareHttpServletResponse) && !((OutputAwareHttpServletResponse) response).isWriterAvailable();
+ if( !isCommitted ) {
+
+ Content content = obtainContent(model, webRequest, request, response);
+ if (content != null) {
+
+ beforeDecorating(content, model, webRequest, request, response);
+ switch (request.getDispatcherType()) {
+ case INCLUDE:
+ break;
+ case ASYNC:
+ case ERROR:
+ case FORWARD:
+ case REQUEST:
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Finding layout for request and content" );
+ }
+ SpringMVCViewDecorator decorator = (SpringMVCViewDecorator) groovyPageLayoutFinder.findLayout(request, content);
+ if (decorator != null) {
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("Found layout. Rendering content for layout {} and model {}", decorator.getPage(), model);
+ }
+
+ decorator.render(content, model, request, response, webRequest.getServletContext());
+ return;
+ }
+ break;
+ }
+ PrintWriter writer = response.getWriter();
+ if(LOG.isDebugEnabled()) {
Review Comment:
Align on space after `if`?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GrailsLayoutRenderViewMutator.groovy:
##########
@@ -0,0 +1,50 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout
+
+import grails.web.pages.GrailsRenderViewMutator
+import org.springframework.beans.factory.NoSuchBeanDefinitionException
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.web.servlet.View
+
+class GrailsLayoutRenderViewMutator implements GrailsRenderViewMutator {
+ @Autowired
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+
+ @Override
+ View mutateView(boolean renderWithLayout, String templateUri, Locale locale, View existingView) {
+ View toReturn = existingView
+
+ // if automatic decoration occurred unwrap, since this is a partial
+ if(toReturn instanceof EmbeddedGrailsLayoutView) {
+ toReturn = ((EmbeddedGrailsLayoutView)toReturn).getInnerView()
Review Comment:
Align on space after cast?
##########
grails-gsp/grails-layout/src/main/groovy/org/grails/plugins/web/taglib/RenderGrailsLayoutTagLib.groovy:
##########
@@ -0,0 +1,337 @@
+/*
+ * 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
+ *
+ * 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.plugins.web.taglib
+
+import com.opensymphony.module.sitemesh.*
+import grails.artefact.TagLibrary
+import grails.core.GrailsApplication
+import grails.core.support.GrailsApplicationAware
+import grails.gsp.TagLib
+import grails.util.TypeConvertingMap
+import groovy.text.Template
+import groovy.transform.CompileStatic
+import org.apache.grails.web.layout.FactoryHolder
+import org.apache.grails.web.layout.GSPGrailsLayoutPage
+import org.apache.grails.web.layout.GrailsHTMLPageParser
+import org.apache.grails.web.layout.EmbeddedGrailsLayoutView
+import org.apache.grails.web.layout.GroovyPageLayoutFinder
+import org.apache.grails.web.layout.SpringMVCViewDecorator
+import org.grails.buffer.FastStringWriter
+import org.grails.buffer.StreamCharBuffer
+import org.grails.gsp.GroovyPageTemplate
+import org.grails.gsp.GroovyPagesTemplateEngine
+import org.grails.gsp.compiler.GroovyPageParser
+import org.grails.taglib.TagLibraryLookup
+import org.grails.taglib.TagOutput
+import org.grails.taglib.encoder.OutputContextLookupHelper
+import jakarta.servlet.http.HttpServletRequest
+
+/**
+ * Tags to help rendering of views and layouts.
+ *
+ * @author Graeme Rocher
+ */
+@CompileStatic
+@TagLib
+class RenderGrailsLayoutTagLib implements RequestConstants, TagLibrary, GrailsApplicationAware {
+ GroovyPagesTemplateEngine groovyPagesTemplateEngine
+ TagLibraryLookup gspTagLibraryLookup
+ GroovyPageLayoutFinder groovyPageLayoutFinder
+ protected boolean grailsLayoutPreprocessMode = true
+
+ protected HTMLPage getPage() {
+ return (HTMLPage) getRequest().getAttribute(PAGE)
+ }
+
+ @Override
+ void setGrailsApplication(GrailsApplication grailsApplication) {
+ grailsLayoutPreprocessMode = grailsApplication.config.getProperty(GroovyPageParser.CONFIG_PROPERTY_GSP_GRAILS_LAYOUT_PREPROCESS, Boolean, true)
+ }
+
+ protected boolean isGrailsLayoutPreprocessMode() {
+ return grailsLayoutPreprocessMode
+ }
+
+
+ /**
+ * Apply a layout to a particular block of text or to the given view or template.<br/>
+ *
+ * <g:applyLayout name="myLayout">some text</g:applyLayout><br/>
+ * <g:applyLayout name="myLayout" template="mytemplate" /><br/>
+ * <g:applyLayout name="myLayout" url="http://www.google.com" /><br/>
+ * <g:applyLayout name="myLayout" action="myAction" controller="myController"><br/>
+ *
+ * @attr name The name of the layout
+ * @attr template Optional. The template to apply the layout to
+ * @attr url Optional. The URL to retrieve the content from and apply a layout to
+ * @attr action Optional. The action to be called to generate the content to apply the layout to
+ * @attr controller Optional. The controller that contains the action that will generate the content to apply the layout to
+ * @attr contentType Optional. The content type to use, default is "text/html"
+ * @attr encoding Optional. The encoding to use
+ * @attr params Optional. The params to pass onto the page object
+ * @attr parse Optional. If true, Grails Layout parser will always be used to parse the content.
+ */
+ Closure applyLayout = { Map attrs, body ->
+ if (!groovyPagesTemplateEngine) throw new IllegalStateException("Property [groovyPagesTemplateEngine] must be set!")
+ def oldPage = getPage()
+ String contentType = attrs.contentType ? attrs.contentType as String : "text/html"
+
+ Map pageParams = attrs.params instanceof Map ? (Map) attrs.params : [:]
+ Map viewModel = attrs.model instanceof Map ? (Map) attrs.model : [:]
+ Object content = null
+ GSPGrailsLayoutPage gspGrailsLayoutPage = null
+ if (attrs.url) {
+ content = new URL(attrs.url as String).getText("UTF-8")
+ } else if (attrs.action && attrs.controller) {
+ def includeAttrs = [action: attrs.action, controller: attrs.controller, params: pageParams, model: viewModel]
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'include', includeAttrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def oldGspGrailsLayoutPage = request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE)
+ try {
+ gspGrailsLayoutPage = new GSPGrailsLayoutPage()
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, gspGrailsLayoutPage)
+ if (attrs.view || attrs.template) {
+ content = TagOutput.captureTagOutput(gspTagLibraryLookup, 'g', 'render', attrs, null, OutputContextLookupHelper.lookupOutputContext())
+ } else {
+ def bodyClosure = TagOutput.createOutputCapturingClosure(this, body, OutputContextLookupHelper.lookupOutputContext())
+ content = bodyClosure()
+ }
+ if (content instanceof StreamCharBuffer) {
+ gspGrailsLayoutPage.setPageBuffer(content)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ } else if (content != null) {
+ FastStringWriter stringWriter = new FastStringWriter()
+ stringWriter.print((Object) content)
+ gspGrailsLayoutPage.setPageBuffer(stringWriter.buffer)
+ gspGrailsLayoutPage.setUsed(isGrailsLayoutPreprocessMode())
+ }
+ }
+ finally {
+ request.setAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE, oldGspGrailsLayoutPage)
+ }
+ }
+ if (content == null) {
+ content = ''
+ }
+
+ Page page = null
+ if (!((TypeConvertingMap) attrs).boolean('parse') && gspGrailsLayoutPage != null && gspGrailsLayoutPage.isUsed()) {
+ page = gspGrailsLayoutPage
+ } else {
+ def parser = createPageParser(contentType)
+ char[] charArray
+ if (content instanceof StreamCharBuffer) {
+ charArray = ((StreamCharBuffer) content).toCharArray()
+ } else {
+ charArray = content.toString().toCharArray()
+ }
+ page = parser.parse(charArray)
+ }
+
+ def decorator = findDecorator(request, attrs.name as String)
+ if (decorator && decorator.page) {
+ pageParams.each { k, v ->
+ page.addProperty(k as String, v as String)
+ }
+ try {
+ request.setAttribute(PAGE, page)
+ Template template = findTemplate(decorator)
+ template.make(viewModel).writeTo(out)
+ }
+ finally {
+ request.setAttribute(PAGE, oldPage)
+ }
+ } else {
+ out << content
+ }
+ }
+
+ protected Template findTemplate(Decorator decorator) {
+ Template template
+ if (decorator instanceof SpringMVCViewDecorator) {
+ template = ((SpringMVCViewDecorator) decorator).getTemplate()
+ if (template instanceof GroovyPageTemplate) {
+ GroovyPageTemplate gpt = (GroovyPageTemplate) template
+ gpt = (GroovyPageTemplate) gpt.clone()
+ gpt.setAllowSettingContentType(false)
+ template = gpt
+ }
+ } else {
+ template = groovyPagesTemplateEngine.createTemplate(decorator.getPage())
+ }
+ return template
+ }
+
+ protected Decorator findDecorator(HttpServletRequest req, String layoutName) {
+ DecoratorMapper decoratorMapper = sitemeshFactory?.getDecoratorMapper()
+ Decorator d
+ if (decoratorMapper) {
+ d = decoratorMapper.getNamedDecorator(req, layoutName)
+ } else {
+ d = groovyPageLayoutFinder.getNamedDecorator(req, layoutName)
+ }
+ d
+ }
+
+ protected PageParser createPageParser(String contentType) {
+ PageParser parser = sitemeshFactory?.getPageParser(contentType)
+ if (parser == null) {
+ parser = new GrailsHTMLPageParser()
+ }
+ return parser
+ }
+
+ protected Factory getSitemeshFactory() {
+ return FactoryHolder.getGrailsLayoutFactory()
+ }
+
+ /**
+ * Used to retrieve a property of the decorated page.<br/>
+ *
+ * <g:pageProperty default="defaultValue" name="body.onload" /><br/>
+ *
+ * @emptyTag
+ *
+ * @attr REQUIRED name the property name
+ * @attr default the default value to use if the property is null
+ * @attr writeEntireProperty if true, writes the property in the form 'foo = "bar"', otherwise renders 'bar'
+ */
+ Closure pageProperty = { Map attrs ->
+ if (!attrs.name) {
+ throwTagError("Tag [pageProperty] is missing required attribute [name]")
+ }
+
+ String propertyName = attrs.name as String
+ def htmlPage = getPage()
+ def propertyValue
Review Comment:
I don't follow. For me it's the opposite. Now the IDE warns that the assignment is not used. Initializing it to `null` should not affect the logic.
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GrailsLayoutDecoratorMapper.java:
##########
@@ -0,0 +1,91 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import java.util.Properties;
+
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.http.HttpServletRequest;
+
+import grails.core.GrailsApplication;
+import org.grails.web.util.WebUtils;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.support.WebApplicationContextUtils;
+
+import com.opensymphony.module.sitemesh.Config;
+import com.opensymphony.module.sitemesh.Decorator;
+import com.opensymphony.module.sitemesh.DecoratorMapper;
+import com.opensymphony.module.sitemesh.Page;
+import com.opensymphony.module.sitemesh.mapper.AbstractDecoratorMapper;
+
+/**
+ * Implements the SiteMesh decorator mapper interface and allows grails views to map to grails layouts.
+ *
+ * @author Graeme Rocher
+ * @author Lari Hotari
+ */
+public class GrailsLayoutDecoratorMapper extends AbstractDecoratorMapper {
+ private GroovyPageLayoutFinder groovyPageLayoutFinder;
Review Comment:
Align on blank line after class definition?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GSPGrailsLayoutPage.java:
##########
@@ -0,0 +1,239 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import com.opensymphony.module.sitemesh.HTMLPage;
+import com.opensymphony.module.sitemesh.parser.AbstractHTMLPage;
+import com.opensymphony.sitemesh.Content;
+import com.opensymphony.sitemesh.compatability.Content2HTMLPage;
+import org.grails.buffer.StreamCharBuffer;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Grails/GSP specific implementation of Sitemesh's AbstractHTMLPage
+ *
+ * g:capture* tags in RenderTagLib are used to capture head, meta, title, component and body contents.
+ * No html parsing is required for templating since capture tags are added at GSP compilation time.
+ *
+ * @author Lari Hotari, Sagire Software Oy
+ */
+public class GSPGrailsLayoutPage extends AbstractHTMLPage implements Content{
Review Comment:
- Spacing?
- Align on blank line after class definition?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GrailsPageResponseWrapper.java:
##########
@@ -0,0 +1,390 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
+
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.buffer.GrailsPrintWriterAdapter;
+import org.grails.buffer.StreamByteBuffer;
+import org.grails.buffer.StreamCharBuffer;
+import org.grails.web.util.WebUtils;
+
+import com.opensymphony.module.sitemesh.Page;
+import com.opensymphony.module.sitemesh.PageParser;
+import com.opensymphony.module.sitemesh.PageParserSelector;
+import com.opensymphony.module.sitemesh.filter.HttpContentType;
+import com.opensymphony.module.sitemesh.filter.RoutableServletOutputStream;
+import com.opensymphony.module.sitemesh.filter.TextEncoder;
+
+/**
+ * @author Graeme Rocher
+ * @since 1.0.4
+ */
+public class GrailsPageResponseWrapper extends HttpServletResponseWrapper {
+
+ private final GrailsRoutablePrintWriter routablePrintWriter;
+ private final RoutableServletOutputStream routableServletOutputStream;
+ private final PageParserSelector parserSelector;
+ private final HttpServletRequest request;
+
+ private GrailsBuffer buffer;
+ private boolean parseablePage = false;
+ private GSPGrailsLayoutPage gspGrailsLayoutPage;
+
+ public GrailsPageResponseWrapper(final HttpServletRequest request, final HttpServletResponse response,
+ PageParserSelector parserSelector) {
+ super(response);
+ this.parserSelector = parserSelector;
+
+ routablePrintWriter = GrailsRoutablePrintWriter.newInstance(new GrailsRoutablePrintWriter.DestinationFactory() {
+ public PrintWriter activateDestination() throws IOException {
+ return response.getWriter();
+ }
+ });
+ routableServletOutputStream = new RoutableServletOutputStream(new RoutableServletOutputStream.DestinationFactory() {
+ public ServletOutputStream create() throws IOException {
+ return response.getOutputStream();
+ }
+ });
+
+ this.request = request;
+
+ gspGrailsLayoutPage = (GSPGrailsLayoutPage) request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE);
+
+ applyContentType(response.getContentType());
+ }
+
+ @Override
+ public void sendError(int sc) throws IOException {
+ clearBuffer();
+ GrailsWebRequest webRequest = GrailsWebRequest.lookup();
+ try {
+ super.sendError(sc);
+ } finally {
+ if (webRequest != null)
+ WebUtils.storeGrailsWebRequest(webRequest);
+ }
+ }
+
+ @Override
+ public void sendError(int sc, String msg) throws IOException {
+ clearBuffer();
+ GrailsWebRequest webRequest = GrailsWebRequest.lookup();
+ try {
+ super.sendError(sc, msg);
+ } finally {
+ if (webRequest != null)
Review Comment:
Align on curly braces for `if` blocks?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GrailsPageResponseWrapper.java:
##########
@@ -0,0 +1,390 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
+
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.buffer.GrailsPrintWriterAdapter;
+import org.grails.buffer.StreamByteBuffer;
+import org.grails.buffer.StreamCharBuffer;
+import org.grails.web.util.WebUtils;
+
+import com.opensymphony.module.sitemesh.Page;
+import com.opensymphony.module.sitemesh.PageParser;
+import com.opensymphony.module.sitemesh.PageParserSelector;
+import com.opensymphony.module.sitemesh.filter.HttpContentType;
+import com.opensymphony.module.sitemesh.filter.RoutableServletOutputStream;
+import com.opensymphony.module.sitemesh.filter.TextEncoder;
+
+/**
+ * @author Graeme Rocher
+ * @since 1.0.4
+ */
+public class GrailsPageResponseWrapper extends HttpServletResponseWrapper {
+
+ private final GrailsRoutablePrintWriter routablePrintWriter;
+ private final RoutableServletOutputStream routableServletOutputStream;
+ private final PageParserSelector parserSelector;
+ private final HttpServletRequest request;
+
+ private GrailsBuffer buffer;
+ private boolean parseablePage = false;
+ private GSPGrailsLayoutPage gspGrailsLayoutPage;
+
+ public GrailsPageResponseWrapper(final HttpServletRequest request, final HttpServletResponse response,
+ PageParserSelector parserSelector) {
+ super(response);
+ this.parserSelector = parserSelector;
+
+ routablePrintWriter = GrailsRoutablePrintWriter.newInstance(new GrailsRoutablePrintWriter.DestinationFactory() {
+ public PrintWriter activateDestination() throws IOException {
+ return response.getWriter();
+ }
+ });
+ routableServletOutputStream = new RoutableServletOutputStream(new RoutableServletOutputStream.DestinationFactory() {
+ public ServletOutputStream create() throws IOException {
+ return response.getOutputStream();
+ }
+ });
+
+ this.request = request;
+
+ gspGrailsLayoutPage = (GSPGrailsLayoutPage) request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE);
+
+ applyContentType(response.getContentType());
+ }
+
+ @Override
+ public void sendError(int sc) throws IOException {
+ clearBuffer();
+ GrailsWebRequest webRequest = GrailsWebRequest.lookup();
+ try {
+ super.sendError(sc);
+ } finally {
+ if (webRequest != null)
Review Comment:
Align on curly braces for `if` blocks?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GrailsPageResponseWrapper.java:
##########
@@ -0,0 +1,390 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
+
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.buffer.GrailsPrintWriterAdapter;
+import org.grails.buffer.StreamByteBuffer;
+import org.grails.buffer.StreamCharBuffer;
+import org.grails.web.util.WebUtils;
+
+import com.opensymphony.module.sitemesh.Page;
+import com.opensymphony.module.sitemesh.PageParser;
+import com.opensymphony.module.sitemesh.PageParserSelector;
+import com.opensymphony.module.sitemesh.filter.HttpContentType;
+import com.opensymphony.module.sitemesh.filter.RoutableServletOutputStream;
+import com.opensymphony.module.sitemesh.filter.TextEncoder;
+
+/**
+ * @author Graeme Rocher
+ * @since 1.0.4
+ */
+public class GrailsPageResponseWrapper extends HttpServletResponseWrapper {
+
+ private final GrailsRoutablePrintWriter routablePrintWriter;
+ private final RoutableServletOutputStream routableServletOutputStream;
+ private final PageParserSelector parserSelector;
+ private final HttpServletRequest request;
+
+ private GrailsBuffer buffer;
+ private boolean parseablePage = false;
+ private GSPGrailsLayoutPage gspGrailsLayoutPage;
+
+ public GrailsPageResponseWrapper(final HttpServletRequest request, final HttpServletResponse response,
+ PageParserSelector parserSelector) {
+ super(response);
+ this.parserSelector = parserSelector;
+
+ routablePrintWriter = GrailsRoutablePrintWriter.newInstance(new GrailsRoutablePrintWriter.DestinationFactory() {
+ public PrintWriter activateDestination() throws IOException {
+ return response.getWriter();
+ }
+ });
+ routableServletOutputStream = new RoutableServletOutputStream(new RoutableServletOutputStream.DestinationFactory() {
+ public ServletOutputStream create() throws IOException {
+ return response.getOutputStream();
+ }
+ });
+
+ this.request = request;
+
+ gspGrailsLayoutPage = (GSPGrailsLayoutPage) request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE);
+
+ applyContentType(response.getContentType());
+ }
+
+ @Override
+ public void sendError(int sc) throws IOException {
+ clearBuffer();
+ GrailsWebRequest webRequest = GrailsWebRequest.lookup();
+ try {
+ super.sendError(sc);
+ } finally {
+ if (webRequest != null)
+ WebUtils.storeGrailsWebRequest(webRequest);
+ }
+ }
+
+ @Override
+ public void sendError(int sc, String msg) throws IOException {
+ clearBuffer();
+ GrailsWebRequest webRequest = GrailsWebRequest.lookup();
+ try {
+ super.sendError(sc, msg);
+ } finally {
+ if (webRequest != null)
+ WebUtils.storeGrailsWebRequest(webRequest);
+ }
+ }
+
+ /**
+ * Set the content-type of the request and store it so it can
+ * be passed to the {@link com.opensymphony.module.sitemesh.PageParser}.
+ */
+ @Override
+ public void setContentType(String type) {
+ super.setContentType(type);
+
+ applyContentType(type);
+ }
+
+ protected void applyContentType(String type) {
+ if (type == null) {
+ return;
+ }
+
+ HttpContentType httpContentType = new HttpContentType(type);
+
+ if (parserSelector.shouldParsePage(httpContentType.getType())) {
+ activateGrailsLayout(httpContentType.getType(), httpContentType.getEncoding());
+ } else {
+ deactivateGrailsLayout();
+ }
+ }
+
+ public void activateGrailsLayout(String contentType, String encoding) {
+ if (parseablePage) {
+ return; // already activated
+ }
+
+ buffer = new GrailsBuffer(parserSelector, contentType, encoding, gspGrailsLayoutPage);
+ routablePrintWriter.updateDestination(new GrailsRoutablePrintWriter.DestinationFactory() {
+ public PrintWriter activateDestination() {
+ return buffer.getWriter();
+ }
+ });
+ routablePrintWriter.blockFlushAndClose();
+ routableServletOutputStream.updateDestination(new RoutableServletOutputStream.DestinationFactory() {
+ public ServletOutputStream create() {
+ return buffer.getOutputStream();
+ }
+ });
+ parseablePage = true;
+ }
+
+ public void deactivateGrailsLayout() {
+ parseablePage = false;
+ buffer = null;
+ if (gspGrailsLayoutPage != null) {
+ gspGrailsLayoutPage.reset();
+ }
+ routablePrintWriter.updateDestination(new GrailsRoutablePrintWriter.DestinationFactory() {
+ public PrintWriter activateDestination() throws IOException {
+ return getResponse().getWriter();
+ }
+ });
+ routablePrintWriter.unBlockFlushAndClose();
+ routableServletOutputStream.updateDestination(new RoutableServletOutputStream.DestinationFactory() {
+ public ServletOutputStream create() throws IOException {
+ return getResponse().getOutputStream();
+ }
+ });
+ }
+
+ /**
+ * Prevent content-length being set if page is parseable.
+ */
+ @Override
+ public void setContentLength(int contentLength) {
+ if (!parseablePage) super.setContentLength(contentLength);
+ }
+
+ /**
+ * Prevent buffer from being flushed if this is a page being parsed.
+ */
+ @Override
+ public void flushBuffer() throws IOException {
+ if (!parseablePage) super.flushBuffer();
+ }
+
+ /**
+ * Prevent content-length being set if page is parseable.
+ */
+ @Override
+ public void setHeader(String name, String value) {
+ if (name.toLowerCase().equals("content-type")) { // ensure ContentType is always set through setContentType()
+ setContentType(value);
+ } else if (!parseablePage || !name.toLowerCase().equals("content-length")) {
Review Comment:
Use `equalsIgnoreCase`?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GrailsLayoutRenderViewMutator.groovy:
##########
@@ -0,0 +1,50 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout
+
+import grails.web.pages.GrailsRenderViewMutator
+import org.springframework.beans.factory.NoSuchBeanDefinitionException
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.web.servlet.View
+
+class GrailsLayoutRenderViewMutator implements GrailsRenderViewMutator {
+ @Autowired
Review Comment:
Align on blank line after class definition?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GrailsPageResponseWrapper.java:
##########
@@ -0,0 +1,390 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
+
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.buffer.GrailsPrintWriterAdapter;
+import org.grails.buffer.StreamByteBuffer;
+import org.grails.buffer.StreamCharBuffer;
+import org.grails.web.util.WebUtils;
+
+import com.opensymphony.module.sitemesh.Page;
+import com.opensymphony.module.sitemesh.PageParser;
+import com.opensymphony.module.sitemesh.PageParserSelector;
+import com.opensymphony.module.sitemesh.filter.HttpContentType;
+import com.opensymphony.module.sitemesh.filter.RoutableServletOutputStream;
+import com.opensymphony.module.sitemesh.filter.TextEncoder;
+
+/**
+ * @author Graeme Rocher
+ * @since 1.0.4
+ */
+public class GrailsPageResponseWrapper extends HttpServletResponseWrapper {
+
+ private final GrailsRoutablePrintWriter routablePrintWriter;
+ private final RoutableServletOutputStream routableServletOutputStream;
+ private final PageParserSelector parserSelector;
+ private final HttpServletRequest request;
+
+ private GrailsBuffer buffer;
+ private boolean parseablePage = false;
+ private GSPGrailsLayoutPage gspGrailsLayoutPage;
+
+ public GrailsPageResponseWrapper(final HttpServletRequest request, final HttpServletResponse response,
+ PageParserSelector parserSelector) {
+ super(response);
+ this.parserSelector = parserSelector;
+
+ routablePrintWriter = GrailsRoutablePrintWriter.newInstance(new GrailsRoutablePrintWriter.DestinationFactory() {
+ public PrintWriter activateDestination() throws IOException {
+ return response.getWriter();
+ }
+ });
+ routableServletOutputStream = new RoutableServletOutputStream(new RoutableServletOutputStream.DestinationFactory() {
+ public ServletOutputStream create() throws IOException {
+ return response.getOutputStream();
+ }
+ });
+
+ this.request = request;
+
+ gspGrailsLayoutPage = (GSPGrailsLayoutPage) request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE);
+
+ applyContentType(response.getContentType());
+ }
+
+ @Override
+ public void sendError(int sc) throws IOException {
+ clearBuffer();
+ GrailsWebRequest webRequest = GrailsWebRequest.lookup();
+ try {
+ super.sendError(sc);
+ } finally {
+ if (webRequest != null)
+ WebUtils.storeGrailsWebRequest(webRequest);
+ }
+ }
+
+ @Override
+ public void sendError(int sc, String msg) throws IOException {
+ clearBuffer();
+ GrailsWebRequest webRequest = GrailsWebRequest.lookup();
+ try {
+ super.sendError(sc, msg);
+ } finally {
+ if (webRequest != null)
+ WebUtils.storeGrailsWebRequest(webRequest);
+ }
+ }
+
+ /**
+ * Set the content-type of the request and store it so it can
+ * be passed to the {@link com.opensymphony.module.sitemesh.PageParser}.
+ */
+ @Override
+ public void setContentType(String type) {
+ super.setContentType(type);
+
+ applyContentType(type);
+ }
+
+ protected void applyContentType(String type) {
+ if (type == null) {
+ return;
+ }
+
+ HttpContentType httpContentType = new HttpContentType(type);
+
+ if (parserSelector.shouldParsePage(httpContentType.getType())) {
+ activateGrailsLayout(httpContentType.getType(), httpContentType.getEncoding());
+ } else {
+ deactivateGrailsLayout();
+ }
+ }
+
+ public void activateGrailsLayout(String contentType, String encoding) {
+ if (parseablePage) {
+ return; // already activated
+ }
+
+ buffer = new GrailsBuffer(parserSelector, contentType, encoding, gspGrailsLayoutPage);
+ routablePrintWriter.updateDestination(new GrailsRoutablePrintWriter.DestinationFactory() {
+ public PrintWriter activateDestination() {
+ return buffer.getWriter();
+ }
+ });
+ routablePrintWriter.blockFlushAndClose();
+ routableServletOutputStream.updateDestination(new RoutableServletOutputStream.DestinationFactory() {
+ public ServletOutputStream create() {
+ return buffer.getOutputStream();
+ }
+ });
+ parseablePage = true;
+ }
+
+ public void deactivateGrailsLayout() {
+ parseablePage = false;
+ buffer = null;
+ if (gspGrailsLayoutPage != null) {
+ gspGrailsLayoutPage.reset();
+ }
+ routablePrintWriter.updateDestination(new GrailsRoutablePrintWriter.DestinationFactory() {
+ public PrintWriter activateDestination() throws IOException {
+ return getResponse().getWriter();
+ }
+ });
+ routablePrintWriter.unBlockFlushAndClose();
+ routableServletOutputStream.updateDestination(new RoutableServletOutputStream.DestinationFactory() {
+ public ServletOutputStream create() throws IOException {
+ return getResponse().getOutputStream();
+ }
+ });
+ }
+
+ /**
+ * Prevent content-length being set if page is parseable.
+ */
+ @Override
+ public void setContentLength(int contentLength) {
+ if (!parseablePage) super.setContentLength(contentLength);
+ }
+
+ /**
+ * Prevent buffer from being flushed if this is a page being parsed.
+ */
+ @Override
+ public void flushBuffer() throws IOException {
+ if (!parseablePage) super.flushBuffer();
+ }
+
+ /**
+ * Prevent content-length being set if page is parseable.
+ */
+ @Override
+ public void setHeader(String name, String value) {
+ if (name.toLowerCase().equals("content-type")) { // ensure ContentType is always set through setContentType()
Review Comment:
Use `equalsIgnoreCase`?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GrailsPageResponseWrapper.java:
##########
@@ -0,0 +1,390 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
+
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.buffer.GrailsPrintWriterAdapter;
+import org.grails.buffer.StreamByteBuffer;
+import org.grails.buffer.StreamCharBuffer;
+import org.grails.web.util.WebUtils;
+
+import com.opensymphony.module.sitemesh.Page;
+import com.opensymphony.module.sitemesh.PageParser;
+import com.opensymphony.module.sitemesh.PageParserSelector;
+import com.opensymphony.module.sitemesh.filter.HttpContentType;
+import com.opensymphony.module.sitemesh.filter.RoutableServletOutputStream;
+import com.opensymphony.module.sitemesh.filter.TextEncoder;
+
+/**
+ * @author Graeme Rocher
+ * @since 1.0.4
+ */
+public class GrailsPageResponseWrapper extends HttpServletResponseWrapper {
+
+ private final GrailsRoutablePrintWriter routablePrintWriter;
+ private final RoutableServletOutputStream routableServletOutputStream;
+ private final PageParserSelector parserSelector;
+ private final HttpServletRequest request;
+
+ private GrailsBuffer buffer;
+ private boolean parseablePage = false;
+ private GSPGrailsLayoutPage gspGrailsLayoutPage;
+
+ public GrailsPageResponseWrapper(final HttpServletRequest request, final HttpServletResponse response,
+ PageParserSelector parserSelector) {
+ super(response);
+ this.parserSelector = parserSelector;
+
+ routablePrintWriter = GrailsRoutablePrintWriter.newInstance(new GrailsRoutablePrintWriter.DestinationFactory() {
+ public PrintWriter activateDestination() throws IOException {
+ return response.getWriter();
+ }
+ });
+ routableServletOutputStream = new RoutableServletOutputStream(new RoutableServletOutputStream.DestinationFactory() {
+ public ServletOutputStream create() throws IOException {
+ return response.getOutputStream();
+ }
+ });
+
+ this.request = request;
+
+ gspGrailsLayoutPage = (GSPGrailsLayoutPage) request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE);
+
+ applyContentType(response.getContentType());
+ }
+
+ @Override
+ public void sendError(int sc) throws IOException {
+ clearBuffer();
+ GrailsWebRequest webRequest = GrailsWebRequest.lookup();
+ try {
+ super.sendError(sc);
+ } finally {
+ if (webRequest != null)
+ WebUtils.storeGrailsWebRequest(webRequest);
+ }
+ }
+
+ @Override
+ public void sendError(int sc, String msg) throws IOException {
+ clearBuffer();
+ GrailsWebRequest webRequest = GrailsWebRequest.lookup();
+ try {
+ super.sendError(sc, msg);
+ } finally {
+ if (webRequest != null)
+ WebUtils.storeGrailsWebRequest(webRequest);
+ }
+ }
+
+ /**
+ * Set the content-type of the request and store it so it can
+ * be passed to the {@link com.opensymphony.module.sitemesh.PageParser}.
+ */
+ @Override
+ public void setContentType(String type) {
+ super.setContentType(type);
+
+ applyContentType(type);
+ }
+
+ protected void applyContentType(String type) {
+ if (type == null) {
+ return;
+ }
+
+ HttpContentType httpContentType = new HttpContentType(type);
+
+ if (parserSelector.shouldParsePage(httpContentType.getType())) {
+ activateGrailsLayout(httpContentType.getType(), httpContentType.getEncoding());
+ } else {
+ deactivateGrailsLayout();
+ }
+ }
+
+ public void activateGrailsLayout(String contentType, String encoding) {
+ if (parseablePage) {
+ return; // already activated
+ }
+
+ buffer = new GrailsBuffer(parserSelector, contentType, encoding, gspGrailsLayoutPage);
+ routablePrintWriter.updateDestination(new GrailsRoutablePrintWriter.DestinationFactory() {
+ public PrintWriter activateDestination() {
+ return buffer.getWriter();
+ }
+ });
+ routablePrintWriter.blockFlushAndClose();
+ routableServletOutputStream.updateDestination(new RoutableServletOutputStream.DestinationFactory() {
+ public ServletOutputStream create() {
+ return buffer.getOutputStream();
+ }
+ });
+ parseablePage = true;
+ }
+
+ public void deactivateGrailsLayout() {
+ parseablePage = false;
+ buffer = null;
+ if (gspGrailsLayoutPage != null) {
+ gspGrailsLayoutPage.reset();
+ }
+ routablePrintWriter.updateDestination(new GrailsRoutablePrintWriter.DestinationFactory() {
+ public PrintWriter activateDestination() throws IOException {
+ return getResponse().getWriter();
+ }
+ });
+ routablePrintWriter.unBlockFlushAndClose();
+ routableServletOutputStream.updateDestination(new RoutableServletOutputStream.DestinationFactory() {
+ public ServletOutputStream create() throws IOException {
+ return getResponse().getOutputStream();
+ }
+ });
+ }
+
+ /**
+ * Prevent content-length being set if page is parseable.
+ */
+ @Override
+ public void setContentLength(int contentLength) {
+ if (!parseablePage) super.setContentLength(contentLength);
+ }
+
+ /**
+ * Prevent buffer from being flushed if this is a page being parsed.
+ */
+ @Override
+ public void flushBuffer() throws IOException {
+ if (!parseablePage) super.flushBuffer();
+ }
+
+ /**
+ * Prevent content-length being set if page is parseable.
+ */
+ @Override
+ public void setHeader(String name, String value) {
+ if (name.toLowerCase().equals("content-type")) { // ensure ContentType is always set through setContentType()
+ setContentType(value);
+ } else if (!parseablePage || !name.toLowerCase().equals("content-length")) {
+ super.setHeader(name, value);
+ }
+ }
+
+ /**
+ * Prevent content-length being set if page is parseable.
+ */
+ @Override
+ public void addHeader(String name, String value) {
+ if (name.toLowerCase().equals("content-type")) { // ensure ContentType is always set through setContentType()
+ setContentType(value);
+ } else if (!parseablePage || !name.toLowerCase().equals("content-length")) {
Review Comment:
Use `equalsIgnoreCase`?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GrailsLayoutView.java:
##########
@@ -0,0 +1,46 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import java.util.Map;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.springframework.web.servlet.View;
+
+import com.opensymphony.sitemesh.ContentProcessor;
+import com.opensymphony.sitemesh.webapp.SiteMeshWebAppContext;
+
+public class GrailsLayoutView extends EmbeddedGrailsLayoutView {
+ ContentProcessor contentProcessor;
Review Comment:
- `private final`?
- Align on blank line after class definition?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/SpringMVCViewDecorator.java:
##########
@@ -0,0 +1,114 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import groovy.text.Template;
+
+import java.util.Collections;
+import java.util.Map;
+
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.grails.web.util.GrailsApplicationAttributes;
+import org.grails.web.servlet.view.AbstractGrailsView;
+import org.grails.web.util.WebUtils;
+import org.springframework.web.servlet.View;
+import org.springframework.web.servlet.view.AbstractUrlBasedView;
+
+import com.opensymphony.module.sitemesh.HTMLPage;
+import com.opensymphony.module.sitemesh.RequestConstants;
+import com.opensymphony.module.sitemesh.mapper.DefaultDecorator;
+import com.opensymphony.sitemesh.Content;
+import com.opensymphony.sitemesh.SiteMeshContext;
+import com.opensymphony.sitemesh.webapp.SiteMeshWebAppContext;
+
+/**
+ * Encapsulates the logic for rendering a layout.
+ *
+ * @author Graeme Rocher
+ * @since 2.0
+ */
+public class SpringMVCViewDecorator extends DefaultDecorator implements com.opensymphony.sitemesh.Decorator {
+ private View view;
Review Comment:
Align on blank line after class definition?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GrailsPageResponseWrapper.java:
##########
@@ -0,0 +1,390 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
+
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.buffer.GrailsPrintWriterAdapter;
+import org.grails.buffer.StreamByteBuffer;
+import org.grails.buffer.StreamCharBuffer;
+import org.grails.web.util.WebUtils;
+
+import com.opensymphony.module.sitemesh.Page;
+import com.opensymphony.module.sitemesh.PageParser;
+import com.opensymphony.module.sitemesh.PageParserSelector;
+import com.opensymphony.module.sitemesh.filter.HttpContentType;
+import com.opensymphony.module.sitemesh.filter.RoutableServletOutputStream;
+import com.opensymphony.module.sitemesh.filter.TextEncoder;
+
+/**
+ * @author Graeme Rocher
+ * @since 1.0.4
+ */
+public class GrailsPageResponseWrapper extends HttpServletResponseWrapper {
+
+ private final GrailsRoutablePrintWriter routablePrintWriter;
+ private final RoutableServletOutputStream routableServletOutputStream;
+ private final PageParserSelector parserSelector;
+ private final HttpServletRequest request;
+
+ private GrailsBuffer buffer;
+ private boolean parseablePage = false;
+ private GSPGrailsLayoutPage gspGrailsLayoutPage;
+
+ public GrailsPageResponseWrapper(final HttpServletRequest request, final HttpServletResponse response,
+ PageParserSelector parserSelector) {
+ super(response);
+ this.parserSelector = parserSelector;
+
+ routablePrintWriter = GrailsRoutablePrintWriter.newInstance(new GrailsRoutablePrintWriter.DestinationFactory() {
+ public PrintWriter activateDestination() throws IOException {
+ return response.getWriter();
+ }
+ });
+ routableServletOutputStream = new RoutableServletOutputStream(new RoutableServletOutputStream.DestinationFactory() {
+ public ServletOutputStream create() throws IOException {
+ return response.getOutputStream();
+ }
+ });
+
+ this.request = request;
+
+ gspGrailsLayoutPage = (GSPGrailsLayoutPage) request.getAttribute(EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE);
+
+ applyContentType(response.getContentType());
+ }
+
+ @Override
+ public void sendError(int sc) throws IOException {
+ clearBuffer();
+ GrailsWebRequest webRequest = GrailsWebRequest.lookup();
+ try {
+ super.sendError(sc);
+ } finally {
+ if (webRequest != null)
+ WebUtils.storeGrailsWebRequest(webRequest);
+ }
+ }
+
+ @Override
+ public void sendError(int sc, String msg) throws IOException {
+ clearBuffer();
+ GrailsWebRequest webRequest = GrailsWebRequest.lookup();
+ try {
+ super.sendError(sc, msg);
+ } finally {
+ if (webRequest != null)
+ WebUtils.storeGrailsWebRequest(webRequest);
+ }
+ }
+
+ /**
+ * Set the content-type of the request and store it so it can
+ * be passed to the {@link com.opensymphony.module.sitemesh.PageParser}.
+ */
+ @Override
+ public void setContentType(String type) {
+ super.setContentType(type);
+
Review Comment:
Remove blank line?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GrailsViewBufferingResponse.java:
##########
@@ -0,0 +1,58 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import java.io.IOException;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import com.opensymphony.sitemesh.Content;
+import com.opensymphony.sitemesh.ContentProcessor;
+import com.opensymphony.sitemesh.SiteMeshContext;
+import com.opensymphony.sitemesh.webapp.SiteMeshWebAppContext;
+
+public class GrailsViewBufferingResponse extends GrailsContentBufferingResponse {
+ private static class SimpleWebAppContext extends SiteMeshWebAppContext {
Review Comment:
Align on blank line after class definition?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GroovyPageLayoutFinder.java:
##########
@@ -0,0 +1,315 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import grails.util.Environment;
+import grails.util.GrailsNameUtils;
+import grails.web.pages.GroovyPagesUriService;
+import groovy.lang.GroovyObject;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+import grails.util.GrailsClassUtils;
+import grails.util.GrailsStringUtils;
+import org.grails.core.artefact.ControllerArtefactHandler;
+import org.grails.io.support.GrailsResourceUtils;
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.web.util.GrailsApplicationAttributes;
+import org.grails.web.servlet.view.AbstractGrailsView;
+import org.grails.web.servlet.view.GrailsViewResolver;
+import org.grails.web.servlet.view.LayoutViewResolver;
+import org.grails.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.core.Ordered;
+import org.springframework.web.servlet.View;
+import org.springframework.web.servlet.ViewResolver;
+
+import com.opensymphony.module.sitemesh.Decorator;
+import com.opensymphony.module.sitemesh.Page;
+import com.opensymphony.sitemesh.Content;
+
+/**
+ * Provides the logic for GrailsLayoutDecoratorMapper without so many ties to
+ * the Sitemesh API.
+ *
+ * @author Graeme Rocher
+ * @since 2.0
+ */
+public class GroovyPageLayoutFinder implements ApplicationListener<ContextRefreshedEvent>, Ordered {
+ private static final Logger LOG = LoggerFactory.getLogger(GrailsLayoutDecoratorMapper.class);
+ private static final long LAYOUT_CACHE_EXPIRATION_MILLIS = Long.getLong("grails.gsp.reload.interval", 5000);
+ private static final String LAYOUTS_PATH = "/layouts";
+
+ private static final int ORDER = Ordered.LOWEST_PRECEDENCE - 1;
+
+ private Map<String, DecoratorCacheValue> decoratorCache = new ConcurrentHashMap<String, DecoratorCacheValue>();
+ private Map<LayoutCacheKey, DecoratorCacheValue> layoutDecoratorCache = new ConcurrentHashMap<LayoutCacheKey, DecoratorCacheValue>();
+
+ private String defaultDecoratorName;
+ private boolean gspReloadEnabled;
+ private boolean cacheEnabled = (Environment.getCurrent() != Environment.DEVELOPMENT);
+ private ViewResolver viewResolver;
+ private boolean enableNonGspViews = false;
+
+ @Override
+ public int getOrder() {
+ return ORDER;
+ }
+
+ public void setDefaultDecoratorName(String defaultDecoratorName) {
+ this.defaultDecoratorName = defaultDecoratorName;
+ }
+
+ public void setEnableNonGspViews(boolean enableNonGspViews) {
+ this.enableNonGspViews = enableNonGspViews;
+ }
+
+ public void setGspReloadEnabled(boolean gspReloadEnabled) {
+ this.gspReloadEnabled = gspReloadEnabled;
+ }
+
+ public void setCacheEnabled(boolean cacheEnabled) {
+ this.cacheEnabled = cacheEnabled;
+ }
+
+ public void setViewResolver(ViewResolver viewResolver) {
+ if(viewResolver instanceof LayoutViewResolver) {
+ this.viewResolver = ((LayoutViewResolver)viewResolver).getInnerViewResolver();
+ } else {
+ this.viewResolver = viewResolver;
+ }
+ }
+
+ public Decorator findLayout(HttpServletRequest request, Content page) {
+ return findLayout(request, GSPGrailsLayoutPage.content2htmlPage(page));
+ }
+
+ public Decorator findLayout(HttpServletRequest request, Page page) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Evaluating layout for request: " + request.getRequestURI());
Review Comment:
Align on lazy evaluated log messages?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GroovyPageLayoutFinder.java:
##########
@@ -0,0 +1,315 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import grails.util.Environment;
+import grails.util.GrailsNameUtils;
+import grails.web.pages.GroovyPagesUriService;
+import groovy.lang.GroovyObject;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+import grails.util.GrailsClassUtils;
+import grails.util.GrailsStringUtils;
+import org.grails.core.artefact.ControllerArtefactHandler;
+import org.grails.io.support.GrailsResourceUtils;
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.web.util.GrailsApplicationAttributes;
+import org.grails.web.servlet.view.AbstractGrailsView;
+import org.grails.web.servlet.view.GrailsViewResolver;
+import org.grails.web.servlet.view.LayoutViewResolver;
+import org.grails.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.core.Ordered;
+import org.springframework.web.servlet.View;
+import org.springframework.web.servlet.ViewResolver;
+
+import com.opensymphony.module.sitemesh.Decorator;
+import com.opensymphony.module.sitemesh.Page;
+import com.opensymphony.sitemesh.Content;
+
+/**
+ * Provides the logic for GrailsLayoutDecoratorMapper without so many ties to
+ * the Sitemesh API.
+ *
+ * @author Graeme Rocher
+ * @since 2.0
+ */
+public class GroovyPageLayoutFinder implements ApplicationListener<ContextRefreshedEvent>, Ordered {
+ private static final Logger LOG = LoggerFactory.getLogger(GrailsLayoutDecoratorMapper.class);
+ private static final long LAYOUT_CACHE_EXPIRATION_MILLIS = Long.getLong("grails.gsp.reload.interval", 5000);
+ private static final String LAYOUTS_PATH = "/layouts";
+
+ private static final int ORDER = Ordered.LOWEST_PRECEDENCE - 1;
+
+ private Map<String, DecoratorCacheValue> decoratorCache = new ConcurrentHashMap<String, DecoratorCacheValue>();
+ private Map<LayoutCacheKey, DecoratorCacheValue> layoutDecoratorCache = new ConcurrentHashMap<LayoutCacheKey, DecoratorCacheValue>();
+
+ private String defaultDecoratorName;
+ private boolean gspReloadEnabled;
+ private boolean cacheEnabled = (Environment.getCurrent() != Environment.DEVELOPMENT);
+ private ViewResolver viewResolver;
+ private boolean enableNonGspViews = false;
+
+ @Override
+ public int getOrder() {
+ return ORDER;
+ }
+
+ public void setDefaultDecoratorName(String defaultDecoratorName) {
+ this.defaultDecoratorName = defaultDecoratorName;
+ }
+
+ public void setEnableNonGspViews(boolean enableNonGspViews) {
+ this.enableNonGspViews = enableNonGspViews;
+ }
+
+ public void setGspReloadEnabled(boolean gspReloadEnabled) {
+ this.gspReloadEnabled = gspReloadEnabled;
+ }
+
+ public void setCacheEnabled(boolean cacheEnabled) {
+ this.cacheEnabled = cacheEnabled;
+ }
+
+ public void setViewResolver(ViewResolver viewResolver) {
+ if(viewResolver instanceof LayoutViewResolver) {
+ this.viewResolver = ((LayoutViewResolver)viewResolver).getInnerViewResolver();
+ } else {
+ this.viewResolver = viewResolver;
+ }
+ }
+
+ public Decorator findLayout(HttpServletRequest request, Content page) {
+ return findLayout(request, GSPGrailsLayoutPage.content2htmlPage(page));
+ }
+
+ public Decorator findLayout(HttpServletRequest request, Page page) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Evaluating layout for request: " + request.getRequestURI());
+ }
+ final Object layoutAttribute = request.getAttribute(WebUtils.LAYOUT_ATTRIBUTE);
+ if (request.getAttribute(GroovyPagesUriService.RENDERING_VIEW_ATTRIBUTE) != null || layoutAttribute != null) {
+ String layoutName = layoutAttribute == null ? null : layoutAttribute.toString();
+
+ if (layoutName == null) {
+ layoutName = page.getProperty("meta.layout");
+ }
+
+ Decorator d = null;
+
+ if (GrailsStringUtils.isBlank(layoutName)) {
+ GroovyObject controller = (GroovyObject)request.getAttribute(GrailsApplicationAttributes.CONTROLLER);
+ if (controller != null ) {
+ GrailsWebRequest webRequest = GrailsWebRequest.lookup(request);
+ String controllerName = webRequest.getControllerName();
+ if(controllerName == null) {
+ controllerName = GrailsNameUtils.getLogicalPropertyName(controller.getClass().getName(), ControllerArtefactHandler.TYPE);
+ }
+ String actionUri = webRequest.getAttributes().getControllerActionUri(request);
+
+ if(controllerName != null && actionUri != null) {
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Found controller in request, locating layout for controller [" + controllerName
+ + "] and action [" + actionUri + "]");
+ }
+
+ LayoutCacheKey cacheKey = null;
+ boolean cachedIsNull = false;
+
+ if (cacheEnabled) {
+ cacheKey = new LayoutCacheKey(controllerName, actionUri);
+ DecoratorCacheValue cacheValue = layoutDecoratorCache.get(cacheKey);
+ if (cacheValue != null && (!gspReloadEnabled || !cacheValue.isExpired())) {
+ d = cacheValue.getDecorator();
+ if (d == null) {
+ cachedIsNull = true;
+ }
+ }
+ }
+
+ if (d == null && !cachedIsNull) {
+ d = resolveDecorator(request, controller, controllerName, actionUri);
+ if (cacheEnabled) {
+ if(LOG.isDebugEnabled() && d != null) {
+ LOG.debug("Caching resolved layout {} for controller {} and action {}",d.getPage(), controllerName, actionUri);
+ }
+ layoutDecoratorCache.put(cacheKey, new DecoratorCacheValue(d));
+ }
+ }
+ }
+ }
+ else {
+ d = getApplicationDefaultDecorator(request);
+ }
+ }
+ else {
+ d = getNamedDecorator(request, layoutName);
+ }
+
+ if (d != null) {
+ return d;
+ }
+ }
+ return null;
+ }
+
+ protected Decorator getApplicationDefaultDecorator(HttpServletRequest request) {
+ return getNamedDecorator(request, defaultDecoratorName == null ? "application" : defaultDecoratorName,
+ !enableNonGspViews || defaultDecoratorName == null);
+ }
+
+ public Decorator getNamedDecorator(HttpServletRequest request, String name) {
+ return getNamedDecorator(request, name, !enableNonGspViews);
+ }
+
+ public Decorator getNamedDecorator(HttpServletRequest request, String name, boolean viewMustExist) {
+ if (GrailsStringUtils.isBlank(name) || WebUtils.NONE_LAYOUT.equals(name)) {
+ return null;
+ }
+
+ if (cacheEnabled) {
+ DecoratorCacheValue cacheValue = decoratorCache.get(name);
+ if (cacheValue != null && (!gspReloadEnabled || !cacheValue.isExpired())) {
+ return cacheValue.getDecorator();
+ }
+ }
+
+ View view;
+ try {
+ view = viewResolver.resolveViewName(GrailsResourceUtils.cleanPath(GrailsResourceUtils.appendPiecesForUri(LAYOUTS_PATH, name)),
+ request.getLocale());
+ // it's only possible to check that GroovyPageView exists
+ if (viewMustExist && !(view instanceof AbstractGrailsView)) {
+ view = null;
+ }
+ }
+ catch (Exception e) {
+ throw new RuntimeException("Unable to resolve view", e);
+ }
+
+ Decorator d = null;
+ if (view != null) {
+ d = createDecorator(name, view);
+ }
+
+ if (cacheEnabled) {
+ decoratorCache.put(name, new DecoratorCacheValue(d));
+ }
+ return d;
+ }
+
+ private Decorator resolveDecorator(HttpServletRequest request, GroovyObject controller, String controllerName,
+ String actionUri) {
+ Decorator d = null;
+
+ Object layoutProperty = GrailsClassUtils.getStaticPropertyValue(controller.getClass(), "layout");
+ if (layoutProperty instanceof CharSequence) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("layout property found in controller, looking for template named " + layoutProperty);
Review Comment:
Align on lazy evaluated log messages?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GroovyPageLayoutFinder.java:
##########
@@ -0,0 +1,315 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import grails.util.Environment;
+import grails.util.GrailsNameUtils;
+import grails.web.pages.GroovyPagesUriService;
+import groovy.lang.GroovyObject;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+import grails.util.GrailsClassUtils;
+import grails.util.GrailsStringUtils;
+import org.grails.core.artefact.ControllerArtefactHandler;
+import org.grails.io.support.GrailsResourceUtils;
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.web.util.GrailsApplicationAttributes;
+import org.grails.web.servlet.view.AbstractGrailsView;
+import org.grails.web.servlet.view.GrailsViewResolver;
+import org.grails.web.servlet.view.LayoutViewResolver;
+import org.grails.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.core.Ordered;
+import org.springframework.web.servlet.View;
+import org.springframework.web.servlet.ViewResolver;
+
+import com.opensymphony.module.sitemesh.Decorator;
+import com.opensymphony.module.sitemesh.Page;
+import com.opensymphony.sitemesh.Content;
+
+/**
+ * Provides the logic for GrailsLayoutDecoratorMapper without so many ties to
+ * the Sitemesh API.
+ *
+ * @author Graeme Rocher
+ * @since 2.0
+ */
+public class GroovyPageLayoutFinder implements ApplicationListener<ContextRefreshedEvent>, Ordered {
+ private static final Logger LOG = LoggerFactory.getLogger(GrailsLayoutDecoratorMapper.class);
+ private static final long LAYOUT_CACHE_EXPIRATION_MILLIS = Long.getLong("grails.gsp.reload.interval", 5000);
+ private static final String LAYOUTS_PATH = "/layouts";
+
+ private static final int ORDER = Ordered.LOWEST_PRECEDENCE - 1;
+
+ private Map<String, DecoratorCacheValue> decoratorCache = new ConcurrentHashMap<String, DecoratorCacheValue>();
+ private Map<LayoutCacheKey, DecoratorCacheValue> layoutDecoratorCache = new ConcurrentHashMap<LayoutCacheKey, DecoratorCacheValue>();
+
+ private String defaultDecoratorName;
+ private boolean gspReloadEnabled;
+ private boolean cacheEnabled = (Environment.getCurrent() != Environment.DEVELOPMENT);
+ private ViewResolver viewResolver;
+ private boolean enableNonGspViews = false;
+
+ @Override
+ public int getOrder() {
+ return ORDER;
+ }
+
+ public void setDefaultDecoratorName(String defaultDecoratorName) {
+ this.defaultDecoratorName = defaultDecoratorName;
+ }
+
+ public void setEnableNonGspViews(boolean enableNonGspViews) {
+ this.enableNonGspViews = enableNonGspViews;
+ }
+
+ public void setGspReloadEnabled(boolean gspReloadEnabled) {
+ this.gspReloadEnabled = gspReloadEnabled;
+ }
+
+ public void setCacheEnabled(boolean cacheEnabled) {
+ this.cacheEnabled = cacheEnabled;
+ }
+
+ public void setViewResolver(ViewResolver viewResolver) {
+ if(viewResolver instanceof LayoutViewResolver) {
+ this.viewResolver = ((LayoutViewResolver)viewResolver).getInnerViewResolver();
+ } else {
+ this.viewResolver = viewResolver;
+ }
+ }
+
+ public Decorator findLayout(HttpServletRequest request, Content page) {
+ return findLayout(request, GSPGrailsLayoutPage.content2htmlPage(page));
+ }
+
+ public Decorator findLayout(HttpServletRequest request, Page page) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Evaluating layout for request: " + request.getRequestURI());
+ }
+ final Object layoutAttribute = request.getAttribute(WebUtils.LAYOUT_ATTRIBUTE);
+ if (request.getAttribute(GroovyPagesUriService.RENDERING_VIEW_ATTRIBUTE) != null || layoutAttribute != null) {
+ String layoutName = layoutAttribute == null ? null : layoutAttribute.toString();
+
+ if (layoutName == null) {
+ layoutName = page.getProperty("meta.layout");
+ }
+
+ Decorator d = null;
+
+ if (GrailsStringUtils.isBlank(layoutName)) {
+ GroovyObject controller = (GroovyObject)request.getAttribute(GrailsApplicationAttributes.CONTROLLER);
+ if (controller != null ) {
+ GrailsWebRequest webRequest = GrailsWebRequest.lookup(request);
+ String controllerName = webRequest.getControllerName();
+ if(controllerName == null) {
+ controllerName = GrailsNameUtils.getLogicalPropertyName(controller.getClass().getName(), ControllerArtefactHandler.TYPE);
+ }
+ String actionUri = webRequest.getAttributes().getControllerActionUri(request);
+
+ if(controllerName != null && actionUri != null) {
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Found controller in request, locating layout for controller [" + controllerName
+ + "] and action [" + actionUri + "]");
+ }
+
+ LayoutCacheKey cacheKey = null;
+ boolean cachedIsNull = false;
+
+ if (cacheEnabled) {
+ cacheKey = new LayoutCacheKey(controllerName, actionUri);
+ DecoratorCacheValue cacheValue = layoutDecoratorCache.get(cacheKey);
+ if (cacheValue != null && (!gspReloadEnabled || !cacheValue.isExpired())) {
+ d = cacheValue.getDecorator();
+ if (d == null) {
+ cachedIsNull = true;
+ }
+ }
+ }
+
+ if (d == null && !cachedIsNull) {
+ d = resolveDecorator(request, controller, controllerName, actionUri);
+ if (cacheEnabled) {
+ if(LOG.isDebugEnabled() && d != null) {
+ LOG.debug("Caching resolved layout {} for controller {} and action {}",d.getPage(), controllerName, actionUri);
Review Comment:
Spacing?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/GroovyPageLayoutFinder.java:
##########
@@ -0,0 +1,315 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout;
+
+import grails.util.Environment;
+import grails.util.GrailsNameUtils;
+import grails.web.pages.GroovyPagesUriService;
+import groovy.lang.GroovyObject;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+import grails.util.GrailsClassUtils;
+import grails.util.GrailsStringUtils;
+import org.grails.core.artefact.ControllerArtefactHandler;
+import org.grails.io.support.GrailsResourceUtils;
+import org.grails.web.servlet.mvc.GrailsWebRequest;
+import org.grails.web.util.GrailsApplicationAttributes;
+import org.grails.web.servlet.view.AbstractGrailsView;
+import org.grails.web.servlet.view.GrailsViewResolver;
+import org.grails.web.servlet.view.LayoutViewResolver;
+import org.grails.web.util.WebUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.core.Ordered;
+import org.springframework.web.servlet.View;
+import org.springframework.web.servlet.ViewResolver;
+
+import com.opensymphony.module.sitemesh.Decorator;
+import com.opensymphony.module.sitemesh.Page;
+import com.opensymphony.sitemesh.Content;
+
+/**
+ * Provides the logic for GrailsLayoutDecoratorMapper without so many ties to
+ * the Sitemesh API.
+ *
+ * @author Graeme Rocher
+ * @since 2.0
+ */
+public class GroovyPageLayoutFinder implements ApplicationListener<ContextRefreshedEvent>, Ordered {
+ private static final Logger LOG = LoggerFactory.getLogger(GrailsLayoutDecoratorMapper.class);
+ private static final long LAYOUT_CACHE_EXPIRATION_MILLIS = Long.getLong("grails.gsp.reload.interval", 5000);
+ private static final String LAYOUTS_PATH = "/layouts";
+
+ private static final int ORDER = Ordered.LOWEST_PRECEDENCE - 1;
+
+ private Map<String, DecoratorCacheValue> decoratorCache = new ConcurrentHashMap<String, DecoratorCacheValue>();
+ private Map<LayoutCacheKey, DecoratorCacheValue> layoutDecoratorCache = new ConcurrentHashMap<LayoutCacheKey, DecoratorCacheValue>();
+
+ private String defaultDecoratorName;
+ private boolean gspReloadEnabled;
+ private boolean cacheEnabled = (Environment.getCurrent() != Environment.DEVELOPMENT);
+ private ViewResolver viewResolver;
+ private boolean enableNonGspViews = false;
+
+ @Override
+ public int getOrder() {
+ return ORDER;
+ }
+
+ public void setDefaultDecoratorName(String defaultDecoratorName) {
+ this.defaultDecoratorName = defaultDecoratorName;
+ }
+
+ public void setEnableNonGspViews(boolean enableNonGspViews) {
+ this.enableNonGspViews = enableNonGspViews;
+ }
+
+ public void setGspReloadEnabled(boolean gspReloadEnabled) {
+ this.gspReloadEnabled = gspReloadEnabled;
+ }
+
+ public void setCacheEnabled(boolean cacheEnabled) {
+ this.cacheEnabled = cacheEnabled;
+ }
+
+ public void setViewResolver(ViewResolver viewResolver) {
+ if(viewResolver instanceof LayoutViewResolver) {
+ this.viewResolver = ((LayoutViewResolver)viewResolver).getInnerViewResolver();
+ } else {
+ this.viewResolver = viewResolver;
+ }
+ }
+
+ public Decorator findLayout(HttpServletRequest request, Content page) {
+ return findLayout(request, GSPGrailsLayoutPage.content2htmlPage(page));
+ }
+
+ public Decorator findLayout(HttpServletRequest request, Page page) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Evaluating layout for request: " + request.getRequestURI());
+ }
+ final Object layoutAttribute = request.getAttribute(WebUtils.LAYOUT_ATTRIBUTE);
+ if (request.getAttribute(GroovyPagesUriService.RENDERING_VIEW_ATTRIBUTE) != null || layoutAttribute != null) {
+ String layoutName = layoutAttribute == null ? null : layoutAttribute.toString();
+
+ if (layoutName == null) {
+ layoutName = page.getProperty("meta.layout");
+ }
+
+ Decorator d = null;
+
+ if (GrailsStringUtils.isBlank(layoutName)) {
+ GroovyObject controller = (GroovyObject)request.getAttribute(GrailsApplicationAttributes.CONTROLLER);
+ if (controller != null ) {
+ GrailsWebRequest webRequest = GrailsWebRequest.lookup(request);
+ String controllerName = webRequest.getControllerName();
+ if(controllerName == null) {
+ controllerName = GrailsNameUtils.getLogicalPropertyName(controller.getClass().getName(), ControllerArtefactHandler.TYPE);
+ }
+ String actionUri = webRequest.getAttributes().getControllerActionUri(request);
+
+ if(controllerName != null && actionUri != null) {
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Found controller in request, locating layout for controller [" + controllerName
Review Comment:
Align on lazy evaluated log messages?
##########
grails-gsp/grails-web-layout/src/main/groovy/org/apache/grails/web/layout/LayoutSelector.groovy:
##########
@@ -0,0 +1,30 @@
+/*
+ * 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
+ *
+ * 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.apache.grails.web.layout
+
+import grails.web.pages.GrailsLayoutSelector
+import org.grails.web.util.WebUtils
+
+class LayoutSelector implements GrailsLayoutSelector {
+ @Override
Review Comment:
Align on blank line after class definition?
##########
grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/GrailsLayoutViewResolverPostProcessor.groovy:
##########
Review Comment:
- Align on single quotes for strings?
- Align on space after `if`?
##########
grails-gsp/plugin/src/test/groovy/org/apache/grails/views/gsp/layout/GSPGrailsLayoutPageTests.groovy:
##########
Review Comment:
Align on parentheses for method calls?
##########
grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/GrailsLayoutViewResolverPostProcessor.groovy:
##########
@@ -0,0 +1,83 @@
+/*
+ * 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
+ *
+ * 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.plugins.web
+
+
+import org.grails.web.servlet.view.GrailsLayoutViewResolver
+import org.springframework.beans.BeansException
+import org.springframework.beans.MutablePropertyValues
+import org.springframework.beans.factory.config.BeanDefinition
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory
+import org.springframework.beans.factory.config.RuntimeBeanReference
+import org.springframework.beans.factory.support.BeanDefinitionRegistry
+import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor
+import org.springframework.beans.factory.support.GenericBeanDefinition
+import org.springframework.core.Ordered
+
+/**
+ * This BeanDefinitionRegistryPostProcessor replaces the existing jspViewResolver bean with EmbeddedGrailsLayoutViewResolver
+ * and moves the previous jspViewResolver bean configuration as an inner bean of EmbeddedGrailsLayoutViewResolver to be used as
+ * the innerViewResolver of it.
+ *
+ * Scaffolding plugin replaces jspViewResolver with it's own implementation and this solution makes it easier to customize
+ * the inner view resolver.
+ *
+ *
+ * @author Lari Hotari
+ * @since 2.4.0
+ * @see org.grails.web.servlet.view.EmbeddedGrailsLayoutViewResolver
+ *
+ */
+class GrailsLayoutViewResolverPostProcessor implements BeanDefinitionRegistryPostProcessor, Ordered {
+ private static final String GRAILS_VIEW_RESOLVER_BEAN_NAME = "jspViewResolver"
Review Comment:
Align on blank line after class definition?
##########
grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/GrailsLayoutViewResolverPostProcessor.groovy:
##########
@@ -0,0 +1,83 @@
+/*
+ * 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
+ *
+ * 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.plugins.web
+
+
+import org.grails.web.servlet.view.GrailsLayoutViewResolver
+import org.springframework.beans.BeansException
+import org.springframework.beans.MutablePropertyValues
+import org.springframework.beans.factory.config.BeanDefinition
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory
+import org.springframework.beans.factory.config.RuntimeBeanReference
+import org.springframework.beans.factory.support.BeanDefinitionRegistry
+import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor
+import org.springframework.beans.factory.support.GenericBeanDefinition
+import org.springframework.core.Ordered
+
+/**
+ * This BeanDefinitionRegistryPostProcessor replaces the existing jspViewResolver bean with EmbeddedGrailsLayoutViewResolver
+ * and moves the previous jspViewResolver bean configuration as an inner bean of EmbeddedGrailsLayoutViewResolver to be used as
+ * the innerViewResolver of it.
+ *
+ * Scaffolding plugin replaces jspViewResolver with it's own implementation and this solution makes it easier to customize
+ * the inner view resolver.
+ *
+ *
+ * @author Lari Hotari
+ * @since 2.4.0
+ * @see org.grails.web.servlet.view.EmbeddedGrailsLayoutViewResolver
+ *
+ */
+class GrailsLayoutViewResolverPostProcessor implements BeanDefinitionRegistryPostProcessor, Ordered {
+ private static final String GRAILS_VIEW_RESOLVER_BEAN_NAME = "jspViewResolver"
+ private static final String GROOVY_PAGE_LAYOUT_FINDER_BEAN_NAME = "groovyPageLayoutFinder"
+ int order = 0
+ Class<?> layoutViewResolverClass = GrailsLayoutViewResolver
+ String layoutViewResolverBeanParentName = null
+ boolean markBeanPrimary = true
+
+ boolean enabled=true
+
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+
Review Comment:
Spacing?
##########
grails-gsp/grails-web-layout/src/test/groovy/org/apache/grails/web/layout/GroovyPageLayoutFinderSpec.groovy:
##########
Review Comment:
Align on single quotes for strings?
##########
grails-gsp/plugin/src/test/groovy/org/apache/grails/views/gsp/layout/FullGrailsLayoutLifeCycleTests.groovy:
##########
Review Comment:
Align on parentheses for method calls?
##########
grails-gsp/plugin/src/test/groovy/org/grails/web/taglib/RenderTagLibTests.groovy:
##########
@@ -247,6 +255,44 @@ class RenderTagLibTests extends Specification implements UrlMappingsUnitTest<Ren
assertOutputEquals('<th id="1" class="sortable" ><a href="/custompathCustomNamespace/mockcontroller/list?sort=id&order=asc">ID</a></th>', template)
}
+ void testPageProperty() {
+
+ def template = '<g:pageProperty name="foo.bar" />'
+
+ def head = ""
+ def page = new TokenizedHTMLPage([] as char[], new CharArray(0), new CharArray(0))
+ request[RequestConstants.PAGE] = page
+
+ page.addProperty("foo.bar", "good")
+
+ assertOutputEquals("good", template)
Review Comment:
Align on single quotes for strings?
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: notifications-unsubscribe@grails.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org