From e40448cf8a3b52735cb3e1c840d400480478f6b7 Mon Sep 17 00:00:00 2001 From: Michael Yan Date: Sat, 11 Jul 2026 08:05:44 +0800 Subject: [PATCH 1/3] Move GroovyPageInjector from grace-core to grace-gsp --- .../src/main/groovy/grails/compiler/ast/GroovyPageInjector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename {grace-core => grace-gsp}/src/main/groovy/grails/compiler/ast/GroovyPageInjector.java (93%) diff --git a/grace-core/src/main/groovy/grails/compiler/ast/GroovyPageInjector.java b/grace-gsp/src/main/groovy/grails/compiler/ast/GroovyPageInjector.java similarity index 93% rename from grace-core/src/main/groovy/grails/compiler/ast/GroovyPageInjector.java rename to grace-gsp/src/main/groovy/grails/compiler/ast/GroovyPageInjector.java index 9e9e3e0a79..5e462dce64 100644 --- a/grace-core/src/main/groovy/grails/compiler/ast/GroovyPageInjector.java +++ b/grace-gsp/src/main/groovy/grails/compiler/ast/GroovyPageInjector.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2022 the original author or authors. + * Copyright 2011-2026 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From c596ab3181cb3527bb9c60f20df7d09b13c200a5 Mon Sep 17 00:00:00 2001 From: Michael Yan Date: Sat, 11 Jul 2026 08:35:34 +0800 Subject: [PATCH 2/3] Introduce GroovyPageClass and GroovyPageArtefactHandler Groovy Page now is a Artefact, just like Controller, Service, it can be transformed and injected when compiling --- .../groovy/grails/artefact/ArtefactTypes.java | 4 +- .../grails/core/gsp/GroovyPageClass.java | 27 ++++++ .../gsp/GroovyPageArtefactHandler.java | 79 ++++++++++++++++ .../core/gsp/DefaultGroovyPageClass.java | 38 ++++++++ .../main/resources/META-INF/grails.factories | 3 + .../gsp/GroovyPageArtefactHandlerSpec.groovy | 89 +++++++++++++++++++ 6 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 grace-plugin-gsp/src/main/groovy/grails/core/gsp/GroovyPageClass.java create mode 100644 grace-plugin-gsp/src/main/groovy/org/grails/core/artefact/gsp/GroovyPageArtefactHandler.java create mode 100644 grace-plugin-gsp/src/main/groovy/org/grails/core/gsp/DefaultGroovyPageClass.java create mode 100644 grace-plugin-gsp/src/test/groovy/org/grails/core/artefact/gsp/GroovyPageArtefactHandlerSpec.groovy diff --git a/grace-api/src/main/groovy/grails/artefact/ArtefactTypes.java b/grace-api/src/main/groovy/grails/artefact/ArtefactTypes.java index 75ae82a1ac..16400232d8 100644 --- a/grace-api/src/main/groovy/grails/artefact/ArtefactTypes.java +++ b/grace-api/src/main/groovy/grails/artefact/ArtefactTypes.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2025 the original author or authors. + * Copyright 2022-2026 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,8 @@ public final class ArtefactTypes { public static final String DOMAIN_CLASS = "Domain"; + public static final String GROOVY_PAGE = "GroovyPage"; + public static final String SERVICE = "Service"; public static final String TAG_LIBRARY = "TagLib"; diff --git a/grace-plugin-gsp/src/main/groovy/grails/core/gsp/GroovyPageClass.java b/grace-plugin-gsp/src/main/groovy/grails/core/gsp/GroovyPageClass.java new file mode 100644 index 0000000000..d21ec223aa --- /dev/null +++ b/grace-plugin-gsp/src/main/groovy/grails/core/gsp/GroovyPageClass.java @@ -0,0 +1,27 @@ +/* + * Copyright 2022-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package grails.core.gsp; + +import grails.core.GrailsClass; + +/** + * Groovy Page class + * + * @author Michael Yan + * @since 2024.2.0 + */ +public interface GroovyPageClass extends GrailsClass { +} diff --git a/grace-plugin-gsp/src/main/groovy/org/grails/core/artefact/gsp/GroovyPageArtefactHandler.java b/grace-plugin-gsp/src/main/groovy/org/grails/core/artefact/gsp/GroovyPageArtefactHandler.java new file mode 100644 index 0000000000..29422176f3 --- /dev/null +++ b/grace-plugin-gsp/src/main/groovy/org/grails/core/artefact/gsp/GroovyPageArtefactHandler.java @@ -0,0 +1,79 @@ +/* + * Copyright 2022-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.grails.core.artefact.gsp; + +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; + +import grails.artefact.ArtefactTypes; +import grails.core.ArtefactHandler; +import grails.core.ArtefactInfo; +import grails.core.GrailsClass; + +import org.grails.core.gsp.DefaultGroovyPageClass; +import org.grails.gsp.GroovyPage; + +/** + * Handler for Groovy Page Artefact + * + * @author Michael Yan + * @since 2024.2.0 + */ +public class GroovyPageArtefactHandler implements ArtefactHandler { + + public static final String TYPE = ArtefactTypes.GROOVY_PAGE; + public static final String PLUGIN_NAME = "GroovyPages"; + + @Override + public String getPluginName() { + return PLUGIN_NAME; + } + + @Override + public String getType() { + return TYPE; + } + + @Override + public boolean isArtefact(ClassNode classNode) { + return classNode.isDerivedFrom(ClassHelper.make(GroovyPage.class)); + } + + @Override + public boolean isArtefact(Class clazz) { + return clazz.isAssignableFrom(GroovyPage.class); + } + + @Override + public GrailsClass newArtefactClass(Class artefactClass) { + return new DefaultGroovyPageClass(artefactClass); + } + + @Override + public void initialize(ArtefactInfo artefacts) { + } + + @Override + public GrailsClass getArtefactForFeature(Object feature) { + return null; + } + + @Override + public boolean isArtefactGrailsClass(GrailsClass artefactGrailsClass) { + return DefaultGroovyPageClass.class.isAssignableFrom(artefactGrailsClass.getClass()); + } + +} diff --git a/grace-plugin-gsp/src/main/groovy/org/grails/core/gsp/DefaultGroovyPageClass.java b/grace-plugin-gsp/src/main/groovy/org/grails/core/gsp/DefaultGroovyPageClass.java new file mode 100644 index 0000000000..f8adfdba17 --- /dev/null +++ b/grace-plugin-gsp/src/main/groovy/org/grails/core/gsp/DefaultGroovyPageClass.java @@ -0,0 +1,38 @@ +/* + * Copyright 2022-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.grails.core.gsp; + +import grails.core.gsp.GroovyPageClass; +import org.grails.core.AbstractGrailsClass; + +/** + * Default Groovy Page class + * + * @author Michael Yan + * @since 2024.2.0 + */ +public class DefaultGroovyPageClass extends AbstractGrailsClass implements GroovyPageClass { + + /** + * Used by all child classes to create a new instance and get the name right. + * + * @param clazz the Grails class + */ + public DefaultGroovyPageClass(Class clazz) { + super(clazz, "_gsp"); + } + +} diff --git a/grace-plugin-gsp/src/main/resources/META-INF/grails.factories b/grace-plugin-gsp/src/main/resources/META-INF/grails.factories index f30ed85d69..303ec62598 100644 --- a/grace-plugin-gsp/src/main/resources/META-INF/grails.factories +++ b/grace-plugin-gsp/src/main/resources/META-INF/grails.factories @@ -1,2 +1,5 @@ +grails.core.ArtefactHandler=\ +org.grails.core.artefact.gsp.GroovyPageArtefactHandler + grails.compiler.ast.ClassInjector=\ org.grails.gsp.compiler.transform.GroovyPageBytecodeOptimizer \ No newline at end of file diff --git a/grace-plugin-gsp/src/test/groovy/org/grails/core/artefact/gsp/GroovyPageArtefactHandlerSpec.groovy b/grace-plugin-gsp/src/test/groovy/org/grails/core/artefact/gsp/GroovyPageArtefactHandlerSpec.groovy new file mode 100644 index 0000000000..e1a370d3bb --- /dev/null +++ b/grace-plugin-gsp/src/test/groovy/org/grails/core/artefact/gsp/GroovyPageArtefactHandlerSpec.groovy @@ -0,0 +1,89 @@ +/* + * Copyright 2022-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.grails.core.artefact.gsp + +import org.codehaus.groovy.ast.ClassNode +import org.codehaus.groovy.ast.ModuleNode +import org.codehaus.groovy.control.SourceUnit +import spock.lang.Specification + +import grails.core.ArtefactHandler + +/** + * Test Specification for GroovyPageArtefactHandler + * + * @author Michael Yan + * @since 2024.2.0 + */ +class GroovyPageArtefactHandlerSpec extends Specification { + + void "Check index.gsp within 'app/views'"() { + given: + ArtefactHandler handler = new GroovyPageArtefactHandler() + GroovyClassLoader gcl = new GroovyClassLoader() + Class clazz = gcl.parseClass(''' +import org.grails.gsp.GroovyPage; + +class app_views_index_gsp extends GroovyPage { + public String getGroovyPageFileName() { + return "/app/views/index.gsp"; + } + + public Object run() { + return null; + } +} +''') + + SourceUnit sourceUnit = Mock() + ModuleNode moduleNode = new ModuleNode(sourceUnit) + moduleNode.putNodeMetaData('PROJECT_DIR', ['', 'Users', 'grace', 'grace-demo-project'].join(File.separator)) + moduleNode.putNodeMetaData('GRAILS_APP_DIR', ['', 'Users', 'grace', 'grace-demo-project', 'app'].join(File.separator)) + sourceUnit.getAST() >> moduleNode + sourceUnit.getName() >> ['', 'Users', 'grace', 'grace-demo-project', 'app', 'views', 'index.gsp'].join(File.separator) + + ClassNode classNode = new ClassNode(clazz) + classNode.setModule(moduleNode) + + expect: + handler.isArtefact(classNode) + } + + void "Check index.jsp within 'app/views'"() { + given: + ArtefactHandler handler = new GroovyPageArtefactHandler() + GroovyClassLoader gcl = new GroovyClassLoader() + Class clazz = gcl.parseClass(''' +class app_views_index_jsp { + +} +''') + + SourceUnit sourceUnit = Mock() + ModuleNode moduleNode = new ModuleNode(sourceUnit) + moduleNode.putNodeMetaData('PROJECT_DIR', ['', 'Users', 'grace', 'grace-demo-project'].join(File.separator)) + moduleNode.putNodeMetaData('GRAILS_APP_DIR', ['', 'Users', 'grace', 'grace-demo-project', 'app'].join(File.separator)) + sourceUnit.getAST() >> moduleNode + sourceUnit.getName() >> ['', 'Users', 'grace', 'grace-demo-project', 'app', 'views', 'index.jsp'].join(File.separator) + + ClassNode classNode = new ClassNode(clazz) + classNode.setModule(moduleNode) + + expect: + !handler.isArtefact(classNode) + } + +} From 37832cb810571b218b0ba134741424eb699c5805 Mon Sep 17 00:00:00 2001 From: Michael Yan Date: Sat, 11 Jul 2026 10:20:00 +0800 Subject: [PATCH 3/3] Use GroovyPageTransform to support `TraitInjector` for GroovyPage - GroovyPageInjector should only inject GroovyPage - Introduce GroovyPageTransform to support TraitInjector for GroovyPage - Use GrailsAwareInjectionOperation instead of GroovyPageInjectionOperation for GroovyPageClassLoader - Deprecated GroovyPageInjectionOperation in favor of GrailsAwareInjectionOperation --- .../compiler/ast/GroovyPageInjector.java | 13 ++- .../org/grails/gsp/GroovyPageClassLoader.java | 17 ++-- .../gsp/compiler/GroovyPageCompiler.groovy | 35 +++++--- .../GroovyPageBytecodeOptimizer.java | 7 +- .../GroovyPageInjectionOperation.java | 4 +- .../transform/GroovyPageTransform.groovy | 79 +++++++++++++++++++ 6 files changed, 125 insertions(+), 30 deletions(-) create mode 100644 grace-gsp/src/main/groovy/org/grails/gsp/compiler/transform/GroovyPageTransform.groovy diff --git a/grace-gsp/src/main/groovy/grails/compiler/ast/GroovyPageInjector.java b/grace-gsp/src/main/groovy/grails/compiler/ast/GroovyPageInjector.java index 5e462dce64..b65b7f5ed3 100644 --- a/grace-gsp/src/main/groovy/grails/compiler/ast/GroovyPageInjector.java +++ b/grace-gsp/src/main/groovy/grails/compiler/ast/GroovyPageInjector.java @@ -15,12 +15,23 @@ */ package grails.compiler.ast; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; + +import org.grails.gsp.GroovyPage; + /** * Extended marker interface that indicates this ClassInjector applies to GSPs. * * @author Stephane Maldini + * @author Michael Yan * @since 2.0 */ public interface GroovyPageInjector extends ClassInjector { - // marker + + @Override + default boolean shouldInject(ClassNode classNode) { + return classNode.isDerivedFrom(ClassHelper.make(GroovyPage.class)); + } + } diff --git a/grace-gsp/src/main/groovy/org/grails/gsp/GroovyPageClassLoader.java b/grace-gsp/src/main/groovy/org/grails/gsp/GroovyPageClassLoader.java index 53f5330590..2227c84418 100644 --- a/grace-gsp/src/main/groovy/org/grails/gsp/GroovyPageClassLoader.java +++ b/grace-gsp/src/main/groovy/org/grails/gsp/GroovyPageClassLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2006-2022 the original author or authors. + * Copyright 2006-2026 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,12 +22,13 @@ import org.codehaus.groovy.control.CompilerConfiguration; import org.codehaus.groovy.control.Phases; -import org.grails.gsp.compiler.transform.GroovyPageInjectionOperation; +import org.grails.compiler.injection.GrailsAwareInjectionOperation; /** * A class loader that is aware of Groovy Pages and injection operations. * * @author Stephane Maldini + * @author Michael Yan * @since 2.0 */ public class GroovyPageClassLoader extends GroovyClassLoader { @@ -57,14 +58,10 @@ public GroovyPageClassLoader(ClassLoader loader, CompilerConfiguration config) { */ @Override protected CompilationUnit createCompilationUnit(CompilerConfiguration config, CodeSource source) { - CompilationUnit cu = super.createCompilationUnit(config, source); - - GroovyPageInjectionOperation operation; - - operation = new GroovyPageInjectionOperation(); - - cu.addPhaseOperation(operation, Phases.CANONICALIZATION); - return cu; + CompilationUnit compilationUnit = super.createCompilationUnit(config, source); + GrailsAwareInjectionOperation operation = new GrailsAwareInjectionOperation(compilationUnit); + compilationUnit.addPhaseOperation(operation, Phases.CANONICALIZATION); + return compilationUnit; } } diff --git a/grace-gsp/src/main/groovy/org/grails/gsp/compiler/GroovyPageCompiler.groovy b/grace-gsp/src/main/groovy/org/grails/gsp/compiler/GroovyPageCompiler.groovy index d2a949a2ec..6bbeb43954 100644 --- a/grace-gsp/src/main/groovy/org/grails/gsp/compiler/GroovyPageCompiler.groovy +++ b/grace-gsp/src/main/groovy/org/grails/gsp/compiler/GroovyPageCompiler.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the original author or authors. + * Copyright 2004-2026 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,12 +29,15 @@ import org.apache.commons.logging.LogFactory import org.codehaus.groovy.control.CompilationUnit import org.codehaus.groovy.control.CompilerConfiguration import org.codehaus.groovy.control.Phases +import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer import grails.config.ConfigMap +import org.grails.compiler.injection.GrailsAwareInjectionOperation import org.grails.config.CodeGenConfig +import org.grails.gsp.GroovyPageClassLoader import org.grails.gsp.GroovyPageMetaInfo -import org.grails.gsp.compiler.transform.GroovyPageInjectionOperation +import org.grails.gsp.compiler.transform.GroovyPageTransform import org.grails.taglib.encoder.OutputEncodingSettings /** @@ -53,9 +56,9 @@ class GroovyPageCompiler { private final Object mutexObject = new Object() File generatedGroovyPagesDirectory File targetDir - CompilerConfiguration compilerConfig = new CompilerConfiguration() - GroovyPageInjectionOperation operation = new GroovyPageInjectionOperation() - GroovyClassLoader classLoader = new GroovyClassLoader(Thread.currentThread().contextClassLoader, compilerConfig) + CompilerConfiguration compilerConfig + GrailsAwareInjectionOperation operation + GroovyPageClassLoader classLoader List srcFiles = [] File viewsDir @@ -67,14 +70,20 @@ class GroovyPageCompiler { ConfigMap configMap ExecutorService threadPool + GroovyPageCompiler() { + this.compilerConfig = new CompilerConfiguration() + this.operation = new GrailsAwareInjectionOperation() + this.classLoader = new GroovyPageClassLoader(Thread.currentThread().contextClassLoader, this.compilerConfig) + } + void setCompilerConfig(CompilerConfiguration c) { - compilerConfig = c - classLoader = new GroovyClassLoader(Thread.currentThread().contextClassLoader, compilerConfig) + this.compilerConfig = c + this.classLoader = new GroovyPageClassLoader(Thread.currentThread().contextClassLoader, this.compilerConfig) } void setCleanCompilerConfig(CompilerConfiguration c) { - compilerConfig = c - classLoader = new GroovyClassLoader(System.classLoader, compilerConfig) + this.compilerConfig = c + this.classLoader = new GroovyPageClassLoader(System.classLoader, this.compilerConfig) } /** @@ -121,7 +130,9 @@ class GroovyPageCompiler { for (int gspIndex = 0; gspIndex < gspFiles.size(); gspIndex++) { File gsp = gspFiles[gspIndex] try { - compileGSP(viewsDir, gsp, viewPrefix, packagePrefix, results) + CompilerConfiguration configuration = new CompilerConfiguration(this.compilerConfig) + configuration.addCompilationCustomizers(new ASTTransformationCustomizer(new GroovyPageTransform())) + compileGSP(configuration, viewsDir, gsp, viewPrefix, packagePrefix, results) } catch (Exception ex) { LOG.error("Error Compiling GSP File: ${gsp.name} - ${ex.message}") @@ -178,7 +189,7 @@ class GroovyPageCompiler { * @param packagePrefix The package prefix to use which allows scoping for different applications and plugins * */ - protected Map compileGSP(File viewsDir, File gspfile, String viewPrefix, String packagePrefix, Map compileGSPResults) { + protected Map compileGSP(CompilerConfiguration configuration, File viewsDir, File gspfile, String viewPrefix, String packagePrefix, Map compileGSPResults) { String relPath = relativePath(viewsDir, gspfile) String viewuri = viewPrefix + relPath @@ -236,7 +247,7 @@ class GroovyPageCompiler { // register viewuri -> classname mapping compileGSPResults[viewuri] = fullClassName - CompilationUnit unit = new CompilationUnit(compilerConfig, null, classLoader) + CompilationUnit unit = new CompilationUnit(configuration, null, classLoader) unit.addPhaseOperation(operation, Phases.CANONICALIZATION) unit.addSource(gspgroovyfile.name, gsptarget.toString()) // unit.addSource(gspgroovyfile) diff --git a/grace-gsp/src/main/groovy/org/grails/gsp/compiler/transform/GroovyPageBytecodeOptimizer.java b/grace-gsp/src/main/groovy/org/grails/gsp/compiler/transform/GroovyPageBytecodeOptimizer.java index c3b1e697aa..19ff45074e 100644 --- a/grace-gsp/src/main/groovy/org/grails/gsp/compiler/transform/GroovyPageBytecodeOptimizer.java +++ b/grace-gsp/src/main/groovy/org/grails/gsp/compiler/transform/GroovyPageBytecodeOptimizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2023 the original author or authors. + * Copyright 2011-2026 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,9 +48,4 @@ public void performInjection(SourceUnit source, ClassNode classNode) { performInjection(source, null, classNode); } - @Override - public boolean shouldInject(ClassNode classNode) { - return false; - } - } diff --git a/grace-gsp/src/main/groovy/org/grails/gsp/compiler/transform/GroovyPageInjectionOperation.java b/grace-gsp/src/main/groovy/org/grails/gsp/compiler/transform/GroovyPageInjectionOperation.java index 9c2b9b00ff..758da1570c 100644 --- a/grace-gsp/src/main/groovy/org/grails/gsp/compiler/transform/GroovyPageInjectionOperation.java +++ b/grace-gsp/src/main/groovy/org/grails/gsp/compiler/transform/GroovyPageInjectionOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2022 the original author or authors. + * Copyright 2011-2026 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,8 +33,10 @@ * attempt AST injection. * * @author Stephane Maldini + * @deprecated since 2024.2.0, in favor of {@link GrailsAwareInjectionOperation} * @since 2.0 */ +@Deprecated(since = "2024.2.0", forRemoval = true) public class GroovyPageInjectionOperation extends GrailsAwareInjectionOperation { private GroovyPageInjector[] groovyPageInjectors; diff --git a/grace-gsp/src/main/groovy/org/grails/gsp/compiler/transform/GroovyPageTransform.groovy b/grace-gsp/src/main/groovy/org/grails/gsp/compiler/transform/GroovyPageTransform.groovy new file mode 100644 index 0000000000..2935adb604 --- /dev/null +++ b/grace-gsp/src/main/groovy/org/grails/gsp/compiler/transform/GroovyPageTransform.groovy @@ -0,0 +1,79 @@ +/* + * Copyright 2022-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.grails.gsp.compiler.transform + +import groovy.transform.CompilationUnitAware +import groovy.transform.CompileStatic +import org.codehaus.groovy.ast.ASTNode +import org.codehaus.groovy.ast.AnnotationNode +import org.codehaus.groovy.ast.ClassNode +import org.codehaus.groovy.ast.expr.ConstantExpression +import org.codehaus.groovy.control.CompilationUnit +import org.codehaus.groovy.control.CompilePhase +import org.codehaus.groovy.control.SourceUnit +import org.codehaus.groovy.transform.ASTTransformation +import org.codehaus.groovy.transform.GroovyASTTransformation + +import grails.artefact.Artefact +import grails.compiler.traits.TraitInjector +import grails.core.ArtefactHandler + +import org.grails.compiler.injection.ArtefactTypeAstTransformation +import org.grails.compiler.injection.GrailsASTUtils +import org.grails.compiler.injection.TraitInjectionUtils +import org.grails.io.support.GrailsFactoriesLoader + +/** + * {@link ASTTransformation} to apply {@link TraitInjector} for GroovyPage. + * + * @author Michael Yan + * @since 2024.2.0 + */ +@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) +@CompileStatic +class GroovyPageTransform implements ASTTransformation, CompilationUnitAware { + + CompilationUnit compilationUnit + + @Override + void visit(ASTNode[] nodes, SourceUnit source) { + List classes = source.AST.classes + + String sourceName = source.name + if (!sourceName.endsWith('_gsp.groovy')) { + return + } + + List artefactHandlers = GrailsFactoriesLoader.loadFactories(ArtefactHandler) + + for (ClassNode classNode in classes) { + if (!GrailsASTUtils.isApplied(classNode, this.getClass())) { + for (ArtefactHandler handler in artefactHandlers) { + if (handler.isArtefact(classNode)) { + AnnotationNode annotationNode = new AnnotationNode(new ClassNode(Artefact)) + annotationNode.addMember('value', new ConstantExpression(handler.type)) + classNode.addAnnotation(annotationNode) + + ArtefactTypeAstTransformation.performInjectionOnNode(source, classNode, handler.type, compilationUnit) + TraitInjectionUtils.processTraitsForNode(source, classNode, handler.type, compilationUnit) + } + } + } + GrailsASTUtils.markApplied(classNode, this.getClass()) + } + } + +}