HHH-10496 Allow RevisionTimestamp to be java.time.LocalDateTime
This commit is contained in:
parent
267a1cdb46
commit
1abf044f2e
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.envers;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -174,6 +175,23 @@ public interface AuditReader {
|
|||
Number getRevisionNumberForDate(Date date) throws IllegalStateException, RevisionDoesNotExistException,
|
||||
IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Gets the revision number, that corresponds to the given date. More precisely, returns
|
||||
* the number of the highest revision, which was created on or before the given date. So:
|
||||
* <code>getRevisionDate(getRevisionNumberForDate(date)) <= date</code> and
|
||||
* <code>getRevisionDate(getRevisionNumberForDate(date)+1) > date</code>.
|
||||
*
|
||||
* @param date Date for which to get the revision.
|
||||
*
|
||||
* @return Revision number corresponding to the given date.
|
||||
*
|
||||
* @throws IllegalStateException If the associated entity manager is closed.
|
||||
* @throws RevisionDoesNotExistException If the given date is before the first revision.
|
||||
* @throws IllegalArgumentException If <code>date</code> is <code>null</code>.
|
||||
*/
|
||||
Number getRevisionNumberForDate(LocalDateTime date) throws IllegalStateException,
|
||||
RevisionDoesNotExistException, IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* A helper method; should be used only if a custom revision entity is used. See also {@link RevisionEntity}.
|
||||
*
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.envers.configuration.internal;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
@ -36,14 +37,17 @@ import org.hibernate.envers.enhanced.OrderedSequenceGenerator;
|
|||
import org.hibernate.envers.enhanced.SequenceIdRevisionEntity;
|
||||
import org.hibernate.envers.enhanced.SequenceIdTrackingModifiedEntitiesRevisionEntity;
|
||||
import org.hibernate.envers.internal.entities.PropertyData;
|
||||
import org.hibernate.envers.internal.entities.RevisionTimestampData;
|
||||
import org.hibernate.envers.internal.revisioninfo.DefaultRevisionInfoGenerator;
|
||||
import org.hibernate.envers.internal.revisioninfo.DefaultTrackingModifiedEntitiesRevisionInfoGenerator;
|
||||
import org.hibernate.envers.internal.revisioninfo.ModifiedEntityNamesReader;
|
||||
import org.hibernate.envers.internal.revisioninfo.RevisionInfoGenerator;
|
||||
import org.hibernate.envers.internal.revisioninfo.RevisionInfoNumberReader;
|
||||
import org.hibernate.envers.internal.revisioninfo.RevisionInfoQueryCreator;
|
||||
import org.hibernate.envers.internal.revisioninfo.RevisionTimestampValueResolver;
|
||||
import org.hibernate.mapping.PersistentClass;
|
||||
import org.hibernate.mapping.Property;
|
||||
import org.hibernate.service.ServiceRegistry;
|
||||
|
||||
/**
|
||||
* @author Adam Warski (adam at warski dot org)
|
||||
|
@ -103,8 +107,7 @@ public class RevisionInfoConfiguration {
|
|||
revisionInfoQueryCreator = new RevisionInfoQueryCreator(
|
||||
resolver.revisionInfoEntityName,
|
||||
resolver.revisionInfoIdData.getName(),
|
||||
resolver.revisionInfoTimestampData.getName(),
|
||||
isTimestampAsDate( revisionInfoTimestampTypeName )
|
||||
resolver.timestampValueResolver
|
||||
);
|
||||
|
||||
if ( configuration.isTrackEntitiesChanged() ) {
|
||||
|
@ -230,14 +233,27 @@ public class RevisionInfoConfiguration {
|
|||
return mapping;
|
||||
}
|
||||
|
||||
private boolean isTimestampAsDate(String typeName) {
|
||||
return "date".equals( typeName ) || "time".equals( typeName ) || "timestamp".equals( typeName );
|
||||
}
|
||||
|
||||
private org.hibernate.envers.boot.model.Column createColumn(String name, String type) {
|
||||
return new org.hibernate.envers.boot.model.Column( name, null, null, null, type, null, null );
|
||||
}
|
||||
|
||||
private RevisionTimestampValueResolver createRevisionTimestampResolver(
|
||||
Class<?> revisionInfoClass,
|
||||
PropertyData revisionInfoTimestampData,
|
||||
String typeName,
|
||||
ServiceRegistry serviceRegistry) {
|
||||
return new RevisionTimestampValueResolver(
|
||||
revisionInfoClass,
|
||||
new RevisionTimestampData(
|
||||
revisionInfoTimestampData.getName(),
|
||||
revisionInfoTimestampData.getBeanName(),
|
||||
revisionInfoTimestampData.getAccessType(),
|
||||
typeName
|
||||
),
|
||||
serviceRegistry
|
||||
);
|
||||
}
|
||||
|
||||
private class RevisionEntityResolver {
|
||||
|
||||
private final MetadataImplementor metadata;
|
||||
|
@ -258,6 +274,7 @@ public class RevisionInfoConfiguration {
|
|||
private String revisionInfoTimestampTypeName;
|
||||
private String revisionPropType;
|
||||
private String revisionPropSqlType;
|
||||
private RevisionTimestampValueResolver timestampValueResolver;
|
||||
|
||||
public RevisionEntityResolver(MetadataImplementor metadata, ReflectionManager reflectionManager) {
|
||||
this.metadata = metadata;
|
||||
|
@ -328,7 +345,12 @@ public class RevisionInfoConfiguration {
|
|||
final Property timestampProperty = persistentClass.getProperty( revisionInfoTimestampData.getName() );
|
||||
revisionInfoTimestampTypeName = timestampProperty.getType().getName();
|
||||
|
||||
boolean timestampAsDate = isTimestampAsDate( revisionInfoTimestampTypeName );
|
||||
timestampValueResolver = createRevisionTimestampResolver(
|
||||
revisionInfoClass,
|
||||
revisionInfoTimestampData,
|
||||
revisionInfoTimestampTypeName,
|
||||
metadata.getMetadataBuildingOptions().getServiceRegistry()
|
||||
);
|
||||
|
||||
if ( useEntityTrackingRevisionEntity( revisionInfoClass ) ) {
|
||||
// If tracking modified entities is enabled, custom revision info entity is a subtype
|
||||
|
@ -337,8 +359,7 @@ public class RevisionInfoConfiguration {
|
|||
revisionInfoEntityName,
|
||||
revisionInfoClass,
|
||||
revisionListenerClass,
|
||||
revisionInfoTimestampData,
|
||||
timestampAsDate,
|
||||
timestampValueResolver,
|
||||
modifiedEntityNamesData,
|
||||
metadata.getMetadataBuildingOptions().getServiceRegistry()
|
||||
);
|
||||
|
@ -349,8 +370,7 @@ public class RevisionInfoConfiguration {
|
|||
revisionInfoEntityName,
|
||||
revisionInfoClass,
|
||||
revisionListenerClass,
|
||||
revisionInfoTimestampData,
|
||||
timestampAsDate,
|
||||
timestampValueResolver,
|
||||
metadata.getMetadataBuildingOptions().getServiceRegistry()
|
||||
);
|
||||
}
|
||||
|
@ -359,33 +379,42 @@ public class RevisionInfoConfiguration {
|
|||
if ( revisionInfoGenerator == null ) {
|
||||
|
||||
revisionListenerClass = getRevisionListenerClass( RevisionListener.class );
|
||||
boolean timestampAsDate = isTimestampAsDate( revisionInfoTimestampTypeName );
|
||||
|
||||
if ( configuration.isTrackEntitiesChanged() ) {
|
||||
revisionInfoClass = configuration.isNativeIdEnabled()
|
||||
? DefaultTrackingModifiedEntitiesRevisionEntity.class
|
||||
: SequenceIdTrackingModifiedEntitiesRevisionEntity.class;
|
||||
revisionInfoEntityName = revisionInfoClass.getName();
|
||||
revisionInfoGenerator = new DefaultTrackingModifiedEntitiesRevisionInfoGenerator(
|
||||
revisionInfoEntityName,
|
||||
revisionInfoClass,
|
||||
revisionListenerClass,
|
||||
revisionInfoTimestampData,
|
||||
timestampAsDate,
|
||||
modifiedEntityNamesData,
|
||||
metadata.getMetadataBuildingOptions().getServiceRegistry()
|
||||
);
|
||||
}
|
||||
else {
|
||||
revisionInfoClass = configuration.isNativeIdEnabled()
|
||||
? DefaultRevisionEntity.class
|
||||
: SequenceIdRevisionEntity.class;
|
||||
}
|
||||
|
||||
timestampValueResolver = createRevisionTimestampResolver(
|
||||
revisionInfoClass,
|
||||
revisionInfoTimestampData,
|
||||
revisionInfoTimestampTypeName,
|
||||
metadata.getMetadataBuildingOptions().getServiceRegistry()
|
||||
);
|
||||
|
||||
if ( configuration.isTrackEntitiesChanged() ) {
|
||||
revisionInfoGenerator = new DefaultTrackingModifiedEntitiesRevisionInfoGenerator(
|
||||
revisionInfoEntityName,
|
||||
revisionInfoClass,
|
||||
revisionListenerClass,
|
||||
timestampValueResolver,
|
||||
modifiedEntityNamesData,
|
||||
metadata.getMetadataBuildingOptions().getServiceRegistry()
|
||||
);
|
||||
}
|
||||
else {
|
||||
revisionInfoGenerator = new DefaultRevisionInfoGenerator(
|
||||
revisionInfoEntityName,
|
||||
revisionInfoClass,
|
||||
revisionListenerClass,
|
||||
revisionInfoTimestampData,
|
||||
timestampAsDate,
|
||||
timestampValueResolver,
|
||||
metadata.getMetadataBuildingOptions().getServiceRegistry()
|
||||
);
|
||||
}
|
||||
|
@ -464,12 +493,12 @@ public class RevisionInfoConfiguration {
|
|||
}
|
||||
|
||||
final XClass propertyType = property.getType();
|
||||
if ( isAnyType( propertyType, Long.class, Long.TYPE, Date.class, java.sql.Date.class ) ) {
|
||||
if ( isAnyType( propertyType, Long.class, Long.TYPE, Date.class, LocalDateTime.class, java.sql.Date.class ) ) {
|
||||
revisionInfoTimestampData = createPropertyData( property, accessType );
|
||||
revisionTimestampFound = true;
|
||||
}
|
||||
else {
|
||||
throwUnexpectedAnnotatedType( property, RevisionTimestamp.class, "long, Long, Date, or java.sql.Date" );
|
||||
throwUnexpectedAnnotatedType( property, RevisionTimestamp.class, "long, Long, Date, LocalDateTime, or java.sql.Date" );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,27 +6,39 @@
|
|||
*/
|
||||
package org.hibernate.envers.exception;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author Adam Warski (adam at warski dot org)
|
||||
* @author Chris Cranford
|
||||
*/
|
||||
public class RevisionDoesNotExistException extends AuditException {
|
||||
private static final long serialVersionUID = -6417768274074962282L;
|
||||
|
||||
private final Number revision;
|
||||
private final Date date;
|
||||
private final LocalDateTime localDateTime;
|
||||
|
||||
public RevisionDoesNotExistException(Number revision) {
|
||||
super( "Revision " + revision + " does not exist." );
|
||||
this.revision = revision;
|
||||
this.date = null;
|
||||
this.localDateTime = null;
|
||||
}
|
||||
|
||||
public RevisionDoesNotExistException(Date date) {
|
||||
super( "There is no revision before or at " + date + "." );
|
||||
this.date = date;
|
||||
this.revision = null;
|
||||
this.localDateTime = null;
|
||||
}
|
||||
|
||||
public RevisionDoesNotExistException(LocalDateTime localDateTime) {
|
||||
super( "There is no revision before or at " + localDateTime + "." );
|
||||
this.localDateTime = localDateTime;
|
||||
this.revision = null;
|
||||
this.date = null;
|
||||
}
|
||||
|
||||
public Number getRevision() {
|
||||
|
@ -36,4 +48,8 @@ public class RevisionDoesNotExistException extends AuditException {
|
|||
public Date getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public LocalDateTime getLocalDateTime() {
|
||||
return localDateTime;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ public class PropertyData {
|
|||
this.accessType = accessType;
|
||||
}
|
||||
|
||||
private PropertyData(String name, String beanName, String accessType, Type propertyType) {
|
||||
public PropertyData(String name, String beanName, String accessType, Type propertyType) {
|
||||
this( name, beanName, accessType );
|
||||
this.propertyType = propertyType;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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.envers.internal.entities;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.hibernate.type.Type;
|
||||
|
||||
/**
|
||||
* @author Chris Cranford
|
||||
* @author 6.0
|
||||
*/
|
||||
public class RevisionTimestampData extends PropertyData {
|
||||
|
||||
private final String typeName;
|
||||
|
||||
public RevisionTimestampData(String name, String beanName, String accessType, String typeName) {
|
||||
super( name, beanName, accessType );
|
||||
this.typeName = typeName;
|
||||
}
|
||||
|
||||
public RevisionTimestampData(RevisionTimestampData old, String typeName) {
|
||||
this( old.getName(), old.getBeanName(), old.getAccessType(), typeName );
|
||||
}
|
||||
|
||||
public String getTypeName() {
|
||||
return typeName;
|
||||
}
|
||||
|
||||
public boolean isTimestampDate() {
|
||||
return "date".equals( typeName )
|
||||
|| "time".equals( typeName )
|
||||
|| "timestamp".equals( typeName );
|
||||
}
|
||||
|
||||
public boolean isTimestampLocalDateTime() {
|
||||
return "LocalDateTime".equals( typeName );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + ( typeName != null ? typeName.hashCode() : 0 );
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
if (!super.equals(o)) {
|
||||
return false;
|
||||
}
|
||||
RevisionTimestampData that = (RevisionTimestampData) o;
|
||||
return Objects.equals( typeName, that.typeName );
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.envers.internal.reader;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
@ -204,6 +205,26 @@ public class AuditReaderImpl implements AuditReaderImplementor {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number getRevisionNumberForDate(LocalDateTime date) {
|
||||
checkNotNull( date, "Date of revision" );
|
||||
checkSession();
|
||||
|
||||
final Query<?> query = enversService.getRevisionInfoQueryCreator().getRevisionNumberForDateQuery( session, date );
|
||||
|
||||
try {
|
||||
final Number res = (Number) query.uniqueResult();
|
||||
if ( res == null ) {
|
||||
throw new RevisionDoesNotExistException( date );
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
catch (NonUniqueResultException e) {
|
||||
throw new AuditException( e );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public <T> T findRevision(Class<T> revisionEntityClass, Number revision)
|
||||
|
|
|
@ -7,18 +7,14 @@
|
|||
package org.hibernate.envers.internal.revisioninfo;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Date;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.envers.EntityTrackingRevisionListener;
|
||||
import org.hibernate.envers.RevisionListener;
|
||||
import org.hibernate.envers.RevisionType;
|
||||
import org.hibernate.envers.exception.AuditException;
|
||||
import org.hibernate.envers.internal.entities.PropertyData;
|
||||
import org.hibernate.envers.internal.synchronization.SessionCacheCleaner;
|
||||
import org.hibernate.envers.internal.tools.ReflectionTools;
|
||||
import org.hibernate.internal.util.ReflectHelper;
|
||||
import org.hibernate.property.access.spi.Setter;
|
||||
import org.hibernate.resource.beans.spi.ManagedBean;
|
||||
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
|
||||
import org.hibernate.service.ServiceRegistry;
|
||||
|
@ -31,10 +27,9 @@ import org.hibernate.service.ServiceRegistry;
|
|||
public class DefaultRevisionInfoGenerator implements RevisionInfoGenerator {
|
||||
private final String revisionInfoEntityName;
|
||||
private final ManagedBean<? extends RevisionListener> listenerManagedBean;
|
||||
private final Setter revisionTimestampSetter;
|
||||
private final boolean timestampAsDate;
|
||||
private final Constructor<?> revisionInfoClassConstructor;
|
||||
private final SessionCacheCleaner sessionCacheCleaner;
|
||||
private final RevisionTimestampValueResolver timestampValueResolver;
|
||||
|
||||
private RevisionInfoNumberReader revisionInfoNumberReader;
|
||||
|
||||
|
@ -42,14 +37,12 @@ public class DefaultRevisionInfoGenerator implements RevisionInfoGenerator {
|
|||
String revisionInfoEntityName,
|
||||
Class<?> revisionInfoClass,
|
||||
Class<? extends RevisionListener> listenerClass,
|
||||
PropertyData revisionInfoTimestampData,
|
||||
boolean timestampAsDate,
|
||||
RevisionTimestampValueResolver timestampValueResolver,
|
||||
ServiceRegistry serviceRegistry) {
|
||||
this.revisionInfoEntityName = revisionInfoEntityName;
|
||||
this.timestampAsDate = timestampAsDate;
|
||||
this.timestampValueResolver = timestampValueResolver;
|
||||
|
||||
this.revisionInfoClassConstructor = ReflectHelper.getDefaultConstructor( revisionInfoClass );
|
||||
this.revisionTimestampSetter = ReflectionTools.getSetter( revisionInfoClass, revisionInfoTimestampData, serviceRegistry );
|
||||
|
||||
this.listenerManagedBean = resolveRevisionListenerBean( listenerClass, serviceRegistry );
|
||||
|
||||
|
@ -80,8 +73,7 @@ public class DefaultRevisionInfoGenerator implements RevisionInfoGenerator {
|
|||
throw new RuntimeException( e );
|
||||
}
|
||||
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
revisionTimestampSetter.set( revisionInfo, timestampAsDate ? new Date( timestamp ) : timestamp, null );
|
||||
timestampValueResolver.resolveNow( revisionInfo );
|
||||
|
||||
if ( listenerManagedBean != null ) {
|
||||
listenerManagedBean.getBeanInstance().newRevision( revisionInfo );
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.hibernate.service.ServiceRegistry;
|
|||
* Automatically adds entity names, that have been changed during current revision, to revision entity.
|
||||
*
|
||||
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||
* @author Chris Cranford
|
||||
*
|
||||
* @see org.hibernate.envers.ModifiedEntityNames
|
||||
* @see org.hibernate.envers.DefaultTrackingModifiedEntitiesRevisionEntity
|
||||
|
@ -33,11 +34,10 @@ public class DefaultTrackingModifiedEntitiesRevisionInfoGenerator extends Defaul
|
|||
String revisionInfoEntityName,
|
||||
Class<?> revisionInfoClass,
|
||||
Class<? extends RevisionListener> listenerClass,
|
||||
PropertyData revisionInfoTimestampData,
|
||||
boolean timestampAsDate,
|
||||
RevisionTimestampValueResolver timestampValueResolver,
|
||||
PropertyData modifiedEntityNamesData,
|
||||
ServiceRegistry serviceRegistry) {
|
||||
super( revisionInfoEntityName, revisionInfoClass, listenerClass, revisionInfoTimestampData, timestampAsDate, serviceRegistry );
|
||||
super( revisionInfoEntityName, revisionInfoClass, listenerClass, timestampValueResolver, serviceRegistry );
|
||||
modifiedEntityNamesSetter = ReflectionTools.getSetter( revisionInfoClass, modifiedEntityNamesData, serviceRegistry );
|
||||
modifiedEntityNamesGetter = ReflectionTools.getGetter( revisionInfoClass, modifiedEntityNamesData, serviceRegistry );
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.envers.internal.revisioninfo;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
@ -28,16 +29,15 @@ public class RevisionInfoQueryCreator {
|
|||
|
||||
private final String revisionInfoEntityName;
|
||||
private final String revisionInfoIdName;
|
||||
private final String revisionInfoTimestampName;
|
||||
private final boolean timestampAsDate;
|
||||
private final RevisionTimestampValueResolver timestampValueResolver;
|
||||
|
||||
public RevisionInfoQueryCreator(
|
||||
String revisionInfoEntityName, String revisionInfoIdName,
|
||||
String revisionInfoTimestampName, boolean timestampAsDate) {
|
||||
String revisionInfoEntityName,
|
||||
String revisionInfoIdName,
|
||||
RevisionTimestampValueResolver timestampValueResolver) {
|
||||
this.revisionInfoEntityName = revisionInfoEntityName;
|
||||
this.revisionInfoIdName = revisionInfoIdName;
|
||||
this.revisionInfoTimestampName = revisionInfoTimestampName;
|
||||
this.timestampAsDate = timestampAsDate;
|
||||
this.timestampValueResolver = timestampValueResolver;
|
||||
}
|
||||
|
||||
public Query<?> getRevisionDateQuery(Session session, Number revision) {
|
||||
|
@ -45,7 +45,7 @@ public class RevisionInfoQueryCreator {
|
|||
String.format(
|
||||
Locale.ENGLISH,
|
||||
REVISION_DATE_QUERY,
|
||||
revisionInfoTimestampName,
|
||||
timestampValueResolver.getName(),
|
||||
revisionInfoEntityName,
|
||||
revisionInfoIdName
|
||||
)
|
||||
|
@ -59,9 +59,21 @@ public class RevisionInfoQueryCreator {
|
|||
REVISION_NUMBER_FOR_DATE_QUERY,
|
||||
revisionInfoIdName,
|
||||
revisionInfoEntityName,
|
||||
revisionInfoTimestampName
|
||||
timestampValueResolver.getName()
|
||||
)
|
||||
).setParameter( REVISION_NUMBER_FOR_DATE_QUERY_PARAMETER, timestampAsDate ? date : date.getTime() );
|
||||
).setParameter( REVISION_NUMBER_FOR_DATE_QUERY_PARAMETER, timestampValueResolver.resolveByValue( date ) );
|
||||
}
|
||||
|
||||
public Query<?> getRevisionNumberForDateQuery(Session session, LocalDateTime localDateTime) {
|
||||
return session.createQuery(
|
||||
String.format(
|
||||
Locale.ENGLISH,
|
||||
REVISION_NUMBER_FOR_DATE_QUERY,
|
||||
revisionInfoIdName,
|
||||
revisionInfoEntityName,
|
||||
timestampValueResolver.getName()
|
||||
)
|
||||
).setParameter( REVISION_NUMBER_FOR_DATE_QUERY_PARAMETER, timestampValueResolver.resolveByValue( localDateTime ) );
|
||||
}
|
||||
|
||||
public Query<?> getRevisionsQuery(Session session, Set<Number> revisions) {
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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.envers.internal.revisioninfo;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
|
||||
import org.hibernate.envers.internal.entities.RevisionTimestampData;
|
||||
import org.hibernate.envers.internal.tools.ReflectionTools;
|
||||
import org.hibernate.property.access.spi.Setter;
|
||||
import org.hibernate.service.ServiceRegistry;
|
||||
|
||||
/**
|
||||
* @author Chris Cranford
|
||||
* @since 6.0
|
||||
*/
|
||||
public class RevisionTimestampValueResolver {
|
||||
|
||||
private final RevisionTimestampData timestampData;
|
||||
private final Setter revisionTimestampSetter;
|
||||
|
||||
public RevisionTimestampValueResolver(Class<?> revisionInfoClass, RevisionTimestampData timestampData, ServiceRegistry serviceRegistry) {
|
||||
this.timestampData = timestampData;
|
||||
this.revisionTimestampSetter = ReflectionTools.getSetter( revisionInfoClass, timestampData, serviceRegistry );
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return timestampData.getName();
|
||||
}
|
||||
|
||||
public void resolveNow(Object object) {
|
||||
if ( timestampData.isTimestampDate() ) {
|
||||
revisionTimestampSetter.set( object, new Date(), null );
|
||||
}
|
||||
else if ( timestampData.isTimestampLocalDateTime() ) {
|
||||
revisionTimestampSetter.set(object, LocalDateTime.now(), null );
|
||||
}
|
||||
else {
|
||||
revisionTimestampSetter.set( object, System.currentTimeMillis(), null );
|
||||
}
|
||||
}
|
||||
|
||||
public Object resolveByValue(Date date) {
|
||||
if ( date != null ) {
|
||||
if ( timestampData.isTimestampDate() ) {
|
||||
return date;
|
||||
}
|
||||
else if ( timestampData.isTimestampLocalDateTime() ) {
|
||||
return LocalDateTime.ofInstant( date.toInstant(), ZoneId.systemDefault() );
|
||||
}
|
||||
else {
|
||||
return date.getTime();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object resolveByValue(LocalDateTime localDateTime) {
|
||||
if ( localDateTime != null ) {
|
||||
if ( timestampData.isTimestampDate() ) {
|
||||
return Date.from( localDateTime.atZone( ZoneId.systemDefault() ).toInstant() );
|
||||
}
|
||||
else if ( timestampData.isTimestampLocalDateTime() ) {
|
||||
return localDateTime;
|
||||
}
|
||||
else {
|
||||
return localDateTime.atZone( ZoneId.systemDefault() ).toInstant().getEpochSecond();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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.envers.test.entities.reventity;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
import org.hibernate.annotations.Parameter;
|
||||
import org.hibernate.envers.RevisionEntity;
|
||||
import org.hibernate.envers.RevisionNumber;
|
||||
import org.hibernate.envers.RevisionTimestamp;
|
||||
|
||||
/**
|
||||
* @author Chris Cranford
|
||||
*/
|
||||
@Entity
|
||||
@GenericGenerator(name = "EnversTestingRevisionGenerator",
|
||||
strategy = "org.hibernate.id.enhanced.TableGenerator",
|
||||
parameters = {
|
||||
@Parameter(name = "table_name", value = "REVISION_GENERATOR"),
|
||||
@Parameter(name = "initial_value", value = "1"),
|
||||
@Parameter(name = "increment_size", value = "1"),
|
||||
@Parameter(name = "prefer_entity_table_as_segment_value", value = "true")
|
||||
}
|
||||
)
|
||||
@RevisionEntity
|
||||
public class CustomLocalDateTimeRevEntity {
|
||||
@Id
|
||||
@GeneratedValue(generator = "EnversTestingRevisionGenerator")
|
||||
@RevisionNumber
|
||||
private int id;
|
||||
|
||||
@RevisionTimestamp
|
||||
private LocalDateTime localDateTimestamp;
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public LocalDateTime getLocalDateTimestamp() {
|
||||
return localDateTimestamp;
|
||||
}
|
||||
|
||||
public void setLocalDateTimestamp(LocalDateTime localDateTimestamp) {
|
||||
this.localDateTimestamp = localDateTimestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = id;
|
||||
result = 31 * result + ( localDateTimestamp != null ? localDateTimestamp.hashCode() : 0 );
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
CustomLocalDateTimeRevEntity that = (CustomLocalDateTimeRevEntity) o;
|
||||
return id == that.id && Objects.equals(localDateTimestamp, that.localDateTimestamp);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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.orm.test.envers.integration.reventity;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
import org.hibernate.envers.test.entities.reventity.CustomLocalDateTimeRevEntity;
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
|
||||
import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase;
|
||||
import org.hibernate.orm.test.envers.Priority;
|
||||
import org.hibernate.orm.test.envers.entities.StrTestEntity;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
|
||||
/**
|
||||
* @author Chris Cranford
|
||||
*/
|
||||
@TestForIssue( jiraKey = "HHH-10496" )
|
||||
public class LocalDateTimeTest extends BaseEnversJPAFunctionalTestCase {
|
||||
private Instant timestampStart;
|
||||
private Instant timestampEnd;
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class<?>[] {
|
||||
StrTestEntity.class,
|
||||
CustomLocalDateTimeRevEntity.class
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
@Priority(10)
|
||||
public void initData() {
|
||||
EntityManager em = getEntityManager();
|
||||
try {
|
||||
timestampStart = Instant.now();
|
||||
|
||||
// some DBMs truncate time to seconds.
|
||||
Thread.sleep( 1100 );
|
||||
|
||||
StrTestEntity entity = new StrTestEntity( "x" );
|
||||
|
||||
// Revision 1
|
||||
em.getTransaction().begin();
|
||||
em.persist( entity );
|
||||
em.getTransaction().commit();
|
||||
|
||||
timestampEnd = Instant.now();
|
||||
}
|
||||
catch( InterruptedException x ) {
|
||||
fail( "Unexpected interrupted exception" );
|
||||
}
|
||||
finally {
|
||||
em.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTimestampsUsingDate() {
|
||||
// expect just one revision prior to this timestamp.
|
||||
assertEquals( 1, getAuditReader().getRevisionNumberForDate( Date.from( timestampEnd ) ) );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRevisionEntityLocalDateTime() {
|
||||
// get revision
|
||||
CustomLocalDateTimeRevEntity revInfo = getAuditReader().findRevision( CustomLocalDateTimeRevEntity.class, 1 );
|
||||
assertNotNull( revInfo );
|
||||
// verify started before revision timestamp
|
||||
final LocalDateTime started = LocalDateTime.ofInstant( timestampStart, ZoneId.systemDefault() );
|
||||
assertTrue( started.isBefore( revInfo.getLocalDateTimestamp() ) );
|
||||
// verify ended after revision timestamp
|
||||
final LocalDateTime ended = LocalDateTime.ofInstant( timestampEnd, ZoneId.systemDefault() );
|
||||
assertTrue( ended.isAfter( revInfo.getLocalDateTimestamp() ) );
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue