diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/GlobalRegistrationsImpl.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/GlobalRegistrationsImpl.java index bbfcf53..299b1aa 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/GlobalRegistrationsImpl.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/internal/GlobalRegistrationsImpl.java @@ -100,7 +100,7 @@ public GlobalRegistrationsImpl(ClassDetailsRegistry classDetailsRegistry) { @Override public List getEntityListenerRegistrations() { - return entityListenerRegistrations; + return entityListenerRegistrations == null ? emptyList() : entityListenerRegistrations; } @Override @@ -480,7 +480,11 @@ public void collectEntityListenerRegistrations(List list } listeners.forEach( (listener) -> { - final EntityListenerRegistration listenerRegistration = EntityListenerRegistration.from( listener, classDetailsRegistry ); + final EntityListenerRegistration listenerRegistration = EntityListenerRegistration.from( + EntityListenerRegistration.CallbackType.LISTENER, + listener, + classDetailsRegistry + ); entityListenerRegistrations.add( listenerRegistration ); } ); } diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/EntityListenerRegistration.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/EntityListenerRegistration.java index 2fd609f..1222fc2 100644 --- a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/EntityListenerRegistration.java +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/spi/EntityListenerRegistration.java @@ -96,7 +96,22 @@ public MethodDetails getPostUpdateMethod() { public MethodDetails getPostLoadMethod() { return postLoadMethod; } - public static EntityListenerRegistration from(JaxbEntityListenerImpl jaxbMapping, ClassDetailsRegistry classDetailsRegistry) { + + public enum CallbackType { + /** + * Methods are defined on the entity as instance methods + */ + CALLBACK, + /** + * Methods are static and defined on a separate listener class + */ + LISTENER + } + + public static EntityListenerRegistration from( + CallbackType callbackType, + JaxbEntityListenerImpl jaxbMapping, + ClassDetailsRegistry classDetailsRegistry) { final ClassDetails listenerClassDetails = classDetailsRegistry.resolveClassDetails( jaxbMapping.getClazz() ); final MutableObject prePersistMethod = new MutableObject<>(); final MutableObject postPersistMethod = new MutableObject<>(); @@ -110,31 +125,38 @@ public static EntityListenerRegistration from(JaxbEntityListenerImpl jaxbMapping // todo : make this sensitive to method arguments once we have MethodDetails tracking arguments // for now, just match name if ( jaxbMapping.getPrePersist() != null - && methodDetails.getName().equals( jaxbMapping.getPrePersist().getMethodName() ) ) { + && methodDetails.getName().equals( jaxbMapping.getPrePersist().getMethodName() ) + && matchesSignature( callbackType, methodDetails ) ) { prePersistMethod.set( methodDetails ); } else if ( jaxbMapping.getPostPersist().getMethodName() != null - && methodDetails.getName().equals( jaxbMapping.getPostPersist().getMethodName() ) ) { + && methodDetails.getName().equals( jaxbMapping.getPostPersist().getMethodName() ) + && matchesSignature( callbackType, methodDetails ) ) { postPersistMethod.set( methodDetails ); } else if ( jaxbMapping.getPreRemove() != null - && methodDetails.getName().equals( jaxbMapping.getPreRemove().getMethodName() ) ) { + && methodDetails.getName().equals( jaxbMapping.getPreRemove().getMethodName() ) + && matchesSignature( callbackType, methodDetails ) ) { preRemoveMethod.set( methodDetails ); } else if ( jaxbMapping.getPostRemove() != null - && methodDetails.getName().equals( jaxbMapping.getPostRemove().getMethodName() ) ) { + && methodDetails.getName().equals( jaxbMapping.getPostRemove().getMethodName() ) + && matchesSignature( callbackType, methodDetails ) ) { postRemoveMethod.set( methodDetails ); } else if ( jaxbMapping.getPreUpdate() != null - && methodDetails.getName().equals( jaxbMapping.getPreUpdate().getMethodName() ) ) { + && methodDetails.getName().equals( jaxbMapping.getPreUpdate().getMethodName() ) + && matchesSignature( callbackType, methodDetails ) ) { preUpdateMethod.set( methodDetails ); } else if ( jaxbMapping.getPostUpdate() != null - && methodDetails.getName().equals( jaxbMapping.getPostUpdate().getMethodName() ) ) { + && methodDetails.getName().equals( jaxbMapping.getPostUpdate().getMethodName() ) + && matchesSignature( callbackType, methodDetails ) ) { postUpdateMethod.set( methodDetails ); } else if ( jaxbMapping.getPostLoad() != null - && methodDetails.getName().equals( jaxbMapping.getPostLoad().getMethodName() ) ) { + && methodDetails.getName().equals( jaxbMapping.getPostLoad().getMethodName() ) + && matchesSignature( callbackType, methodDetails ) ) { postLoadMethod.set( methodDetails ); } } ); @@ -150,4 +172,18 @@ else if ( jaxbMapping.getPostLoad() != null postLoadMethod.get() ); } + + private static boolean matchesSignature(CallbackType callbackType, MethodDetails methodDetails) { + if ( callbackType == CallbackType.CALLBACK ) { + // should have no arguments. and technically (spec) have a void return + return methodDetails.getArgumentTypes().isEmpty() + && methodDetails.getReturnType() == null; + } + else { + assert callbackType == CallbackType.LISTENER; + // should have 1 argument. and technically (spec) have a void return + return methodDetails.getArgumentTypes().size() == 1 + && methodDetails.getReturnType() == null; + } + } } diff --git a/hibernate-models-orm/src/main/java/org/hibernate/models/orm/xml/package-info.java b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/xml/package-info.java new file mode 100644 index 0000000..be7a9d4 --- /dev/null +++ b/hibernate-models-orm/src/main/java/org/hibernate/models/orm/xml/package-info.java @@ -0,0 +1,16 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ + +/** + * Support for processing mapping XML files, ultimately creating/updating + * {@linkplain org.hibernate.models.source.spi.AnnotationUsage annotation} references + * on the model's {@linkplain org.hibernate.models.source.spi.AnnotationTarget targets} + * based on the XML. + * + * @author Steve Ebersole + */ +package org.hibernate.models.orm.xml; \ No newline at end of file diff --git a/hibernate-models-orm/src/test/java/org/hibernate/models/orm/xml/globals/EntityListenerTests.java b/hibernate-models-orm/src/test/java/org/hibernate/models/orm/xml/globals/EntityListenerTests.java new file mode 100644 index 0000000..e1a8551 --- /dev/null +++ b/hibernate-models-orm/src/test/java/org/hibernate/models/orm/xml/globals/EntityListenerTests.java @@ -0,0 +1,75 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.orm.xml.globals; + +import java.util.List; + +import org.hibernate.models.orm.internal.ManagedResourcesImpl; +import org.hibernate.models.orm.spi.EntityListenerRegistration; +import org.hibernate.models.orm.spi.ManagedResources; +import org.hibernate.models.orm.spi.ProcessResult; +import org.hibernate.models.orm.spi.Processor; +import org.hibernate.models.orm.xml.SimpleEntity; +import org.hibernate.models.source.SourceModelTestHelper; +import org.hibernate.models.source.internal.SourceModelBuildingContextImpl; +import org.hibernate.models.source.spi.MethodDetails; + +import org.junit.jupiter.api.Test; + +import org.jboss.jandex.Index; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.models.internal.SimpleClassLoading.SIMPLE_CLASS_LOADING; + +/** + * @author Steve Ebersole + */ +public class EntityListenerTests { + @Test + void testGlobalRegistration() { + final ManagedResources managedResources = new ManagedResourcesImpl.Builder() + .addXmlMappings( "mappings/globals.xml" ) + .build(); + + final Index jandexIndex = SourceModelTestHelper.buildJandexIndex( + SIMPLE_CLASS_LOADING, + SimpleEntity.class + ); + final SourceModelBuildingContextImpl buildingContext = SourceModelTestHelper.createBuildingContext( + jandexIndex, + SIMPLE_CLASS_LOADING + ); + + final ProcessResult processResult = Processor.process( + managedResources, + null, + new Processor.Options() { + @Override + public boolean areGeneratorsGlobal() { + return false; + } + + @Override + public boolean shouldIgnoreUnlistedClasses() { + return false; + } + }, + buildingContext + ); + + final List registrations = processResult + .getGlobalRegistrations() + .getEntityListenerRegistrations(); + assertThat( registrations ).hasSize( 1 ); + final EntityListenerRegistration registration = registrations.get( 0 ); + final MethodDetails postPersistMethod = registration.getPostPersistMethod(); + assertThat( postPersistMethod ).isNotNull(); + assertThat( postPersistMethod.getReturnType() ).isNull(); + assertThat( postPersistMethod.getArgumentTypes() ).hasSize( 1 ); + } + +} diff --git a/hibernate-models-orm/src/test/java/org/hibernate/models/orm/xml/globals/GlobalEntityListener.java b/hibernate-models-orm/src/test/java/org/hibernate/models/orm/xml/globals/GlobalEntityListener.java new file mode 100644 index 0000000..85c2a85 --- /dev/null +++ b/hibernate-models-orm/src/test/java/org/hibernate/models/orm/xml/globals/GlobalEntityListener.java @@ -0,0 +1,26 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.orm.xml.globals; + +/** + * JPA entity listener + * + * @author Steve Ebersole + */ +public class GlobalEntityListener { + public void entityCreated(Object entity) { + System.out.println( "Entity was created - " + entity ); + } + + public Object entityCreated() { + throw new RuntimeException( "Should not be called" ); + } + + public void entityCreated(Object e1, Object e2) { + throw new RuntimeException( "Should not be called" ); + } +} diff --git a/hibernate-models-orm/src/test/resources/mappings/globals.xml b/hibernate-models-orm/src/test/resources/mappings/globals.xml index 24bdccb..3ec1eee 100644 --- a/hibernate-models-orm/src/test/resources/mappings/globals.xml +++ b/hibernate-models-orm/src/test/resources/mappings/globals.xml @@ -8,6 +8,16 @@ + + + + + + + + + + diff --git a/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/dynamic/DynamicMethodDetails.java b/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/dynamic/DynamicMethodDetails.java index 071c6a3..82daf55 100644 --- a/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/dynamic/DynamicMethodDetails.java +++ b/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/dynamic/DynamicMethodDetails.java @@ -6,6 +6,8 @@ */ package org.hibernate.models.source.internal.dynamic; +import java.util.List; + import org.hibernate.models.source.internal.MutableMemberDetails; import org.hibernate.models.source.spi.ClassDetails; import org.hibernate.models.source.spi.MethodDetails; @@ -20,17 +22,24 @@ public class DynamicMethodDetails extends AbstractAnnotationTarget implements Me private final MethodKind methodKind; private final boolean isPersistable; + private final ClassDetails returnType; + private final List argumentTypes; + public DynamicMethodDetails( String name, ClassDetails type, MethodKind methodKind, boolean isPersistable, + ClassDetails returnType, + List argumentTypes, SourceModelBuildingContext buildingContext) { super( buildingContext ); this.name = name; this.type = type; this.methodKind = methodKind; this.isPersistable = isPersistable; + this.returnType = returnType; + this.argumentTypes = argumentTypes; } @Override @@ -48,6 +57,16 @@ public ClassDetails getType() { return type; } + @Override + public ClassDetails getReturnType() { + return returnType; + } + + @Override + public List getArgumentTypes() { + return argumentTypes; + } + @Override public boolean isPersistable() { return isPersistable; diff --git a/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/jandex/JandexBuilders.java b/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/jandex/JandexBuilders.java index 14f08e6..57d261b 100644 --- a/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/jandex/JandexBuilders.java +++ b/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/jandex/JandexBuilders.java @@ -156,7 +156,7 @@ else if ( isBoolean( returnType ) && ( methodName.startsWith( "is" ) && method.name().startsWith( "set" ) ) { return new JandexMethodDetails( method, - MethodDetails.MethodKind.GETTER, + MethodDetails.MethodKind.SETTER, buildingContext.getClassDetailsRegistry().resolveClassDetails( method.parameterType( 0 ).name().toString() ), buildingContext ); diff --git a/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/jandex/JandexMethodDetails.java b/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/jandex/JandexMethodDetails.java index e57554d..14f6194 100644 --- a/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/jandex/JandexMethodDetails.java +++ b/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/jandex/JandexMethodDetails.java @@ -6,13 +6,18 @@ */ package org.hibernate.models.source.internal.jandex; +import java.util.ArrayList; +import java.util.List; + import org.hibernate.models.source.internal.MutableMemberDetails; import org.hibernate.models.source.spi.ClassDetails; +import org.hibernate.models.source.spi.ClassDetailsRegistry; import org.hibernate.models.source.spi.MethodDetails; import org.hibernate.models.source.spi.SourceModelBuildingContext; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; import static org.hibernate.models.source.internal.ModifierUtils.isPersistableMethod; @@ -24,6 +29,9 @@ public class JandexMethodDetails extends AbstractAnnotationTarget implements Met private final MethodKind methodKind; private final ClassDetails type; + private final ClassDetails returnType; + private final List argumentTypes; + public JandexMethodDetails( MethodInfo methodInfo, MethodKind methodKind, @@ -33,6 +41,18 @@ public JandexMethodDetails( this.methodInfo = methodInfo; this.methodKind = methodKind; this.type = type; + + final ClassDetailsRegistry classDetailsRegistry = buildingContext.getClassDetailsRegistry(); + if ( methodInfo.returnType().kind() == Type.Kind.VOID ) { + returnType = null; + } + else { + returnType = classDetailsRegistry.resolveClassDetails( methodInfo.returnType().name().toString() ); + } + argumentTypes = new ArrayList<>( methodInfo.parametersCount() ); + for ( int i = 0; i < methodInfo.parametersCount(); i++ ) { + argumentTypes.add( classDetailsRegistry.resolveClassDetails( methodInfo.parameterType( i ).name().toString() ) ); + } } @Override @@ -55,6 +75,16 @@ public ClassDetails getType() { return type; } + @Override + public ClassDetails getReturnType() { + return returnType; + } + + @Override + public List getArgumentTypes() { + return argumentTypes; + } + @Override public boolean isPersistable() { if ( methodInfo.parametersCount() > 0 ) { diff --git a/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/jdk/JdkMethodDetails.java b/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/jdk/JdkMethodDetails.java index df4edf9..fea375f 100644 --- a/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/jdk/JdkMethodDetails.java +++ b/hibernate-models-source/src/main/java/org/hibernate/models/source/internal/jdk/JdkMethodDetails.java @@ -7,9 +7,12 @@ package org.hibernate.models.source.internal.jdk; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; import org.hibernate.models.source.internal.MutableMemberDetails; import org.hibernate.models.source.spi.ClassDetails; +import org.hibernate.models.source.spi.ClassDetailsRegistry; import org.hibernate.models.source.spi.MethodDetails; import org.hibernate.models.source.spi.SourceModelBuildingContext; @@ -23,6 +26,9 @@ public class JdkMethodDetails extends AbstractAnnotationTarget implements Method private final MethodKind methodKind; private final ClassDetails type; + private final ClassDetails returnType; + private final List argumentTypes; + public JdkMethodDetails( Method method, MethodKind methodKind, @@ -32,6 +38,20 @@ public JdkMethodDetails( this.method = method; this.methodKind = methodKind; this.type = type; + + final ClassDetailsRegistry classDetailsRegistry = buildingContext.getClassDetailsRegistry(); + + if ( void.class.equals( method.getReturnType() ) ) { + returnType = null; + } + else { + returnType = classDetailsRegistry.getClassDetails( method.getReturnType().getName() ); + } + + this.argumentTypes = new ArrayList<>( method.getParameterCount() ); + for ( int i = 0; i < method.getParameterTypes().length; i++ ) { + argumentTypes.add( classDetailsRegistry.resolveClassDetails( method.getParameterTypes()[i].getName() ) ); + } } @Override @@ -49,6 +69,16 @@ public ClassDetails getType() { return type; } + @Override + public ClassDetails getReturnType() { + return returnType; + } + + @Override + public List getArgumentTypes() { + return argumentTypes; + } + @Override public boolean isPersistable() { if ( method.getParameterCount() > 0 ) { diff --git a/hibernate-models-source/src/main/java/org/hibernate/models/source/spi/MethodDetails.java b/hibernate-models-source/src/main/java/org/hibernate/models/source/spi/MethodDetails.java index 0b3c8fe..620d4fe 100644 --- a/hibernate-models-source/src/main/java/org/hibernate/models/source/spi/MethodDetails.java +++ b/hibernate-models-source/src/main/java/org/hibernate/models/source/spi/MethodDetails.java @@ -7,6 +7,7 @@ package org.hibernate.models.source.spi; import java.beans.Introspector; +import java.util.List; import org.hibernate.models.ModelsException; @@ -29,6 +30,9 @@ default Kind getKind() { return Kind.METHOD; } + ClassDetails getReturnType(); + List getArgumentTypes(); + @Override default String resolveAttributeName() { final String methodName = getName(); diff --git a/hibernate-models-source/src/test/java/org/hibernate/models/source/MethodDetailsTests.java b/hibernate-models-source/src/test/java/org/hibernate/models/source/MethodDetailsTests.java new file mode 100644 index 0000000..be74eb2 --- /dev/null +++ b/hibernate-models-source/src/test/java/org/hibernate/models/source/MethodDetailsTests.java @@ -0,0 +1,78 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright: Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.models.source; + +import org.hibernate.models.source.internal.SourceModelBuildingContextImpl; +import org.hibernate.models.source.internal.jandex.JandexClassDetails; +import org.hibernate.models.source.spi.ClassDetails; +import org.hibernate.models.source.spi.MethodDetails; + +import org.junit.jupiter.api.Test; + +import org.jboss.jandex.Index; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * @author Steve Ebersole + */ +public class MethodDetailsTests { + @Test + void testMethodDetails() { + final Index index = SourceModelTestHelper.buildJandexIndex( RandomClass.class ); + final SourceModelBuildingContextImpl buildingContext = SourceModelTestHelper.createBuildingContext( + index, + MixedSourcesTests.Entity1.class, + MixedSourcesTests.Embeddable1.class + ); + + final ClassDetails classDetails = buildingContext + .getClassDetailsRegistry() + .findClassDetails( RandomClass.class.getName() ); + assertThat( classDetails ).isNotNull(); + assertThat( classDetails.getMethods() ).hasSize( 4 ); + + classDetails.forEachMethod( (pos, method) -> { + if ( method.getName().equals( "getProperty" ) ) { + assertThat( method.getMethodKind() ).isEqualTo( MethodDetails.MethodKind.GETTER ); + assertThat( method.getType() ).isSameAs( method.getReturnType() ); + assertThat( method.getArgumentTypes() ).isEmpty(); + } + else if ( method.getName().equals( "setProperty" ) ) { + assertThat( method.getMethodKind() ).isEqualTo( MethodDetails.MethodKind.SETTER ); + assertThat( method.getReturnType() ).isNull(); + assertThat( method.getArgumentTypes() ).hasSize( 1 ); + assertThat( method.getType() ).isSameAs( method.getArgumentTypes().get(0) ); + } + else if ( method.getName().equals( "nothing" ) ) { + assertThat( method.getMethodKind() ).isEqualTo( MethodDetails.MethodKind.OTHER ); + assertThat( method.getReturnType() ).isNull(); + assertThat( method.getType() ).isNull(); + assertThat( method.getArgumentTypes() ).isEmpty(); + } + else if ( method.getName().equals( "something" ) ) { + assertThat( method.getMethodKind() ).isEqualTo( MethodDetails.MethodKind.OTHER ); + assertThat( method.getReturnType() ).isNull(); + assertThat( method.getType() ).isNull(); + assertThat( method.getArgumentTypes() ).hasSize( 1 ); + } + else { + fail(); + } + } ); + } + + + public static class RandomClass { + public Integer getProperty() { return null; } + public void setProperty(Integer id) {} + + public void nothing() {} + public void something(Object stuff) {} + } +}