Migrate User Guide Events chapter extras to test folder
This commit is contained in:
parent
ff3b54a962
commit
3642bb63cd
|
@ -1,6 +1,6 @@
|
||||||
[[events]]
|
[[events]]
|
||||||
== Interceptors and events
|
== Interceptors and events
|
||||||
:sourcedir: extras
|
:sourcedir: ../../../../../test/java/org/hibernate/userguide/events
|
||||||
|
|
||||||
It is useful for the application to react to certain events that occur inside Hibernate.
|
It is useful for the application to react to certain events that occur inside Hibernate.
|
||||||
This allows for the implementation of generic functionality and the extension of Hibernate functionality.
|
This allows for the implementation of generic functionality and the extension of Hibernate functionality.
|
||||||
|
@ -12,8 +12,15 @@ The `org.hibernate.Interceptor` interface provides callbacks from the session to
|
||||||
allowing the application to inspect and/or manipulate properties of a persistent object before it is saved, updated, deleted or loaded.
|
allowing the application to inspect and/or manipulate properties of a persistent object before it is saved, updated, deleted or loaded.
|
||||||
|
|
||||||
One possible use for this is to track auditing information.
|
One possible use for this is to track auditing information.
|
||||||
The following example shows an `Interceptor` implementation that automatically sets the `createTimestamp` property when an `Auditable` entity is created,
|
The following example shows an `Interceptor` implementation that automatically logs when an entity is updated.
|
||||||
and it updates the `lastUpdateTimestamp` property when an Auditable entity is updated.
|
|
||||||
|
[[events-interceptors-example]]
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/InterceptorTest.java[tags=events-interceptors-example]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
[NOTE]
|
[NOTE]
|
||||||
====
|
====
|
||||||
|
@ -24,10 +31,11 @@ An Interceptor can be either `Session`-scoped or `SessionFactory`-scoped.
|
||||||
|
|
||||||
A Session-scoped interceptor is specified when a session is opened.
|
A Session-scoped interceptor is specified when a session is opened.
|
||||||
|
|
||||||
|
[[events-interceptors-session-scope-example]]
|
||||||
====
|
====
|
||||||
[source,java]
|
[source, JAVA, indent=0]
|
||||||
----
|
----
|
||||||
include::{sourcedir}/SessionScopedExample.java[]
|
include::{sourcedir}/InterceptorTest.java[tags=events-interceptors-session-scope-example]
|
||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
|
@ -36,10 +44,11 @@ Unless a session is opened explicitly specifying the interceptor to use, the `Se
|
||||||
`SessionFactory`-scoped interceptors must be thread safe.
|
`SessionFactory`-scoped interceptors must be thread safe.
|
||||||
Ensure that you do not store session-specific states, since multiple sessions will use this interceptor potentially concurrently.
|
Ensure that you do not store session-specific states, since multiple sessions will use this interceptor potentially concurrently.
|
||||||
|
|
||||||
|
[[events-interceptors-session-factory-scope-example]]
|
||||||
====
|
====
|
||||||
[source,java]
|
[source, JAVA, indent=0]
|
||||||
----
|
----
|
||||||
include::{sourcedir}/SessionFactoryScopedExample.java[]
|
include::{sourcedir}/InterceptorTest.java[tags=events-interceptors-session-factory-scope-example]
|
||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
|
@ -65,14 +74,16 @@ A custom listener implements the appropriate interface for the event it wants to
|
||||||
(or even the default event listeners used by Hibernate out-of-the-box as these are declared non-final for this purpose).
|
(or even the default event listeners used by Hibernate out-of-the-box as these are declared non-final for this purpose).
|
||||||
Here is an example of a custom load event listener:
|
Here is an example of a custom load event listener:
|
||||||
|
|
||||||
|
[[events-interceptors-load-listener-example]]
|
||||||
.Custom LoadListener example
|
.Custom LoadListener example
|
||||||
====
|
====
|
||||||
[source,java]
|
[source, JAVA, indent=0]
|
||||||
----
|
----
|
||||||
include::{sourcedir}/LoadListenerExample.java[]
|
include::{sourcedir}/ListenerTest.java[tags=events-interceptors-load-listener-example]
|
||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
|
[[events-declarative-security]]
|
||||||
=== Hibernate declarative security
|
=== Hibernate declarative security
|
||||||
|
|
||||||
Usually, declarative security in Hibernate applications is managed in a session facade layer.
|
Usually, declarative security in Hibernate applications is managed in a session facade layer.
|
||||||
|
@ -84,16 +95,18 @@ Again, see <<chapters/bootstrap/Bootstrap.adoc#bootstrap-event-listener-registra
|
||||||
|
|
||||||
Below is an example of an appropriate `org.hibernate.integrator.spi.Integrator` implementation for this purpose.
|
Below is an example of an appropriate `org.hibernate.integrator.spi.Integrator` implementation for this purpose.
|
||||||
|
|
||||||
|
[[events-declarative-security-jacc-example]]
|
||||||
.JACC listener registration example
|
.JACC listener registration example
|
||||||
====
|
====
|
||||||
[source,java]
|
[source, JAVA, indent=0]
|
||||||
----
|
----
|
||||||
include::{sourcedir}/jacc-event-reg-example.java[]
|
include::{sourcedir}/ListenerTest.java[tags=events-declarative-security-jacc-example]
|
||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
You must also decide how to configure your JACC provider. Consult your JACC provider documentation.
|
You must also decide how to configure your JACC provider. Consult your JACC provider documentation.
|
||||||
|
|
||||||
|
[[events-jpa-callbacks]]
|
||||||
=== JPA Callbacks
|
=== JPA Callbacks
|
||||||
|
|
||||||
JPA also defines a more limited set of callbacks through annotations.
|
JPA also defines a more limited set of callbacks through annotations.
|
||||||
|
@ -111,7 +124,7 @@ JPA also defines a more limited set of callbacks through annotations.
|
||||||
|@PostLoad |Executed after an entity has been loaded into the current persistence context or an entity has been refreshed.
|
|@PostLoad |Executed after an entity has been loaded into the current persistence context or an entity has been refreshed.
|
||||||
|=======================================================================
|
|=======================================================================
|
||||||
|
|
||||||
There are 2 available approaches defined for specifying callback handling:
|
There are two available approaches defined for specifying callback handling:
|
||||||
|
|
||||||
* The first approach is to annotate methods on the entity itself to receive notification of particular entity life cycle event(s).
|
* The first approach is to annotate methods on the entity itself to receive notification of particular entity life cycle event(s).
|
||||||
* The second is to use a separate entity listener class.
|
* The second is to use a separate entity listener class.
|
||||||
|
@ -119,11 +132,12 @@ An entity listener is a stateless class with a no-arg constructor.
|
||||||
The callback annotations are placed on a method of this class instead of the entity class.
|
The callback annotations are placed on a method of this class instead of the entity class.
|
||||||
The entity listener class is then associated with the entity using the `javax.persistence.EntityListeners` annotation
|
The entity listener class is then associated with the entity using the `javax.persistence.EntityListeners` annotation
|
||||||
|
|
||||||
|
[[events-jpa-callbacks-example]]
|
||||||
.Example of specifying JPA callbacks
|
.Example of specifying JPA callbacks
|
||||||
====
|
====
|
||||||
[source,java]
|
[source, JAVA, indent=0]
|
||||||
----
|
----
|
||||||
include::{sourcedir}/JpaCallbacksExample.java[]
|
include::{sourcedir}/ListenerTest.java[tags=events-jpa-callbacks-example]
|
||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
import org.hibernate.EmptyInterceptor;
|
|
||||||
import org.hibernate.Transaction;
|
|
||||||
import org.hibernate.type.Type;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
public class AuditInterceptor extends EmptyInterceptor {
|
|
||||||
|
|
||||||
private int updates;
|
|
||||||
|
|
||||||
private int creates;
|
|
||||||
|
|
||||||
private int loads;
|
|
||||||
|
|
||||||
public void onDelete(Object entity,
|
|
||||||
Serializable id,
|
|
||||||
Object[] state,
|
|
||||||
String[] propertyNames,
|
|
||||||
Type[] types) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean onFlushDirty(Object entity,
|
|
||||||
Serializable id,
|
|
||||||
Object[] currentState,
|
|
||||||
Object[] previousState,
|
|
||||||
String[] propertyNames,
|
|
||||||
Type[] types) {
|
|
||||||
|
|
||||||
if ( entity instanceof Auditable ) {
|
|
||||||
updates++;
|
|
||||||
for ( int i = 0; i < propertyNames.length; i++ ) {
|
|
||||||
if ( "lastUpdateTimestamp".equals( propertyNames[i] ) ) {
|
|
||||||
currentState[i] = new Date();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean onLoad(Object entity,
|
|
||||||
Serializable id,
|
|
||||||
Object[] state,
|
|
||||||
String[] propertyNames,
|
|
||||||
Type[] types) {
|
|
||||||
if ( entity instanceof Auditable ) {
|
|
||||||
loads++;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean onSave(Object entity,
|
|
||||||
Serializable id,
|
|
||||||
Object[] state,
|
|
||||||
String[] propertyNames,
|
|
||||||
Type[] types) {
|
|
||||||
|
|
||||||
if ( entity instanceof Auditable ) {
|
|
||||||
creates++;
|
|
||||||
for ( int i = 0; i < propertyNames.length; i++ ) {
|
|
||||||
if ( "createTimestamp".equals( propertyNames[i] ) ) {
|
|
||||||
state[i] = new Date();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void afterTransactionCompletion( Transaction tx ) {
|
|
||||||
if ( tx.wasCommitted() ) {
|
|
||||||
System.out.println( "Creations: " + creates + ", Updates: " + updates + "Loads: " + loads );
|
|
||||||
}
|
|
||||||
updates = 0;
|
|
||||||
creates = 0;
|
|
||||||
loads = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
@Entity
|
|
||||||
@EntityListeners( LastUpdateListener.class )
|
|
||||||
public class Cat {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
private Integer id;
|
|
||||||
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
private Calendar dateOfBirth;
|
|
||||||
|
|
||||||
@Transient
|
|
||||||
private int age;
|
|
||||||
|
|
||||||
private Date lastUpdate;
|
|
||||||
//getters and setters
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set my transient property at load time based on a calculation,
|
|
||||||
* note that a native Hibernate formula mapping is better for this purpose.
|
|
||||||
*/
|
|
||||||
@PostLoad
|
|
||||||
public void calculateAge() {
|
|
||||||
Calendar birth = new GregorianCalendar();
|
|
||||||
birth.setTime( dateOfBirth );
|
|
||||||
Calendar now = new GregorianCalendar();
|
|
||||||
now.setTime( new Date() );
|
|
||||||
int adjust = 0;
|
|
||||||
if ( now.get( Calendar.DAY_OF_YEAR ) - birth.get( Calendar.DAY_OF_YEAR ) & lt;
|
|
||||||
0){
|
|
||||||
adjust = -1;
|
|
||||||
}
|
|
||||||
age = now.get( Calendar.YEAR ) - birth.get( Calendar.YEAR ) + adjust;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class LastUpdateListener {
|
|
||||||
/**
|
|
||||||
* automatic property set before any database persistence
|
|
||||||
*/
|
|
||||||
@PreUpdate
|
|
||||||
@PrePersist
|
|
||||||
public void setLastUpdate( Cat o ) {
|
|
||||||
o.setLastUpdate( new Date() );
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
public class LoadListenerExample implements LoadEventListener {
|
|
||||||
// this is the single method defined by the LoadEventListener interface
|
|
||||||
public void onLoad( LoadEvent event, LoadEventListener.LoadType loadType )
|
|
||||||
throws HibernateException {
|
|
||||||
if (!MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() )) {
|
|
||||||
throw MySecurityException( "Unauthorized access" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
SessionFactory sessionFactory = new Configuration()
|
|
||||||
.setInterceptor( new AuditInterceptor() )
|
|
||||||
...
|
|
||||||
.buildSessionFactory();
|
|
|
@ -1 +0,0 @@
|
||||||
Session session = sf.withOptions().interceptor( new AuditInterceptor() ).openSession();
|
|
|
@ -1,42 +0,0 @@
|
||||||
import org.hibernate.event.service.spi.DuplicationStrategy;
|
|
||||||
import org.hibernate.event.service.spi.EventListenerRegistry;
|
|
||||||
import org.hibernate.integrator.spi.Integrator;
|
|
||||||
import org.hibernate.secure.internal.*;
|
|
||||||
|
|
||||||
public class JaccEventListenerIntegrator implements Integrator {
|
|
||||||
|
|
||||||
private static final DuplicationStrategy JACC_DUPLICATION_STRATEGY = new DuplicationStrategy() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean areMatch( Object listener, Object original ) {
|
|
||||||
return listener.getClass().equals( original.getClass() ) &&
|
|
||||||
JACCSecurityListener.class.isInstance( original );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Action getAction() {
|
|
||||||
return Action.KEEP_ORIGINAL;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings({"unchecked"})
|
|
||||||
public void integrate(
|
|
||||||
Configuration configuration,
|
|
||||||
SessionFactoryImplementor sessionFactory,
|
|
||||||
SessionFactoryServiceRegistry serviceRegistry) {
|
|
||||||
boolean isSecurityEnabled = configuration.getProperties().containsKey( AvailableSettings.JACC_ENABLED );
|
|
||||||
if (!isSecurityEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService( EventListenerRegistry.class );
|
|
||||||
eventListenerRegistry.addDuplicationStrategy( JACC_DUPLICATION_STRATEGY );
|
|
||||||
|
|
||||||
final String jaccContextId = configuration.getProperty( Environment.JACC_CONTEXTID );
|
|
||||||
eventListenerRegistry.prependListeners( EventType.PRE_DELETE, new JACCPreDeleteEventListener( jaccContextId ) );
|
|
||||||
eventListenerRegistry.prependListeners( EventType.PRE_INSERT, new JACCPreInsertEventListener( jaccContextId ) );
|
|
||||||
eventListenerRegistry.prependListeners( EventType.PRE_UPDATE, new JACCPreUpdateEventListener( jaccContextId ) );
|
|
||||||
eventListenerRegistry.prependListeners( EventType.PRE_LOAD, new JACCPreLoadEventListener( jaccContextId ) );
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,147 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.userguide.events;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.EntityManagerFactory;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
|
||||||
|
import org.hibernate.EmptyInterceptor;
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.SessionFactory;
|
||||||
|
import org.hibernate.boot.MetadataSources;
|
||||||
|
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||||
|
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||||
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
import static org.hibernate.userguide.util.TransactionUtil.doInJPA;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Vlad Mihalcea
|
||||||
|
*/
|
||||||
|
public class InterceptorTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger( InterceptorTest.class );
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?>[] getAnnotatedClasses() {
|
||||||
|
return new Class<?>[] {
|
||||||
|
Customer.class
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
entityManager.persist( new Customer( "John Doe" ) );
|
||||||
|
Customer customer = new Customer();
|
||||||
|
entityManager.persist( customer );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSessionInterceptor() {
|
||||||
|
EntityManagerFactory entityManagerFactory = entityManagerFactory();
|
||||||
|
Serializable customerId = 1L;
|
||||||
|
//tag::events-interceptors-session-scope-example[]
|
||||||
|
SessionFactory sessionFactory = entityManagerFactory.unwrap( SessionFactory.class );
|
||||||
|
Session session = sessionFactory
|
||||||
|
.withOptions()
|
||||||
|
.interceptor(new LoggingInterceptor() )
|
||||||
|
.openSession();
|
||||||
|
|
||||||
|
Customer customer = session.get( Customer.class, customerId );
|
||||||
|
customer.setName( "Mr. John Doe" );
|
||||||
|
//Entity Customer#1 changed from [John Doe, 0] to [Mr. John Doe, 0]
|
||||||
|
session.flush();
|
||||||
|
//end::events-interceptors-session-scope-example[]
|
||||||
|
session.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSessionFactoryInterceptor() {
|
||||||
|
|
||||||
|
Serializable customerId = 1L;
|
||||||
|
//tag::events-interceptors-session-factory-scope-example[]
|
||||||
|
SessionFactory sessionFactory = new MetadataSources( new StandardServiceRegistryBuilder().build() )
|
||||||
|
.addAnnotatedClass( Customer.class )
|
||||||
|
.getMetadataBuilder()
|
||||||
|
.build()
|
||||||
|
.getSessionFactoryBuilder()
|
||||||
|
.applyInterceptor( new LoggingInterceptor() )
|
||||||
|
.build();
|
||||||
|
//end::events-interceptors-session-factory-scope-example[]
|
||||||
|
Session session = sessionFactory.openSession();
|
||||||
|
|
||||||
|
Customer customer = session.get( Customer.class, customerId );
|
||||||
|
customer.setName( "Mr. John Doe" );
|
||||||
|
//Entity Customer#1 changed from [John Doe, 0] to [Mr. John Doe, 0]
|
||||||
|
session.flush();
|
||||||
|
session.close();
|
||||||
|
sessionFactory.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Customer")
|
||||||
|
public static class Customer {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public Customer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Customer(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//tag::events-interceptors-example[]
|
||||||
|
public static class LoggingInterceptor extends EmptyInterceptor {
|
||||||
|
@Override
|
||||||
|
public boolean onFlushDirty(
|
||||||
|
Object entity,
|
||||||
|
Serializable id,
|
||||||
|
Object[] currentState,
|
||||||
|
Object[] previousState,
|
||||||
|
String[] propertyNames,
|
||||||
|
Type[] types) {
|
||||||
|
LOGGER.debugv( "Entity {0}#{1} changed from {2} to {3}",
|
||||||
|
entity.getClass().getSimpleName(),
|
||||||
|
id,
|
||||||
|
Arrays.toString( previousState ),
|
||||||
|
Arrays.toString( currentState )
|
||||||
|
);
|
||||||
|
return super.onFlushDirty( entity, id, currentState,
|
||||||
|
previousState, propertyNames, types
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//end::events-interceptors-example[]
|
||||||
|
}
|
|
@ -0,0 +1,296 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.userguide.events;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.EntityListeners;
|
||||||
|
import javax.persistence.EntityManagerFactory;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.PostLoad;
|
||||||
|
import javax.persistence.PrePersist;
|
||||||
|
import javax.persistence.PreUpdate;
|
||||||
|
import javax.persistence.Transient;
|
||||||
|
|
||||||
|
import org.hibernate.HibernateException;
|
||||||
|
import org.hibernate.boot.Metadata;
|
||||||
|
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||||
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
|
import org.hibernate.engine.config.spi.ConfigurationService;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.event.service.spi.DuplicationStrategy;
|
||||||
|
import org.hibernate.event.service.spi.EventListenerRegistry;
|
||||||
|
import org.hibernate.event.spi.EventType;
|
||||||
|
import org.hibernate.event.spi.LoadEvent;
|
||||||
|
import org.hibernate.event.spi.LoadEventListener;
|
||||||
|
import org.hibernate.integrator.spi.ServiceContributingIntegrator;
|
||||||
|
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||||
|
import org.hibernate.secure.internal.DisabledJaccServiceImpl;
|
||||||
|
import org.hibernate.secure.internal.JaccPreDeleteEventListener;
|
||||||
|
import org.hibernate.secure.internal.JaccPreInsertEventListener;
|
||||||
|
import org.hibernate.secure.internal.JaccPreLoadEventListener;
|
||||||
|
import org.hibernate.secure.internal.JaccPreUpdateEventListener;
|
||||||
|
import org.hibernate.secure.internal.JaccSecurityListener;
|
||||||
|
import org.hibernate.secure.internal.StandardJaccServiceImpl;
|
||||||
|
import org.hibernate.secure.spi.GrantedPermission;
|
||||||
|
import org.hibernate.secure.spi.IntegrationException;
|
||||||
|
import org.hibernate.secure.spi.JaccPermissionDeclarations;
|
||||||
|
import org.hibernate.secure.spi.JaccService;
|
||||||
|
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
import static org.hibernate.userguide.util.TransactionUtil.doInJPA;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Vlad Mihalcea
|
||||||
|
*/
|
||||||
|
public class ListenerTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?>[] getAnnotatedClasses() {
|
||||||
|
return new Class<?>[] {
|
||||||
|
Person.class,
|
||||||
|
Customer.class
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = SecurityException.class)
|
||||||
|
public void testLoadListener() {
|
||||||
|
Serializable customerId = 1L;
|
||||||
|
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
//tag::events-interceptors-load-listener-example[]
|
||||||
|
EntityManagerFactory entityManagerFactory = entityManagerFactory();
|
||||||
|
SessionFactoryImplementor sessionFactory = entityManagerFactory.unwrap( SessionFactoryImplementor.class );
|
||||||
|
sessionFactory
|
||||||
|
.getServiceRegistry()
|
||||||
|
.getService( EventListenerRegistry.class )
|
||||||
|
.prependListeners( EventType.LOAD, new SecuredLoadEntityListener() );
|
||||||
|
|
||||||
|
Customer customer = entityManager.find( Customer.class, customerId );
|
||||||
|
//end::events-interceptors-load-listener-example[]
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJPACallback() {
|
||||||
|
Long personId = 1L;
|
||||||
|
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
Person person = new Person();
|
||||||
|
person.id = personId;
|
||||||
|
person.name = "John Doe";
|
||||||
|
person.dateOfBirth = Timestamp.valueOf(LocalDateTime.of( 2000, 1, 1, 0, 0, 0 ));
|
||||||
|
entityManager.persist( person );
|
||||||
|
} );
|
||||||
|
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
Person person = entityManager.find( Person.class, personId );
|
||||||
|
assertTrue(person.age > 0);
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity(name = "Customer")
|
||||||
|
public static class Customer {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public Customer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Customer(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//tag::events-jpa-callbacks-example[]
|
||||||
|
@Entity
|
||||||
|
@EntityListeners( LastUpdateListener.class )
|
||||||
|
public static class Person {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private Date dateOfBirth;
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private long age;
|
||||||
|
|
||||||
|
private Date lastUpdate;
|
||||||
|
|
||||||
|
public void setLastUpdate(Date lastUpdate) {
|
||||||
|
this.lastUpdate = lastUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the transient property at load time based on a calculation.
|
||||||
|
* Note that a native Hibernate formula mapping is better for this purpose.
|
||||||
|
*/
|
||||||
|
@PostLoad
|
||||||
|
public void calculateAge() {
|
||||||
|
age = ChronoUnit.YEARS.between( LocalDateTime.ofInstant(
|
||||||
|
Instant.ofEpochMilli( dateOfBirth.getTime()), ZoneOffset.UTC),
|
||||||
|
LocalDateTime.now()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LastUpdateListener {
|
||||||
|
|
||||||
|
@PreUpdate
|
||||||
|
@PrePersist
|
||||||
|
public void setLastUpdate( Person p ) {
|
||||||
|
p.setLastUpdate( new Date() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//end::events-jpa-callbacks-example[]
|
||||||
|
|
||||||
|
//tag::events-interceptors-example[]
|
||||||
|
public static class SecuredLoadEntityListener implements LoadEventListener {
|
||||||
|
// this is the single method defined by the LoadEventListener interface
|
||||||
|
public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType)
|
||||||
|
throws HibernateException {
|
||||||
|
if ( !Principal.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) {
|
||||||
|
throw new SecurityException( "Unauthorized access" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//end::events-Principal-example[]
|
||||||
|
public static class Principal {
|
||||||
|
public static boolean isAuthorized(String clazz, Serializable id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//tag::events-declarative-security-jacc-example[]
|
||||||
|
public static class JaccIntegrator implements ServiceContributingIntegrator {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger( JaccIntegrator.class );
|
||||||
|
|
||||||
|
private static final DuplicationStrategy DUPLICATION_STRATEGY =
|
||||||
|
new DuplicationStrategy() {
|
||||||
|
@Override
|
||||||
|
public boolean areMatch(Object listener, Object original) {
|
||||||
|
return listener.getClass().equals( original.getClass() ) &&
|
||||||
|
JaccSecurityListener.class.isInstance( original );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Action getAction() {
|
||||||
|
return Action.KEEP_ORIGINAL;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void prepareServices(
|
||||||
|
StandardServiceRegistryBuilder serviceRegistryBuilder) {
|
||||||
|
boolean isSecurityEnabled = serviceRegistryBuilder
|
||||||
|
.getSettings().containsKey( AvailableSettings.JACC_ENABLED );
|
||||||
|
final JaccService jaccService = isSecurityEnabled ?
|
||||||
|
new StandardJaccServiceImpl() : new DisabledJaccServiceImpl();
|
||||||
|
serviceRegistryBuilder.addService( JaccService.class, jaccService );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void integrate(
|
||||||
|
Metadata metadata,
|
||||||
|
SessionFactoryImplementor sessionFactory,
|
||||||
|
SessionFactoryServiceRegistry serviceRegistry) {
|
||||||
|
doIntegration(
|
||||||
|
serviceRegistry
|
||||||
|
.getService( ConfigurationService.class ).getSettings(),
|
||||||
|
// pass no permissions here, because atm actually injecting the
|
||||||
|
// permissions into the JaccService is handled on SessionFactoryImpl via
|
||||||
|
// the org.hibernate.boot.cfgxml.spi.CfgXmlAccessService
|
||||||
|
null,
|
||||||
|
serviceRegistry
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doIntegration(
|
||||||
|
Map properties,
|
||||||
|
JaccPermissionDeclarations permissionDeclarations,
|
||||||
|
SessionFactoryServiceRegistry serviceRegistry) {
|
||||||
|
boolean isSecurityEnabled = properties
|
||||||
|
.containsKey( AvailableSettings.JACC_ENABLED );
|
||||||
|
if ( ! isSecurityEnabled ) {
|
||||||
|
log.debug( "Skipping JACC integration as it was not enabled" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String contextId = (String) properties
|
||||||
|
.get( AvailableSettings.JACC_CONTEXT_ID );
|
||||||
|
if ( contextId == null ) {
|
||||||
|
throw new IntegrationException( "JACC context id must be specified" );
|
||||||
|
}
|
||||||
|
|
||||||
|
final JaccService jaccService = serviceRegistry
|
||||||
|
.getService( JaccService.class );
|
||||||
|
if ( jaccService == null ) {
|
||||||
|
throw new IntegrationException( "JaccService was not set up" );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( permissionDeclarations != null ) {
|
||||||
|
for ( GrantedPermission declaration : permissionDeclarations
|
||||||
|
.getPermissionDeclarations() ) {
|
||||||
|
jaccService.addPermission( declaration );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final EventListenerRegistry eventListenerRegistry =
|
||||||
|
serviceRegistry.getService( EventListenerRegistry.class );
|
||||||
|
eventListenerRegistry.addDuplicationStrategy( DUPLICATION_STRATEGY );
|
||||||
|
|
||||||
|
eventListenerRegistry.prependListeners(
|
||||||
|
EventType.PRE_DELETE, new JaccPreDeleteEventListener() );
|
||||||
|
eventListenerRegistry.prependListeners(
|
||||||
|
EventType.PRE_INSERT, new JaccPreInsertEventListener() );
|
||||||
|
eventListenerRegistry.prependListeners(
|
||||||
|
EventType.PRE_UPDATE, new JaccPreUpdateEventListener() );
|
||||||
|
eventListenerRegistry.prependListeners(
|
||||||
|
EventType.PRE_LOAD, new JaccPreLoadEventListener() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disintegrate(SessionFactoryImplementor sessionFactory,
|
||||||
|
SessionFactoryServiceRegistry serviceRegistry) {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//end::events-declarative-security-jacc-example[]
|
||||||
|
}
|
|
@ -49,3 +49,4 @@ log4j.logger.org.hibernate.tool.hbm2ddl=info
|
||||||
### provide information about merged entity copies.
|
### provide information about merged entity copies.
|
||||||
#log4j.logger.org.hibernate.event.internal.EntityCopyAllowedLoggedObserver=warn
|
#log4j.logger.org.hibernate.event.internal.EntityCopyAllowedLoggedObserver=warn
|
||||||
|
|
||||||
|
log4j.logger.org.hibernate.userguide=debug
|
||||||
|
|
Loading…
Reference in New Issue