Skip to content

Commit

Permalink
#53 - Create AnnotationUsage for entity-listener and callback methods
Browse files Browse the repository at this point in the history
  • Loading branch information
mbladel committed Oct 27, 2023
1 parent c96fe6f commit a9803e6
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -455,10 +455,8 @@ private static void processEntityMetadata(
sourceModelBuildingContext
) );

processCommonEntityOrMappedSuperclass( jaxbEntity, classDetails, sourceModelBuildingContext );
processEntityOrMappedSuperclass( jaxbEntity, classDetails, sourceModelBuildingContext );

// todo : callbacks
// todo : entity-listeners
// todo : secondary-tables
}

Expand Down Expand Up @@ -581,10 +579,7 @@ private static void processMappedSuperclassMetadata(
final JaxbAttributesContainer attributes = jaxbMappedSuperclass.getAttributes();
processAttributes( attributes, classDetails, classAccessType, sourceModelBuildingContext );

processCommonEntityOrMappedSuperclass( jaxbMappedSuperclass, classDetails, sourceModelBuildingContext );

// todo : entity-listeners
// todo : callbacks
processEntityOrMappedSuperclass( jaxbMappedSuperclass, classDetails, sourceModelBuildingContext );
}

public static void processOverrideMappedSuperclass(
Expand All @@ -603,15 +598,19 @@ public static void processOverrideMappedSuperclass(
} );
}

private static void processCommonEntityOrMappedSuperclass(
private static void processEntityOrMappedSuperclass(
JaxbEntityOrMappedSuperclass jaxbEntity,
MutableClassDetails classDetails,
SourceModelBuildingContext sourceModelBuildingContext) {
XmlAnnotationHelper.applyIdClass( jaxbEntity.getIdClass(), classDetails, sourceModelBuildingContext );

// todo : entity-listeners
// todo : exclude default listeners (?)
// todo : exclude superclass listeners (?)
XmlAnnotationHelper.applyLifecycleCallbacks( jaxbEntity, classDetails, sourceModelBuildingContext );

if ( jaxbEntity.getEntityListeners() != null ) {
jaxbEntity.getEntityListeners().getEntityListener().forEach( ( jaxbEntityListener -> {
XmlAnnotationHelper.applyEntityListener( jaxbEntityListener, classDetails, sourceModelBuildingContext );
} ) );
}
}

public static void processCompleteEmbeddable(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,13 @@
import org.hibernate.boot.jaxb.mapping.spi.JaxbConvertImpl;
import org.hibernate.boot.jaxb.mapping.spi.JaxbEmbeddedIdImpl;
import org.hibernate.boot.jaxb.mapping.spi.JaxbEntity;
import org.hibernate.boot.jaxb.mapping.spi.JaxbEntityListenerImpl;
import org.hibernate.boot.jaxb.mapping.spi.JaxbGeneratedValueImpl;
import org.hibernate.boot.jaxb.mapping.spi.JaxbHbmFilterImpl;
import org.hibernate.boot.jaxb.mapping.spi.JaxbIdClassImpl;
import org.hibernate.boot.jaxb.mapping.spi.JaxbIdImpl;
import org.hibernate.boot.jaxb.mapping.spi.JaxbLifecycleCallback;
import org.hibernate.boot.jaxb.mapping.spi.JaxbLifecycleCallbackContainer;
import org.hibernate.boot.jaxb.mapping.spi.JaxbLobImpl;
import org.hibernate.boot.jaxb.mapping.spi.JaxbNationalizedImpl;
import org.hibernate.boot.jaxb.mapping.spi.JaxbNaturalId;
Expand All @@ -67,6 +70,7 @@
import org.hibernate.models.source.spi.AnnotationUsage;
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.hibernate.type.SqlTypes;

Expand All @@ -79,13 +83,21 @@
import jakarta.persistence.Convert;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.IdClass;
import jakarta.persistence.Inheritance;
import jakarta.persistence.Lob;
import jakarta.persistence.PostLoad;
import jakarta.persistence.PostPersist;
import jakarta.persistence.PostRemove;
import jakarta.persistence.PostUpdate;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreRemove;
import jakarta.persistence.PreUpdate;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import jakarta.persistence.TableGenerator;
Expand Down Expand Up @@ -861,4 +873,47 @@ static void applyIdClass(
);
}
}

static void applyEntityListener(
JaxbEntityListenerImpl jaxbEntityListener,
MutableClassDetails classDetails,
SourceModelBuildingContext buildingContext) {
final MutableAnnotationUsage<EntityListeners> entityListeners = getOrMakeAnnotation(
EntityListeners.class,
classDetails
);
final MutableClassDetails entityListenerClass = (MutableClassDetails) buildingContext.getClassDetailsRegistry()
.resolveClassDetails( jaxbEntityListener.getClazz() );
applyLifecycleCallbacks( jaxbEntityListener, entityListenerClass, buildingContext );
final List<ClassDetails> values = entityListeners.getAttributeValue( "value" );
if ( values != null ) {
values.add( entityListenerClass );
}
else {
entityListeners.setAttributeValue( "value", new ArrayList<>( List.of( entityListenerClass ) ) );
}
}

static void applyLifecycleCallbacks(
JaxbLifecycleCallbackContainer lifecycleCallbackContainer,
MutableClassDetails classDetails,
SourceModelBuildingContext buildingContext) {
applyLifecycleCallback( lifecycleCallbackContainer.getPrePersist(), PrePersist.class, classDetails );
applyLifecycleCallback( lifecycleCallbackContainer.getPostPersist(), PostPersist.class, classDetails );
applyLifecycleCallback( lifecycleCallbackContainer.getPreRemove(), PreRemove.class, classDetails );
applyLifecycleCallback( lifecycleCallbackContainer.getPostRemove(), PostRemove.class, classDetails );
applyLifecycleCallback( lifecycleCallbackContainer.getPreUpdate(), PreUpdate.class, classDetails );
applyLifecycleCallback( lifecycleCallbackContainer.getPostUpdate(), PostUpdate.class, classDetails );
applyLifecycleCallback( lifecycleCallbackContainer.getPostLoad(), PostLoad.class, classDetails );
}

private static void applyLifecycleCallback(
JaxbLifecycleCallback lifecycleCallback,
Class<? extends Annotation> lifecycleAnnotation,
MutableClassDetails classDetails) {
if ( lifecycleCallback != null ) {
final MethodDetails method = classDetails.findMethodByName( lifecycleCallback.getMethodName() );
makeAnnotation( lifecycleAnnotation, (MutableMemberDetails) method );
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ public SimpleEntity(Integer id, String name) {
this.name = name;
}

public void prePersist() {
// used by hibernate lifecycle callbacks
}

public void preRemove() {
// used by hibernate lifecycle callbacks
}

public void preUpdate() {
// used by hibernate lifecycle callbacks
}

public Integer getId() {
return id;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* 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.lifecycle;

import java.util.List;

import org.hibernate.boot.internal.BootstrapContextImpl;
import org.hibernate.boot.internal.MetadataBuilderImpl;
import org.hibernate.boot.model.process.spi.ManagedResources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.models.orm.process.ManagedResourcesImpl;
import org.hibernate.models.orm.spi.CategorizedDomainModel;
import org.hibernate.models.orm.spi.EntityTypeMetadata;
import org.hibernate.models.orm.xml.SimpleEntity;
import org.hibernate.models.source.spi.AnnotationUsage;
import org.hibernate.models.source.spi.ClassDetails;

import org.junit.jupiter.api.Test;

import jakarta.persistence.EntityListeners;
import jakarta.persistence.PostLoad;
import jakarta.persistence.PostPersist;
import jakarta.persistence.PostRemove;
import jakarta.persistence.PostUpdate;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreRemove;
import jakarta.persistence.PreUpdate;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.models.orm.spi.ManagedResourcesProcessor.processManagedResources;

/**
* @author Marco Belladelli
*/
public class EntityLifecycleTests {
@Test
void testEntityLifecycle() {
final ManagedResources managedResources = new ManagedResourcesImpl.Builder()
.addXmlMappings( "mappings/lifecycle/entity-lifecycle.xml" )
.build();
try (StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().build()) {
final BootstrapContextImpl bootstrapContext = new BootstrapContextImpl(
serviceRegistry,
new MetadataBuilderImpl.MetadataBuildingOptionsImpl( serviceRegistry )
);
final CategorizedDomainModel categorizedDomainModel = processManagedResources(
managedResources,
bootstrapContext
);

assertThat( categorizedDomainModel.getEntityHierarchies() ).hasSize( 1 );
final EntityTypeMetadata rootEntity = categorizedDomainModel.getEntityHierarchies()
.iterator()
.next()
.getRoot();
final ClassDetails classDetails = rootEntity.getClassDetails();
assertThat( classDetails.getName() ).isEqualTo( SimpleEntity.class.getName() );

// lifecycle callback methods
assertThat( classDetails.findMethodByName( "prePersist" ).getAnnotationUsage( PrePersist.class ) ).isNotNull();
assertThat( classDetails.findMethodByName( "preRemove" ).getAnnotationUsage( PreRemove.class ) ).isNotNull();
assertThat( classDetails.findMethodByName( "preUpdate" ).getAnnotationUsage( PreUpdate.class ) ).isNotNull();

// entity listeners
final AnnotationUsage<EntityListeners> entityListenersAnn = classDetails.getAnnotationUsage( EntityListeners.class );
assertThat( entityListenersAnn ).isNotNull();
final List<ClassDetails> entityListeners = entityListenersAnn.getAttributeValue( "value" );
assertThat( entityListeners ).hasSize( 1 );
final ClassDetails listener = entityListeners.get( 0 );
assertThat( listener.getName() ).isEqualTo( SimpleEntityListener.class.getName() );
assertThat( listener.findMethodByName( "postPersist" ).getAnnotationUsage( PostPersist.class ) ).isNotNull();
assertThat( listener.findMethodByName( "postRemove" ).getAnnotationUsage( PostRemove.class ) ).isNotNull();
assertThat( listener.findMethodByName( "postUpdate" ).getAnnotationUsage( PostUpdate.class ) ).isNotNull();
assertThat( listener.findMethodByName( "postLoad" ).getAnnotationUsage( PostLoad.class ) ).isNotNull();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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.lifecycle;

/**
* @author Marco Belladelli
*/
public class SimpleEntityListener {
public void postPersist() {
// used by hibernate lifecycle callbacks
}

public void postRemove() {
// used by hibernate lifecycle callbacks
}

public void postUpdate() {
// used by hibernate lifecycle callbacks
}

public void postLoad() {
// used by hibernate lifecycle callbacks
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!--
~ Hibernate, Relational Persistence for Idiomatic Java
~
~ SPDX-License-Identifier: Apache-2.0
~ Copyright: Red Hat Inc. and Hibernate Authors
-->
<entity-mappings xmlns="http://www.hibernate.org/xsd/orm/mapping"
version="3.1">
<entity class="org.hibernate.models.orm.xml.SimpleEntity" access="FIELD">
<entity-listeners>
<entity-listener class="org.hibernate.models.orm.xml.lifecycle.SimpleEntityListener">
<post-persist method-name="postPersist"/>
<post-remove method-name="postRemove"/>
<post-update method-name="postUpdate"/>
<post-load method-name="postLoad"/>
</entity-listener>
</entity-listeners>
<pre-persist method-name="prePersist"/>
<pre-remove method-name="preRemove"/>
<pre-update method-name="preUpdate"/>

<attributes>
<id name="id"/>
<basic name="name"/>
</attributes>
</entity>
</entity-mappings>
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,28 @@ default FieldDetails findFieldByName(String name) {
*/
List<MethodDetails> getMethods();

/**
* Find a method by check
*/
default MethodDetails findMethod(Predicate<MethodDetails> check) {
final List<MethodDetails> methods = getMethods();
for ( int i = 0; i < methods.size(); i++ ) {
final MethodDetails methodDetails = methods.get( i );
if ( check.test( methodDetails ) ) {
return methodDetails;
}
}
return null;
}

/**
* Find a method by name
*/
default MethodDetails findMethodByName(String name) {
assert name != null;
return findMethod( methodDetails -> name.equals( methodDetails.getName() ) );
}

/**
* Visit each method
*/
Expand Down

0 comments on commit a9803e6

Please sign in to comment.