diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/BasicMetadataGenerator.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/BasicMetadataGenerator.java index 3e6f69e7767a..3fb8bf79c320 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/BasicMetadataGenerator.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/metadata/BasicMetadataGenerator.java @@ -57,10 +57,7 @@ boolean addBasic( final boolean addNestedType = (value instanceof SimpleValue) && ((SimpleValue) value).getTypeParameters() != null; - String typeName = type.getName(); - if ( typeName == null ) { - typeName = type.getClass().getName(); - } + final String typeName = resolveType( value ); final Element propMapping = MetadataTools.addProperty( parent, @@ -106,6 +103,21 @@ boolean addBasic( return true; } + private String resolveType(Value value) { + final Type type = value.getType(); + String typeName = null; + if ( value instanceof SimpleValue ) { + typeName = ( (SimpleValue) value ).getTypeName(); + } + if ( typeName == null ) { + typeName = type.getName(); + } + if ( typeName == null ) { + typeName = type.getClass().getName(); + } + return typeName; + } + private void mapEnumerationType(Element parent, Type type, Properties parameters) { if ( parameters.getProperty( EnumType.ENUM ) != null ) { parent.addElement( "param" ) diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/CalendarEntity.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/CalendarEntity.java new file mode 100644 index 000000000000..900d74c4b02d --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/CalendarEntity.java @@ -0,0 +1,118 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2014, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.integration.customtype; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import java.io.Serializable; +import java.util.Calendar; + +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.hibernate.envers.Audited; +import org.hibernate.type.CalendarDateType; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@Entity +@Audited +@TypeDef(name = "hibernate_calendar_date", typeClass = CalendarDateType.class) +public class CalendarEntity implements Serializable { + @Id + @GeneratedValue + private Long id = null; + + @Type(type = "hibernate_calendar_date") + private Calendar dayOne = Calendar.getInstance(); // org.hibernate.type.CalendarDateType + + private Calendar dayTwo = Calendar.getInstance(); // org.hibernate.envers.test.integration.customtype.UTCCalendarType + + public CalendarEntity() { + } + + public CalendarEntity(Calendar dayOne, Calendar dayTwo) { + this.dayOne = dayOne; + this.dayTwo = dayTwo; + } + + public CalendarEntity(Long id, Calendar dayOne, Calendar dayTwo) { + this.id = id; + this.dayOne = dayOne; + this.dayTwo = dayTwo; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + if ( !( o instanceof CalendarEntity ) ) return false; + + CalendarEntity that = (CalendarEntity) o; + + if ( dayOne != null ? !dayOne.equals( that.dayOne ) : that.dayOne != null ) return false; + if ( dayTwo != null ? !dayTwo.equals( that.dayTwo ) : that.dayTwo != null ) return false; + if ( id != null ? !id.equals( that.id ) : that.id != null ) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id != null ? id.hashCode() : 0; + result = 31 * result + (dayOne != null ? dayOne.hashCode() : 0); + result = 31 * result + (dayTwo != null ? dayTwo.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "CalendarEntity(id = " + id + ", dayOne = " + dayOne + ", dayTwo = " + dayTwo + ")"; + } + + public Calendar getDayOne() { + return dayOne; + } + + public void setDayOne(Calendar dayOne) { + this.dayOne = dayOne; + } + + public Calendar getDayTwo() { + return dayTwo; + } + + public void setDayTwo(Calendar dayTwo) { + this.dayTwo = dayTwo; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/DefaultTypeTest.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/DefaultTypeTest.java new file mode 100644 index 000000000000..99f642ff3508 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/DefaultTypeTest.java @@ -0,0 +1,80 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2014, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.integration.customtype; + +import org.hibernate.Session; +import org.hibernate.envers.test.BaseEnversFunctionalTestCase; +import org.hibernate.envers.test.Priority; + +import org.junit.Assert; +import org.junit.Test; + +import org.hibernate.testing.TestForIssue; + +/** + * @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com) + */ +@TestForIssue( jiraKey = "HHH-8602" ) +public class DefaultTypeTest extends BaseEnversFunctionalTestCase { + private Long id = null; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { CalendarEntity.class }; + } + + @Override + protected String[] getAnnotatedPackages() { + return new String[] { "org.hibernate.envers.test.integration.customtype" }; + } + + @Test + @Priority(10) + public void initData() { + Session session = openSession(); + + // Revision 1 + session.getTransaction().begin(); + final CalendarEntity entity = new CalendarEntity(); + entity.getDayOne().set( 2013, java.util.Calendar.DECEMBER, 24, 0, 0, 0 ); + entity.getDayTwo().set( 2013, java.util.Calendar.DECEMBER, 24, 0, 0, 0 ); + session.persist( entity ); + session.getTransaction().commit(); + + id = entity.getId(); + + session.close(); + } + + @Test + public void testDateEqualityWithCurrentObject() { + final CalendarEntity ver1 = getAuditReader().find( CalendarEntity.class, id, 1 ); + final Session session = openSession(); + final CalendarEntity current = (CalendarEntity) session.get( CalendarEntity.class, id ); + session.close(); + + Assert.assertEquals( current.getDayOne(), ver1.getDayOne() ); + Assert.assertEquals( current.getDayTwo(), ver1.getDayTwo() ); + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/UTCCalendarType.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/UTCCalendarType.java new file mode 100644 index 000000000000..84c83af7bd35 --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/UTCCalendarType.java @@ -0,0 +1,140 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2014, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.envers.test.integration.customtype; + +import java.io.Serializable; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Calendar; +import java.util.TimeZone; + +import org.hibernate.HibernateException; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.type.CalendarType; +import org.hibernate.usertype.UserType; + +/** + * @author Sebastian Götz (s got goetz at inform dot ch) + */ +public class UTCCalendarType implements UserType { + private static final TimeZone timeZone = TimeZone.getTimeZone( "UTC" ); + + @Override + public int[] sqlTypes() { + return new int[] { Types.TIMESTAMP }; + } + + @Override + public Class returnedClass() { + return Calendar.class; + } + + @Override + public boolean equals(Object x, Object y) throws HibernateException { + if ( x == y ) { + return true; + } + if ( x == null || y == null ) { + return false; + } + return x.equals( y ); + } + + @Override + public int hashCode(Object x) throws HibernateException { + return x.hashCode(); + } + + @Override + public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException { + /* + * Delegate the nullSafeGet to the Hibernate CalendarType since we ensured upon insert that the value is in UTC. + * + * ATTENTION: Hibernate CalendarType will read the date value into a Calendar instance generated by + * Calendar.getInstance(). So the resulting calendar will have the system's default time zone but the value + * actually is in UTC since we corrected it upon insertion already. So we just have to ensure, that the time + * zone of the calendar is being set to UTC. Otherwise when it comes to insertion/update again we would subtract + * the offset again which would lead to incorrect values. + */ + Calendar calendar = CalendarType.INSTANCE.nullSafeGet( rs, names[0], session ); + + if (calendar != null) { + calendar.setTimeZone( timeZone ); + } + + return calendar; + } + + @Override + public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException { + if ( value instanceof Calendar ) { + value = convertToTimeZone( (Calendar) value, timeZone ); + } + CalendarType.INSTANCE.nullSafeSet( st, value, index, session ); + } + + @Override + public Object deepCopy(Object value) throws HibernateException { + if ( value == null ) { + return null; + } + return ( (Calendar) value ).clone(); + } + + @Override + public boolean isMutable() { + return true; + } + + @Override + public Serializable disassemble(Object value) throws HibernateException { + return (Serializable) deepCopy( value ); + } + + @Override + public Object assemble(Serializable cached, Object owner) throws HibernateException { + return deepCopy( cached ); + } + + @Override + public Object replace(Object original, Object target, Object owner) throws HibernateException { + return deepCopy( original ); + } + + public static Calendar convertToTimeZone(Calendar calendar, TimeZone timeZone) { + if ( calendar != null ) { + final TimeZone currentTimeZone = calendar.getTimeZone(); + if ( !currentTimeZone.equals( timeZone ) ) { + int currentOffset = currentTimeZone.getOffset( calendar.getTimeInMillis() ); + int targetOffset = timeZone.getOffset( calendar.getTimeInMillis() ); + int offset = currentOffset - targetOffset; + calendar.add( Calendar.MILLISECOND, -offset ); + calendar.setTimeZone( timeZone ); + } + } + return calendar; + } +} diff --git a/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/package-info.java b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/package-info.java new file mode 100644 index 000000000000..b902322b0dec --- /dev/null +++ b/hibernate-envers/src/test/java/org/hibernate/envers/test/integration/customtype/package-info.java @@ -0,0 +1,30 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2014, Red Hat Inc. or third-party contributors as + * indicated by the @author tags or express copyright attribution + * statements applied by the authors. All third-party contributions are + * distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +@TypeDef(defaultForType = Calendar.class, name = "calendar_date", typeClass = UTCCalendarType.class) +// HHH-8602: Name collision with org.hibernate.type.CalendarDateType. +package org.hibernate.envers.test.integration.customtype; + +import java.util.Calendar; + +import org.hibernate.annotations.TypeDef;