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 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 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 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); + } + +}