diff --git a/grails-datastore-gorm-test/src/test/groovy/grails/gorm/services/multitenancy/partitioned/PartitionMultiTenancySpec.groovy b/grails-datastore-gorm-test/src/test/groovy/grails/gorm/services/multitenancy/partitioned/PartitionMultiTenancySpec.groovy
index 947c5e13574..05129f34d96 100644
--- a/grails-datastore-gorm-test/src/test/groovy/grails/gorm/services/multitenancy/partitioned/PartitionMultiTenancySpec.groovy
+++ b/grails-datastore-gorm-test/src/test/groovy/grails/gorm/services/multitenancy/partitioned/PartitionMultiTenancySpec.groovy
@@ -13,7 +13,6 @@ import org.grails.datastore.mapping.multitenancy.exceptions.TenantNotFoundExcept
import org.grails.datastore.mapping.multitenancy.resolvers.SystemPropertyTenantResolver
import org.grails.datastore.mapping.simple.SimpleMapDatastore
import spock.lang.AutoCleanup
-import spock.lang.PendingFeature
import spock.lang.Shared
import spock.lang.Specification
@@ -27,18 +26,6 @@ class PartitionMultiTenancySpec extends Specification {
)
@Shared IBookService bookDataService = datastore.getService(IBookService)
- @PendingFeature(reason='''Expected exception of type 'org.grails.datastore.mapping.multitenancy.exceptions.TenantNotFoundException', but got 'java.lang.IllegalStateException\'
- at app//org.spockframework.lang.SpecInternals.checkExceptionThrown(SpecInternals.java:84)
- at app//org.spockframework.lang.SpecInternals.thrownImpl(SpecInternals.java:71)
- at grails.gorm.services.multitenancy.partitioned.PartitionMultiTenancySpec.Test partitioned multi-tenancy with GORM services(PartitionMultiTenancySpec.groovy:42)
- Caused by: java.lang.IllegalStateException: Either class [grails.gorm.services.multitenancy.partitioned.Book] is not a domain class or GORM has not been initialized correctly or has already been shutdown. Ensure GORM is loaded and configured correctly before calling any methods on a GORM entity.
- at org.grails.datastore.gorm.GormEnhancer.stateException(GormEnhancer.groovy:467)
- org.grails.datastore.gorm.GormEnhancer.findDatastore(GormEnhancer.groovy:349)
- at org.grails.datastore.gorm.GormEnhancer.findTenantId(GormEnhancer.groovy:263)
- at org.grails.datastore.gorm.GormEnhancer.findStaticApi(GormEnhancer.groovy:294)
- at org.grails.datastore.gorm.GormEntity$Trait$Helper.currentGormStaticApi(GormEntity.groovy:1370)
- at org.grails.datastore.gorm.GormEntity$Trait$Helper.count(GormEntity.groovy:649)
- at grails.gorm.services.multitenancy.partitioned.PartitionMultiTenancySpec.Test partitioned multi-tenancy with GORM services(PartitionMultiTenancySpec.groovy:39)''')
void 'Test partitioned multi-tenancy with GORM services'() {
setup:
BookService bookService = new BookService()
diff --git a/grails-datastore-gorm-test/src/test/groovy/grails/gorm/services/multitenancy/schema/SchemaPerTenantSpec.groovy b/grails-datastore-gorm-test/src/test/groovy/grails/gorm/services/multitenancy/schema/SchemaPerTenantSpec.groovy
index b82a17bb51e..fc46ff5e544 100644
--- a/grails-datastore-gorm-test/src/test/groovy/grails/gorm/services/multitenancy/schema/SchemaPerTenantSpec.groovy
+++ b/grails-datastore-gorm-test/src/test/groovy/grails/gorm/services/multitenancy/schema/SchemaPerTenantSpec.groovy
@@ -12,7 +12,6 @@ import org.grails.datastore.mapping.multitenancy.exceptions.TenantNotFoundExcept
import org.grails.datastore.mapping.multitenancy.resolvers.SystemPropertyTenantResolver
import org.grails.datastore.mapping.simple.SimpleMapDatastore
import spock.lang.AutoCleanup
-import spock.lang.PendingFeature
import spock.lang.Shared
import spock.lang.Specification
@@ -30,7 +29,6 @@ class SchemaPerTenantSpec extends Specification {
System.setProperty(SystemPropertyTenantResolver.PROPERTY_NAME, "")
}
- @PendingFeature(reason="java.lang.IllegalStateException: Either class [grails.gorm.services.multitenancy.schema.Book] is not a domain class or GORM has not been initialized correctly or has already been shutdown. Ensure GORM is loaded and configured correctly before calling any methods on a GORM entity.")
void 'Test schema per tenant'() {
when:"When there is no tenant"
Book.count()
diff --git a/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/AbstractRecursiveAnnotationVisitor.java b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/AbstractRecursiveAnnotationVisitor.java
new file mode 100644
index 00000000000..a372208261a
--- /dev/null
+++ b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/AbstractRecursiveAnnotationVisitor.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2002-2024 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.datastore.gorm.utils;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.asm.AnnotationVisitor;
+import org.springframework.asm.SpringAsmInfo;
+import org.springframework.asm.Type;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.lang.Nullable;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ReflectionUtils;
+
+import java.lang.reflect.Field;
+import java.security.AccessControlException;
+
+/**
+ * {@link AnnotationVisitor} to recursively visit annotations.
+
+ *
Note: This class was ported to Grails 7 from Spring Framework 5.3 as it was
+ * removed in Spring 6 without a public replacement.
+ *
+ * @author Chris Beams
+ * @author Juergen Hoeller
+ * @author Phillip Webb
+ * @author Sam Brannen
+ * @since 3.1.1
+ * @deprecated As of Spring Framework 5.2, this class and related classes in this
+ * package have been replaced by SimpleAnnotationMetadataReadingVisitor
+ * and related classes for internal use within the framework.
+ */
+@Deprecated
+abstract class AbstractRecursiveAnnotationVisitor extends AnnotationVisitor {
+
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ protected final AnnotationAttributes attributes;
+
+ @Nullable
+ protected final ClassLoader classLoader;
+
+
+ public AbstractRecursiveAnnotationVisitor(@Nullable ClassLoader classLoader, AnnotationAttributes attributes) {
+ super(SpringAsmInfo.ASM_VERSION);
+ this.classLoader = classLoader;
+ this.attributes = attributes;
+ }
+
+
+ @Override
+ public void visit(String attributeName, Object attributeValue) {
+ this.attributes.put(attributeName, attributeValue);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) {
+ String annotationType = Type.getType(asmTypeDescriptor).getClassName();
+ AnnotationAttributes nestedAttributes = new AnnotationAttributes(annotationType, this.classLoader);
+ this.attributes.put(attributeName, nestedAttributes);
+ return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader);
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String attributeName) {
+ return new RecursiveAnnotationArrayVisitor(attributeName, this.attributes, this.classLoader);
+ }
+
+ @Override
+ public void visitEnum(String attributeName, String asmTypeDescriptor, String attributeValue) {
+ Object newValue = getEnumValue(asmTypeDescriptor, attributeValue);
+ visit(attributeName, newValue);
+ }
+
+ protected Object getEnumValue(String asmTypeDescriptor, String attributeValue) {
+ Object valueToUse = attributeValue;
+ try {
+ Class> enumType = ClassUtils.forName(Type.getType(asmTypeDescriptor).getClassName(), this.classLoader);
+ Field enumConstant = ReflectionUtils.findField(enumType, attributeValue);
+ if (enumConstant != null) {
+ ReflectionUtils.makeAccessible(enumConstant);
+ valueToUse = enumConstant.get(null);
+ }
+ }
+ catch (ClassNotFoundException | NoClassDefFoundError ex) {
+ logger.debug("Failed to classload enum type while reading annotation metadata", ex);
+ }
+ catch (IllegalAccessException | AccessControlException ex) {
+ logger.debug("Could not access enum value while reading annotation metadata", ex);
+ }
+ return valueToUse;
+ }
+
+}
diff --git a/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/AnnotationAttributesReadingVisitor.java b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/AnnotationAttributesReadingVisitor.java
new file mode 100644
index 00000000000..16821594ab1
--- /dev/null
+++ b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/AnnotationAttributesReadingVisitor.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2002-2024 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.datastore.gorm.utils;
+
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.lang.Nullable;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.ObjectUtils;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Modifier;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * ASM visitor which looks for annotations defined on a class or method,
+ * including meta-annotations.
+ *
+ *
This visitor is fully recursive, taking into account any nested
+ * annotations or nested annotation arrays.
+ *
+ *
Note: This class was ported to Grails 7 from Spring Framework 5.3 as it was
+ * removed in Spring 6 without a public replacement.
+ *
+ * @author Juergen Hoeller
+ * @author Chris Beams
+ * @author Phillip Webb
+ * @author Sam Brannen
+ * @since 3.0
+ * @deprecated As of Spring Framework 5.2, this class and related classes in this
+ * package have been replaced by SimpleAnnotationMetadataReadingVisitor
+ * and related classes for internal use within the framework.
+ */
+@Deprecated
+final class AnnotationAttributesReadingVisitor extends RecursiveAnnotationAttributesVisitor {
+
+ private final MultiValueMap attributesMap;
+
+ private final Map> metaAnnotationMap;
+
+
+ public AnnotationAttributesReadingVisitor(String annotationType,
+ MultiValueMap attributesMap, Map> metaAnnotationMap,
+ @Nullable ClassLoader classLoader) {
+
+ super(annotationType, new AnnotationAttributes(annotationType, classLoader), classLoader);
+ this.attributesMap = attributesMap;
+ this.metaAnnotationMap = metaAnnotationMap;
+ }
+
+
+ @Override
+ public void visitEnd() {
+ super.visitEnd();
+
+ Class extends Annotation> annotationClass = this.attributes.annotationType();
+ if (annotationClass != null) {
+ List attributeList = this.attributesMap.get(this.annotationType);
+ if (attributeList == null) {
+ this.attributesMap.add(this.annotationType, this.attributes);
+ }
+ else {
+ attributeList.add(0, this.attributes);
+ }
+ if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotationClass.getName())) {
+ try {
+ Annotation[] metaAnnotations = annotationClass.getAnnotations();
+ if (!ObjectUtils.isEmpty(metaAnnotations)) {
+ Set visited = new LinkedHashSet<>();
+ for (Annotation metaAnnotation : metaAnnotations) {
+ recursivelyCollectMetaAnnotations(visited, metaAnnotation);
+ }
+ if (!visited.isEmpty()) {
+ Set metaAnnotationTypeNames = new LinkedHashSet<>(visited.size());
+ for (Annotation ann : visited) {
+ metaAnnotationTypeNames.add(ann.annotationType().getName());
+ }
+ this.metaAnnotationMap.put(annotationClass.getName(), metaAnnotationTypeNames);
+ }
+ }
+ }
+ catch (Throwable ex) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Failed to introspect meta-annotations on " + annotationClass + ": " + ex);
+ }
+ }
+ }
+ }
+ }
+
+ private void recursivelyCollectMetaAnnotations(Set visited, Annotation annotation) {
+ Class extends Annotation> annotationType = annotation.annotationType();
+ String annotationName = annotationType.getName();
+ if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotationName) && visited.add(annotation)) {
+ try {
+ // Only do attribute scanning for public annotations; we'd run into
+ // IllegalAccessExceptions otherwise, and we don't want to mess with
+ // accessibility in a SecurityManager environment.
+ if (Modifier.isPublic(annotationType.getModifiers())) {
+ this.attributesMap.add(annotationName,
+ AnnotationUtils.getAnnotationAttributes(annotation, false, true));
+ }
+ for (Annotation metaMetaAnnotation : annotationType.getAnnotations()) {
+ recursivelyCollectMetaAnnotations(visited, metaMetaAnnotation);
+ }
+ }
+ catch (Throwable ex) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Failed to introspect meta-annotations on " + annotation + ": " + ex);
+ }
+ }
+ }
+ }
+
+}
diff --git a/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/AnnotationMetadataReader.java b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/AnnotationMetadataReader.java
index 19ee93a9b2d..785d0410db7 100644
--- a/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/AnnotationMetadataReader.java
+++ b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/AnnotationMetadataReader.java
@@ -1,4 +1,3 @@
-package org.grails.datastore.gorm.utils;
/*
* Copyright 2016-2024 original authors
*
@@ -15,15 +14,19 @@
* limitations under the License.
*/
+package org.grails.datastore.gorm.utils;
+
import org.springframework.asm.AnnotationVisitor;
import org.springframework.asm.SpringAsmInfo;
-import org.springframework.core.annotation.AnnotationFilter;
+import org.springframework.asm.Type;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
+import java.io.BufferedInputStream;
import java.io.IOException;
+import java.io.InputStream;
/**
* A more limited version of Spring's annotation reader that only reads annotations on classes
@@ -39,18 +42,52 @@ public class AnnotationMetadataReader implements MetadataReader {
private final AnnotationMetadata annotationMetadata;
/**
- * Constructs a new annotation metadata reader with the attributes
+ * Constructs a new annotation metadata reader
*
* @param resource The resource
+ * @param classLoader The classloader
+ * @param readAttributeValues Whether to read the attributes in addition or just the annotation class names
* @throws IOException
*/
- AnnotationMetadataReader(Resource resource, AnnotationFilter filter) throws IOException {
- this.annotationMetadata = new FilteredAnnotationMetadata(resource.getClass(), filter);
- // since AnnotationMetadata extends ClassMetadata
- this.classMetadata = this.annotationMetadata;
+ public AnnotationMetadataReader(Resource resource, ClassLoader classLoader, boolean readAttributeValues) throws IOException {
+ InputStream is = new BufferedInputStream(resource.getInputStream());
+ ClassReader classReader;
+ try {
+ classReader = new ClassReader(is);
+ }
+ catch (IllegalArgumentException ex) {
+ throw new IOException("ASM ClassReader failed to parse class file - " +
+ "probably due to a new Java class file version that isn't supported yet: " + resource, ex);
+ }
+ finally {
+ is.close();
+ }
+
+
+ AnnotationMetadataReadingVisitor visitor;
+
+ if(readAttributeValues) {
+ visitor = new AnnotationMetadataReadingVisitor(classLoader);
+ }
+ else {
+ visitor = new AnnotationMetadataReadingVisitor(classLoader) {
+ @Override
+ public AnnotationVisitor visitAnnotation(final String desc, boolean visible) {
+ String className = Type.getType(desc).getClassName();
+ this.annotationSet.add(className);
+ return new EmptyAnnotationVisitor();
+ }
+ };
+ }
+ classReader.accept(visitor, ClassReader.SKIP_DEBUG);
+
+ this.annotationMetadata = visitor;
+ // (since AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor)
+ this.classMetadata = visitor;
this.resource = resource;
}
+
@Override
public Resource getResource() {
return this.resource;
diff --git a/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/EntityAnnotationMetadataReaderFactory.java b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/AnnotationMetadataReaderFactory.java
similarity index 57%
rename from grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/EntityAnnotationMetadataReaderFactory.java
rename to grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/AnnotationMetadataReaderFactory.java
index 99d053afa28..e7e4d75afc7 100644
--- a/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/EntityAnnotationMetadataReaderFactory.java
+++ b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/AnnotationMetadataReaderFactory.java
@@ -1,6 +1,5 @@
package org.grails.datastore.gorm.utils;
-import org.springframework.core.annotation.AnnotationFilter;
import org.springframework.core.io.Resource;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
@@ -10,13 +9,13 @@
/**
* A {@link CachingMetadataReaderFactory} that only reads annotations and not the whole class body
*/
-class EntityAnnotationMetadataReaderFactory extends CachingMetadataReaderFactory {
- public EntityAnnotationMetadataReaderFactory(ClassLoader classLoader) {
+class AnnotationMetadataReaderFactory extends CachingMetadataReaderFactory {
+ public AnnotationMetadataReaderFactory(ClassLoader classLoader) {
super(classLoader);
}
@Override
public MetadataReader getMetadataReader(Resource resource) throws IOException {
- return new AnnotationMetadataReader(resource, AnnotationFilter.packages("grails.gorm.annotation", "grails.persistence", "jakarta.persistence"));
+ return new AnnotationMetadataReader(resource, getResourceLoader().getClassLoader(), false);
}
}
diff --git a/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/AnnotationMetadataReadingVisitor.groovy b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/AnnotationMetadataReadingVisitor.groovy
new file mode 100644
index 00000000000..f876c7317ec
--- /dev/null
+++ b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/AnnotationMetadataReadingVisitor.groovy
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2002-2024 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.datastore.gorm.utils
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.asm.AnnotationVisitor;
+import org.springframework.asm.MethodVisitor;
+import org.springframework.asm.Opcodes;
+import org.springframework.asm.Type;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.MergedAnnotations;
+import org.springframework.core.type.AnnotationMetadata;
+import org.springframework.core.type.MethodMetadata;
+import org.springframework.lang.Nullable;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+/**
+ * ASM class visitor which looks for the class name and implemented types as
+ * well as for the annotations defined on the class, exposing them through
+ * the {@link org.springframework.core.type.AnnotationMetadata} interface.
+ *
+ * Note: This class was ported to Grails 7 from Spring Framework 5.3 as it was
+ * removed in Spring 6 without a public replacement.
+ *
+ * @author Juergen Hoeller
+ * @author Mark Fisher
+ * @author Costin Leau
+ * @author Phillip Webb
+ * @author Sam Brannen
+ * @since 2.5
+ * @deprecated As of Spring Framework 5.2, this class has been replaced by
+ * SimpleAnnotationMetadataReadingVisitor for internal use within the
+ * framework, but there is no public replacement for
+ * {@code AnnotationMetadataReadingVisitor}.
+ */
+@Deprecated
+public class AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor implements AnnotationMetadata {
+
+ @Nullable
+ protected final ClassLoader classLoader;
+
+ protected final Set annotationSet = new LinkedHashSet<>(4);
+
+ protected final Map> metaAnnotationMap = new LinkedHashMap<>(4);
+
+ /**
+ * Declared as a {@link LinkedMultiValueMap} instead of a {@link MultiValueMap}
+ * to ensure that the hierarchical ordering of the entries is preserved.
+ * @see AnnotationReadingVisitorUtils#getMergedAnnotationAttributes
+ */
+ protected final LinkedMultiValueMap attributesMap = new LinkedMultiValueMap<>(3);
+
+ protected final Set methodMetadataSet = new LinkedHashSet<>(4);
+
+
+ public AnnotationMetadataReadingVisitor(@Nullable ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+
+ @Override
+ public MergedAnnotations getAnnotations() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ // Skip bridge methods - we're only interested in original annotation-defining user methods.
+ // On JDK 8, we'd otherwise run into double detection of the same annotated method...
+ if ((access & Opcodes.ACC_BRIDGE) != 0) {
+ return super.visitMethod(access, name, desc, signature, exceptions);
+ }
+ return new MethodMetadataReadingVisitor(name, access, getClassName(),
+ Type.getReturnType(desc).getClassName(), this.classLoader, this.methodMetadataSet);
+ }
+
+ @Override
+ @Nullable
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (!visible) {
+ return null;
+ }
+ String className = Type.getType(desc).getClassName();
+ if (AnnotationUtils.isInJavaLangAnnotationPackage(className)) {
+ return null;
+ }
+ this.annotationSet.add(className);
+ return new AnnotationAttributesReadingVisitor(
+ className, this.attributesMap, this.metaAnnotationMap, this.classLoader);
+ }
+
+
+ @Override
+ public Set getAnnotationTypes() {
+ return this.annotationSet;
+ }
+
+ @Override
+ public Set getMetaAnnotationTypes(String annotationName) {
+ Set metaAnnotationTypes = this.metaAnnotationMap.get(annotationName);
+ return (metaAnnotationTypes != null ? metaAnnotationTypes : Collections.emptySet());
+ }
+
+ @Override
+ public boolean hasMetaAnnotation(String metaAnnotationType) {
+ if (AnnotationUtils.isInJavaLangAnnotationPackage(metaAnnotationType)) {
+ return false;
+ }
+ Collection> allMetaTypes = this.metaAnnotationMap.values();
+ for (Set metaTypes : allMetaTypes) {
+ if (metaTypes.contains(metaAnnotationType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isAnnotated(String annotationName) {
+ return (!AnnotationUtils.isInJavaLangAnnotationPackage(annotationName) &&
+ this.attributesMap.containsKey(annotationName));
+ }
+
+ @Override
+ public boolean hasAnnotation(String annotationName) {
+ return getAnnotationTypes().contains(annotationName);
+ }
+
+ @Override
+ @Nullable
+ public AnnotationAttributes getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
+ AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes(
+ this.attributesMap, this.metaAnnotationMap, annotationName);
+ if (raw == null) {
+ return null;
+ }
+ return AnnotationReadingVisitorUtils.convertClassValues(
+ "class '" + getClassName() + "'", this.classLoader, raw, classValuesAsString);
+ }
+
+ @Override
+ @Nullable
+ public MultiValueMap getAllAnnotationAttributes(String annotationName, boolean classValuesAsString) {
+ MultiValueMap allAttributes = new LinkedMultiValueMap<>();
+ List attributes = this.attributesMap.get(annotationName);
+ if (attributes == null) {
+ return null;
+ }
+ String annotatedElement = "class '" + getClassName() + "'";
+ for (AnnotationAttributes raw : attributes) {
+ for (Map.Entry entry : AnnotationReadingVisitorUtils.convertClassValues(
+ annotatedElement, this.classLoader, raw, classValuesAsString).entrySet()) {
+ allAttributes.add(entry.getKey(), entry.getValue());
+ }
+ }
+ return allAttributes;
+ }
+
+ @Override
+ public boolean hasAnnotatedMethods(String annotationName) {
+ for (MethodMetadata methodMetadata : this.methodMetadataSet) {
+ if (methodMetadata.isAnnotated(annotationName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Set getAnnotatedMethods(String annotationName) {
+ Set annotatedMethods = new LinkedHashSet<>(4);
+ for (MethodMetadata methodMetadata : this.methodMetadataSet) {
+ if (methodMetadata.isAnnotated(annotationName)) {
+ annotatedMethods.add(methodMetadata);
+ }
+ }
+ return annotatedMethods;
+ }
+
+ @Override
+ public Set getDeclaredMethods() {
+ return Set.of();
+ }
+
+}
+
diff --git a/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/AnnotationReadingVisitorUtils.java b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/AnnotationReadingVisitorUtils.java
new file mode 100644
index 00000000000..4c185fb61d9
--- /dev/null
+++ b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/AnnotationReadingVisitorUtils.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2002-2024 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.datastore.gorm.utils;
+
+import org.springframework.asm.Type;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.lang.Nullable;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.ObjectUtils;
+
+import java.util.*;
+
+/**
+ * Internal utility class used when reading annotations via ASM.
+ *
+ * Note: This class was ported to Grails 7 from Spring Framework 5.3 as it was
+ * removed in Spring 6 without a public replacement.
+ *
+ * @author Juergen Hoeller
+ * @author Mark Fisher
+ * @author Costin Leau
+ * @author Phillip Webb
+ * @author Sam Brannen
+ * @since 4.0
+ * @deprecated As of Spring Framework 5.2, this class and related classes in this
+ * package have been replaced by SimpleAnnotationMetadataReadingVisitor
+ * and related classes for internal use within the framework.
+ */
+@Deprecated
+abstract class AnnotationReadingVisitorUtils {
+
+ public static AnnotationAttributes convertClassValues(Object annotatedElement,
+ @Nullable ClassLoader classLoader, AnnotationAttributes original, boolean classValuesAsString) {
+
+ AnnotationAttributes result = new AnnotationAttributes(original);
+ AnnotationUtils.postProcessAnnotationAttributes(annotatedElement, result, classValuesAsString);
+
+ for (Map.Entry entry : result.entrySet()) {
+ try {
+ Object value = entry.getValue();
+ if (value instanceof AnnotationAttributes) {
+ value = convertClassValues(
+ annotatedElement, classLoader, (AnnotationAttributes) value, classValuesAsString);
+ }
+ else if (value instanceof AnnotationAttributes[]) {
+ AnnotationAttributes[] values = (AnnotationAttributes[]) value;
+ for (int i = 0; i < values.length; i++) {
+ values[i] = convertClassValues(annotatedElement, classLoader, values[i], classValuesAsString);
+ }
+ value = values;
+ }
+ else if (value instanceof Type) {
+ value = (classValuesAsString ? ((Type) value).getClassName() :
+ ClassUtils.forName(((Type) value).getClassName(), classLoader));
+ }
+ else if (value instanceof Type[]) {
+ Type[] array = (Type[]) value;
+ Object[] convArray =
+ (classValuesAsString ? new String[array.length] : new Class>[array.length]);
+ for (int i = 0; i < array.length; i++) {
+ convArray[i] = (classValuesAsString ? array[i].getClassName() :
+ ClassUtils.forName(array[i].getClassName(), classLoader));
+ }
+ value = convArray;
+ }
+ else if (classValuesAsString) {
+ if (value instanceof Class) {
+ value = ((Class>) value).getName();
+ }
+ else if (value instanceof Class[]) {
+ Class>[] clazzArray = (Class>[]) value;
+ String[] newValue = new String[clazzArray.length];
+ for (int i = 0; i < clazzArray.length; i++) {
+ newValue[i] = clazzArray[i].getName();
+ }
+ value = newValue;
+ }
+ }
+ entry.setValue(value);
+ }
+ catch (Throwable ex) {
+ // Class not found - can't resolve class reference in annotation attribute.
+ result.put(entry.getKey(), ex);
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Retrieve the merged attributes of the annotation of the given type,
+ * if any, from the supplied {@code attributesMap}.
+ * Annotation attribute values appearing lower in the annotation
+ * hierarchy (i.e., closer to the declaring class) will override those
+ * defined higher in the annotation hierarchy.
+ * @param attributesMap the map of annotation attribute lists, keyed by
+ * annotation type name
+ * @param metaAnnotationMap the map of meta annotation relationships,
+ * keyed by annotation type name
+ * @param annotationName the fully qualified class name of the annotation
+ * type to look for
+ * @return the merged annotation attributes, or {@code null} if no
+ * matching annotation is present in the {@code attributesMap}
+ * @since 4.0.3
+ */
+ @Nullable
+ public static AnnotationAttributes getMergedAnnotationAttributes(
+ LinkedMultiValueMap attributesMap,
+ Map> metaAnnotationMap, String annotationName) {
+
+ // Get the unmerged list of attributes for the target annotation.
+ List attributesList = attributesMap.get(annotationName);
+ if (CollectionUtils.isEmpty(attributesList)) {
+ return null;
+ }
+
+ // To start with, we populate the result with a copy of all attribute values
+ // from the target annotation. A copy is necessary so that we do not
+ // inadvertently mutate the state of the metadata passed to this method.
+ AnnotationAttributes result = new AnnotationAttributes(attributesList.get(0));
+
+ Set overridableAttributeNames = new HashSet<>(result.keySet());
+ overridableAttributeNames.remove(AnnotationUtils.VALUE);
+
+ // Since the map is a LinkedMultiValueMap, we depend on the ordering of
+ // elements in the map and reverse the order of the keys in order to traverse
+ // "down" the annotation hierarchy.
+ List annotationTypes = new ArrayList<>(attributesMap.keySet());
+ Collections.reverse(annotationTypes);
+
+ // No need to revisit the target annotation type:
+ annotationTypes.remove(annotationName);
+
+ for (String currentAnnotationType : annotationTypes) {
+ List currentAttributesList = attributesMap.get(currentAnnotationType);
+ if (!ObjectUtils.isEmpty(currentAttributesList)) {
+ Set metaAnns = metaAnnotationMap.get(currentAnnotationType);
+ if (metaAnns != null && metaAnns.contains(annotationName)) {
+ AnnotationAttributes currentAttributes = currentAttributesList.get(0);
+ for (String overridableAttributeName : overridableAttributeNames) {
+ Object value = currentAttributes.get(overridableAttributeName);
+ if (value != null) {
+ // Store the value, potentially overriding a value from an attribute
+ // of the same name found higher in the annotation hierarchy.
+ result.put(overridableAttributeName, value);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+}
diff --git a/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/ClassMetadataReadingVisitor.java b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/ClassMetadataReadingVisitor.java
new file mode 100644
index 00000000000..8d0d79f6e9b
--- /dev/null
+++ b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/ClassMetadataReadingVisitor.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2002-2024 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.datastore.gorm.utils;
+
+import org.springframework.asm.Attribute;
+import org.springframework.asm.*;
+import org.springframework.core.type.ClassMetadata;
+import org.springframework.lang.Nullable;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * ASM class visitor which looks only for the class name and implemented types,
+ * exposing them through the {@link ClassMetadata}
+ * interface.
+ *
+ * Note: This class was ported to Grails 7 from Spring Framework 5.3 as it was
+ * removed in Spring 6 without a public replacement.
+ *
+ * @author Rod Johnson
+ * @author Costin Leau
+ * @author Mark Fisher
+ * @author Ramnivas Laddad
+ * @author Chris Beams
+ * @since 2.5
+ * @deprecated As of Spring Framework 5.2, this class and related classes in this
+ * package have been replaced by SimpleAnnotationMetadataReadingVisitor
+ * and related classes for internal use within the framework.
+ */
+@Deprecated
+class ClassMetadataReadingVisitor extends ClassVisitor implements ClassMetadata {
+
+ private String className = "";
+
+ private boolean isInterface;
+
+ private boolean isAnnotation;
+
+ private boolean isAbstract;
+
+ private boolean isFinal;
+
+ @Nullable
+ private String enclosingClassName;
+
+ private boolean independentInnerClass;
+
+ @Nullable
+ private String superClassName;
+
+ private String[] interfaces = new String[0];
+
+ private Set memberClassNames = new LinkedHashSet<>(4);
+
+
+ public ClassMetadataReadingVisitor() {
+ super(SpringAsmInfo.ASM_VERSION);
+ }
+
+
+ @Override
+ public void visit(
+ int version, int access, String name, String signature, @Nullable String supername, String[] interfaces) {
+
+ this.className = ClassUtils.convertResourcePathToClassName(name);
+ this.isInterface = ((access & Opcodes.ACC_INTERFACE) != 0);
+ this.isAnnotation = ((access & Opcodes.ACC_ANNOTATION) != 0);
+ this.isAbstract = ((access & Opcodes.ACC_ABSTRACT) != 0);
+ this.isFinal = ((access & Opcodes.ACC_FINAL) != 0);
+ if (supername != null && !this.isInterface) {
+ this.superClassName = ClassUtils.convertResourcePathToClassName(supername);
+ }
+ this.interfaces = new String[interfaces.length];
+ for (int i = 0; i < interfaces.length; i++) {
+ this.interfaces[i] = ClassUtils.convertResourcePathToClassName(interfaces[i]);
+ }
+ }
+
+ @Override
+ public void visitOuterClass(String owner, String name, String desc) {
+ this.enclosingClassName = ClassUtils.convertResourcePathToClassName(owner);
+ }
+
+ @Override
+ public void visitInnerClass(String name, @Nullable String outerName, String innerName, int access) {
+ if (outerName != null) {
+ String fqName = ClassUtils.convertResourcePathToClassName(name);
+ String fqOuterName = ClassUtils.convertResourcePathToClassName(outerName);
+ if (this.className.equals(fqName)) {
+ this.enclosingClassName = fqOuterName;
+ this.independentInnerClass = ((access & Opcodes.ACC_STATIC) != 0);
+ }
+ else if (this.className.equals(fqOuterName)) {
+ this.memberClassNames.add(fqName);
+ }
+ }
+ }
+
+ @Override
+ public void visitSource(String source, String debug) {
+ // no-op
+ }
+
+ @Override
+ @Nullable
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ // no-op
+ return new EmptyAnnotationVisitor();
+ }
+
+ @Override
+ public void visitAttribute(Attribute attr) {
+ // no-op
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
+ // no-op
+ return new EmptyFieldVisitor();
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+ // no-op
+ return new EmptyMethodVisitor();
+ }
+
+ @Override
+ public void visitEnd() {
+ // no-op
+ }
+
+
+ @Override
+ public String getClassName() {
+ return this.className;
+ }
+
+ @Override
+ public boolean isInterface() {
+ return this.isInterface;
+ }
+
+ @Override
+ public boolean isAnnotation() {
+ return this.isAnnotation;
+ }
+
+ @Override
+ public boolean isAbstract() {
+ return this.isAbstract;
+ }
+
+ @Override
+ public boolean isFinal() {
+ return this.isFinal;
+ }
+
+ @Override
+ public boolean isIndependent() {
+ return (this.enclosingClassName == null || this.independentInnerClass);
+ }
+
+ @Override
+ public boolean hasEnclosingClass() {
+ return (this.enclosingClassName != null);
+ }
+
+ @Override
+ @Nullable
+ public String getEnclosingClassName() {
+ return this.enclosingClassName;
+ }
+
+ @Override
+ @Nullable
+ public String getSuperClassName() {
+ return this.superClassName;
+ }
+
+ @Override
+ public String[] getInterfaceNames() {
+ return this.interfaces;
+ }
+
+ @Override
+ public String[] getMemberClassNames() {
+ return StringUtils.toStringArray(this.memberClassNames);
+ }
+
+
+ private static class EmptyAnnotationVisitor extends AnnotationVisitor {
+
+ public EmptyAnnotationVisitor() {
+ super(SpringAsmInfo.ASM_VERSION);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String desc) {
+ return this;
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ return this;
+ }
+ }
+
+
+ private static class EmptyMethodVisitor extends MethodVisitor {
+
+ public EmptyMethodVisitor() {
+ super(SpringAsmInfo.ASM_VERSION);
+ }
+ }
+
+
+ private static class EmptyFieldVisitor extends FieldVisitor {
+
+ public EmptyFieldVisitor() {
+ super(SpringAsmInfo.ASM_VERSION);
+ }
+ }
+
+}
diff --git a/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/ClasspathEntityScanner.groovy b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/ClasspathEntityScanner.groovy
index 7037960a8ec..5e6d05536fa 100644
--- a/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/ClasspathEntityScanner.groovy
+++ b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/ClasspathEntityScanner.groovy
@@ -67,7 +67,7 @@ class ClasspathEntityScanner {
*/
Class[] scan(Package... packages) {
ClassPathScanningCandidateComponentProvider componentProvider = new ClassPathScanningCandidateComponentProvider(false)
- componentProvider.setMetadataReaderFactory(new EntityAnnotationMetadataReaderFactory(classLoader))
+ componentProvider.setMetadataReaderFactory(new AnnotationMetadataReaderFactory(classLoader))
for(ann in annotations) {
componentProvider.addIncludeFilter(new AnnotationTypeFilter(ann))
}
diff --git a/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/MethodMetadataReadingVisitor.java b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/MethodMetadataReadingVisitor.java
new file mode 100644
index 00000000000..8cb1322db76
--- /dev/null
+++ b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/MethodMetadataReadingVisitor.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2002-2024 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.datastore.gorm.utils;
+
+import org.springframework.asm.*;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.annotation.MergedAnnotations;
+import org.springframework.core.type.MethodMetadata;
+import org.springframework.lang.Nullable;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * ASM method visitor which looks for the annotations defined on a method,
+ * exposing them through the {@link MethodMetadata}
+ * interface.
+ *
+ * Note: This class was ported to Grails 7 from Spring Framework 5.3 as it was
+ * removed in Spring 6 without a public replacement.
+ *
+ * @author Juergen Hoeller
+ * @author Mark Pollack
+ * @author Costin Leau
+ * @author Chris Beams
+ * @author Phillip Webb
+ * @since 3.0
+ * @deprecated As of Spring Framework 5.2, this class and related classes in this
+ * package have been replaced by SimpleAnnotationMetadataReadingVisitor
+ * and related classes for internal use within the framework.
+ */
+@Deprecated
+public class MethodMetadataReadingVisitor extends MethodVisitor implements MethodMetadata {
+
+ protected final String methodName;
+
+ protected final int access;
+
+ protected final String declaringClassName;
+
+ protected final String returnTypeName;
+
+ @Nullable
+ protected final ClassLoader classLoader;
+
+ protected final Set methodMetadataSet;
+
+ protected final Map> metaAnnotationMap = new LinkedHashMap<>(4);
+
+ protected final LinkedMultiValueMap attributesMap = new LinkedMultiValueMap<>(3);
+
+
+ public MethodMetadataReadingVisitor(String methodName, int access, String declaringClassName,
+ String returnTypeName, @Nullable ClassLoader classLoader, Set methodMetadataSet) {
+
+ super(SpringAsmInfo.ASM_VERSION);
+ this.methodName = methodName;
+ this.access = access;
+ this.declaringClassName = declaringClassName;
+ this.returnTypeName = returnTypeName;
+ this.classLoader = classLoader;
+ this.methodMetadataSet = methodMetadataSet;
+ }
+
+
+ @Override
+ public MergedAnnotations getAnnotations() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ @Nullable
+ public AnnotationVisitor visitAnnotation(final String desc, boolean visible) {
+ if (!visible) {
+ return null;
+ }
+ this.methodMetadataSet.add(this);
+ String className = Type.getType(desc).getClassName();
+ return new AnnotationAttributesReadingVisitor(
+ className, this.attributesMap, this.metaAnnotationMap, this.classLoader);
+ }
+
+
+ @Override
+ public String getMethodName() {
+ return this.methodName;
+ }
+
+ @Override
+ public boolean isAbstract() {
+ return ((this.access & Opcodes.ACC_ABSTRACT) != 0);
+ }
+
+ @Override
+ public boolean isStatic() {
+ return ((this.access & Opcodes.ACC_STATIC) != 0);
+ }
+
+ @Override
+ public boolean isFinal() {
+ return ((this.access & Opcodes.ACC_FINAL) != 0);
+ }
+
+ @Override
+ public boolean isOverridable() {
+ return (!isStatic() && !isFinal() && ((this.access & Opcodes.ACC_PRIVATE) == 0));
+ }
+
+ @Override
+ public boolean isAnnotated(String annotationName) {
+ return this.attributesMap.containsKey(annotationName);
+ }
+
+ @Override
+ @Nullable
+ public AnnotationAttributes getAnnotationAttributes(String annotationName, boolean classValuesAsString) {
+ AnnotationAttributes raw = AnnotationReadingVisitorUtils.getMergedAnnotationAttributes(
+ this.attributesMap, this.metaAnnotationMap, annotationName);
+ if (raw == null) {
+ return null;
+ }
+ return AnnotationReadingVisitorUtils.convertClassValues(
+ "method '" + getMethodName() + "'", this.classLoader, raw, classValuesAsString);
+ }
+
+ @Override
+ @Nullable
+ public MultiValueMap getAllAnnotationAttributes(String annotationName, boolean classValuesAsString) {
+ if (!this.attributesMap.containsKey(annotationName)) {
+ return null;
+ }
+ MultiValueMap allAttributes = new LinkedMultiValueMap<>();
+ List attributesList = this.attributesMap.get(annotationName);
+ if (attributesList != null) {
+ String annotatedElement = "method '" + getMethodName() + '\'';
+ for (AnnotationAttributes annotationAttributes : attributesList) {
+ AnnotationAttributes convertedAttributes = AnnotationReadingVisitorUtils.convertClassValues(
+ annotatedElement, this.classLoader, annotationAttributes, classValuesAsString);
+ convertedAttributes.forEach(allAttributes::add);
+ }
+ }
+ return allAttributes;
+ }
+
+ @Override
+ public String getDeclaringClassName() {
+ return this.declaringClassName;
+ }
+
+ @Override
+ public String getReturnTypeName() {
+ return this.returnTypeName;
+ }
+
+}
diff --git a/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/RecursiveAnnotationArrayVisitor.java b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/RecursiveAnnotationArrayVisitor.java
new file mode 100644
index 00000000000..5e86dc66b95
--- /dev/null
+++ b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/RecursiveAnnotationArrayVisitor.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2002-2024 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.datastore.gorm.utils;
+
+import org.springframework.asm.AnnotationVisitor;
+import org.springframework.asm.Type;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.lang.Nullable;
+import org.springframework.util.ObjectUtils;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * {@link AnnotationVisitor} to recursively visit annotation arrays.
+ *
+ * Note: This class was ported to Grails 7 from Spring Framework 5.3 as it was
+ * removed in Spring 6 without a public replacement.
+ *
+ * @author Chris Beams
+ * @author Juergen Hoeller
+ * @since 3.1.1
+ * @deprecated As of Spring Framework 5.2, this class and related classes in this
+ * package have been replaced by SimpleAnnotationMetadataReadingVisitor
+ * and related classes for internal use within the framework.
+ */
+@Deprecated
+class RecursiveAnnotationArrayVisitor extends AbstractRecursiveAnnotationVisitor {
+
+ private final String attributeName;
+
+ private final List allNestedAttributes = new ArrayList<>();
+
+
+ public RecursiveAnnotationArrayVisitor(
+ String attributeName, AnnotationAttributes attributes, @Nullable ClassLoader classLoader) {
+
+ super(classLoader, attributes);
+ this.attributeName = attributeName;
+ }
+
+
+ @Override
+ public void visit(String attributeName, Object attributeValue) {
+ Object newValue = attributeValue;
+ Object existingValue = this.attributes.get(this.attributeName);
+ if (existingValue != null) {
+ newValue = ObjectUtils.addObjectToArray((Object[]) existingValue, newValue);
+ }
+ else {
+ Class> arrayClass = newValue.getClass();
+ if (Enum.class.isAssignableFrom(arrayClass)) {
+ while (arrayClass.getSuperclass() != null && !arrayClass.isEnum()) {
+ arrayClass = arrayClass.getSuperclass();
+ }
+ }
+ Object[] newArray = (Object[]) Array.newInstance(arrayClass, 1);
+ newArray[0] = newValue;
+ newValue = newArray;
+ }
+ this.attributes.put(this.attributeName, newValue);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String attributeName, String asmTypeDescriptor) {
+ String annotationType = Type.getType(asmTypeDescriptor).getClassName();
+ AnnotationAttributes nestedAttributes = new AnnotationAttributes(annotationType, this.classLoader);
+ this.allNestedAttributes.add(nestedAttributes);
+ return new RecursiveAnnotationAttributesVisitor(annotationType, nestedAttributes, this.classLoader);
+ }
+
+ @Override
+ public void visitEnd() {
+ if (!this.allNestedAttributes.isEmpty()) {
+ this.attributes.put(this.attributeName, this.allNestedAttributes.toArray(new AnnotationAttributes[0]));
+ }
+ else if (!this.attributes.containsKey(this.attributeName)) {
+ Class extends Annotation> annotationType = this.attributes.annotationType();
+ if (annotationType != null) {
+ try {
+ Class> attributeType = annotationType.getMethod(this.attributeName).getReturnType();
+ if (attributeType.isArray()) {
+ Class> elementType = attributeType.getComponentType();
+ if (elementType.isAnnotation()) {
+ elementType = AnnotationAttributes.class;
+ }
+ this.attributes.put(this.attributeName, Array.newInstance(elementType, 0));
+ }
+ }
+ catch (NoSuchMethodException ex) {
+ // Corresponding attribute method not found: cannot expose empty array.
+ }
+ }
+ }
+ }
+
+}
diff --git a/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/RecursiveAnnotationAttributesVisitor.java b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/RecursiveAnnotationAttributesVisitor.java
new file mode 100644
index 00000000000..b93f70a1140
--- /dev/null
+++ b/grails-datastore-gorm/src/main/groovy/org/grails/datastore/gorm/utils/RecursiveAnnotationAttributesVisitor.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2024 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.datastore.gorm.utils;
+
+import org.springframework.asm.AnnotationVisitor;
+import org.springframework.core.annotation.AnnotationAttributes;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.lang.Nullable;
+
+/**
+ * {@link AnnotationVisitor} to recursively visit annotation attributes.
+ *
+ * Note: This class was ported to Grails 7 from Spring Framework 5.3 as it was
+ * removed in Spring 6 without a public replacement.
+ *
+ * @author Chris Beams
+ * @author Juergen Hoeller
+ * @since 3.1.1
+ * @deprecated As of Spring Framework 5.2, this class and related classes in this
+ * package have been replaced by SimpleAnnotationMetadataReadingVisitor
+ * and related classes for internal use within the framework.
+ */
+@Deprecated
+class RecursiveAnnotationAttributesVisitor extends AbstractRecursiveAnnotationVisitor {
+
+ protected final String annotationType;
+
+
+ public RecursiveAnnotationAttributesVisitor(
+ String annotationType, AnnotationAttributes attributes, @Nullable ClassLoader classLoader) {
+
+ super(classLoader, attributes);
+ this.annotationType = annotationType;
+ }
+
+
+ @Override
+ public void visitEnd() {
+ AnnotationUtils.registerDefaultValues(this.attributes);
+ }
+
+}