/* * 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.test.converter; import java.io.Serializable; import java.sql.Timestamp; import java.sql.Types; import javax.persistence.AttributeConverter; import javax.persistence.Column; import javax.persistence.Convert; import javax.persistence.Converter; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import org.hibernate.AnnotationException; import org.hibernate.IrrelevantEntity; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.internal.AttributeConverterDescriptorNonAutoApplicableImpl; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.cfg.AttributeConverterDefinition; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.hql.internal.ast.tree.JavaConstantNode; import org.hibernate.internal.util.ConfigHelper; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.SimpleValue; import org.hibernate.type.AbstractStandardBasicType; import org.hibernate.type.BasicType; import org.hibernate.type.Type; import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter; import org.hibernate.type.descriptor.java.EnumJavaTypeDescriptor; import org.hibernate.type.descriptor.java.StringTypeDescriptor; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseUnitTestCase; import org.junit.Test; import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.fail; /** * Tests the principle of adding "AttributeConverter" to the mix of {@link org.hibernate.type.Type} resolution * * @author Steve Ebersole */ public class AttributeConverterTest extends BaseUnitTestCase { @Test public void testErrorInstantiatingConverterClass() { Configuration cfg = new Configuration(); try { cfg.addAttributeConverter( BlowsUpConverter.class ); fail( "expecting an exception" ); } catch (AnnotationException e) { assertNotNull( e.getCause() ); assertTyping( BlewUpException.class, e.getCause() ); } } public static class BlewUpException extends RuntimeException { } public static class BlowsUpConverter implements AttributeConverter<String,String> { public BlowsUpConverter() { throw new BlewUpException(); } @Override public String convertToDatabaseColumn(String attribute) { return null; } @Override public String convertToEntityAttribute(String dbData) { return null; } } @Test public void testBasicOperation() { final StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build(); try { MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr ).buildMetadata(); SimpleValue simpleValue = new SimpleValue( metadata ); simpleValue.setJpaAttributeConverterDescriptor( new AttributeConverterDescriptorNonAutoApplicableImpl( new StringClobConverter() ) ); simpleValue.setTypeUsingReflection( IrrelevantEntity.class.getName(), "name" ); Type type = simpleValue.getType(); assertNotNull( type ); if ( !AttributeConverterTypeAdapter.class.isInstance( type ) ) { fail( "AttributeConverter not applied" ); } AbstractStandardBasicType basicType = assertTyping( AbstractStandardBasicType.class, type ); assertSame( StringTypeDescriptor.INSTANCE, basicType.getJavaTypeDescriptor() ); assertEquals( Types.CLOB, basicType.getSqlTypeDescriptor().getSqlType() ); } finally { StandardServiceRegistryBuilder.destroy( ssr ); } } @Test public void testNonAutoApplyHandling() { final StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build(); try { MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr ) .addAnnotatedClass( Tester.class ) .getMetadataBuilder() .applyAttributeConverter( NotAutoAppliedConverter.class, false ) .build(); PersistentClass tester = metadata.getEntityBinding( Tester.class.getName() ); Property nameProp = tester.getProperty( "name" ); SimpleValue nameValue = (SimpleValue) nameProp.getValue(); Type type = nameValue.getType(); assertNotNull( type ); if ( AttributeConverterTypeAdapter.class.isInstance( type ) ) { fail( "AttributeConverter with autoApply=false was auto applied" ); } } finally { StandardServiceRegistryBuilder.destroy( ssr ); } } @Test public void testBasicConverterApplication() { final StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build(); try { MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr ) .addAnnotatedClass( Tester.class ) .getMetadataBuilder() .applyAttributeConverter( StringClobConverter.class, true ) .build(); PersistentClass tester = metadata.getEntityBinding( Tester.class.getName() ); Property nameProp = tester.getProperty( "name" ); SimpleValue nameValue = (SimpleValue) nameProp.getValue(); Type type = nameValue.getType(); assertNotNull( type ); assertTyping( BasicType.class, type ); if ( !AttributeConverterTypeAdapter.class.isInstance( type ) ) { fail( "AttributeConverter not applied" ); } AbstractStandardBasicType basicType = assertTyping( AbstractStandardBasicType.class, type ); assertSame( StringTypeDescriptor.INSTANCE, basicType.getJavaTypeDescriptor() ); assertEquals( Types.CLOB, basicType.getSqlTypeDescriptor().getSqlType() ); } finally { StandardServiceRegistryBuilder.destroy( ssr ); } } @Test @TestForIssue(jiraKey = "HHH-8462") public void testBasicOrmXmlConverterApplication() { final StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build(); try { MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr ) .addAnnotatedClass( Tester.class ) .addURL( ConfigHelper.findAsResource( "org/hibernate/test/converter/orm.xml" ) ) .getMetadataBuilder() .build(); PersistentClass tester = metadata.getEntityBinding( Tester.class.getName() ); Property nameProp = tester.getProperty( "name" ); SimpleValue nameValue = (SimpleValue) nameProp.getValue(); Type type = nameValue.getType(); assertNotNull( type ); if ( !AttributeConverterTypeAdapter.class.isInstance( type ) ) { fail( "AttributeConverter not applied" ); } AttributeConverterTypeAdapter basicType = assertTyping( AttributeConverterTypeAdapter.class, type ); assertSame( StringTypeDescriptor.INSTANCE, basicType.getJavaTypeDescriptor() ); assertEquals( Types.CLOB, basicType.getSqlTypeDescriptor().getSqlType() ); } finally { StandardServiceRegistryBuilder.destroy( ssr ); } } @Test public void testBasicConverterDisableApplication() { final StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().build(); try { MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr ) .addAnnotatedClass( Tester2.class ) .getMetadataBuilder() .applyAttributeConverter( StringClobConverter.class, true ) .build(); PersistentClass tester = metadata.getEntityBinding( Tester2.class.getName() ); Property nameProp = tester.getProperty( "name" ); SimpleValue nameValue = (SimpleValue) nameProp.getValue(); Type type = nameValue.getType(); assertNotNull( type ); if ( AttributeConverterTypeAdapter.class.isInstance( type ) ) { fail( "AttributeConverter applied (should not have been)" ); } AbstractStandardBasicType basicType = assertTyping( AbstractStandardBasicType.class, type ); assertSame( StringTypeDescriptor.INSTANCE, basicType.getJavaTypeDescriptor() ); assertEquals( Types.VARCHAR, basicType.getSqlTypeDescriptor().getSqlType() ); } finally { StandardServiceRegistryBuilder.destroy( ssr ); } } @Test public void testBasicUsage() { Configuration cfg = new Configuration(); cfg.addAttributeConverter( IntegerToVarcharConverter.class, false ); cfg.addAnnotatedClass( Tester4.class ); cfg.setProperty( AvailableSettings.HBM2DDL_AUTO, "create-drop" ); cfg.setProperty( AvailableSettings.GENERATE_STATISTICS, "true" ); SessionFactory sf = cfg.buildSessionFactory(); try { Session session = sf.openSession(); session.beginTransaction(); session.save( new Tester4( 1L, "steve", 200 ) ); session.getTransaction().commit(); session.close(); sf.getStatistics().clear(); session = sf.openSession(); session.beginTransaction(); session.get( Tester4.class, 1L ); session.getTransaction().commit(); session.close(); assertEquals( 0, sf.getStatistics().getEntityUpdateCount() ); session = sf.openSession(); session.beginTransaction(); Tester4 t4 = (Tester4) session.get( Tester4.class, 1L ); t4.code = 300; session.getTransaction().commit(); session.close(); session = sf.openSession(); session.beginTransaction(); t4 = (Tester4) session.get( Tester4.class, 1L ); assertEquals( 300, t4.code.longValue() ); session.delete( t4 ); session.getTransaction().commit(); session.close(); } finally { sf.close(); } } @Test public void testBasicTimestampUsage() { Configuration cfg = new Configuration(); cfg.addAttributeConverter( InstantConverter.class, false ); cfg.addAnnotatedClass( IrrelevantInstantEntity.class ); cfg.setProperty( AvailableSettings.HBM2DDL_AUTO, "create-drop" ); cfg.setProperty( AvailableSettings.GENERATE_STATISTICS, "true" ); SessionFactory sf = cfg.buildSessionFactory(); try { Session session = sf.openSession(); session.beginTransaction(); session.save( new IrrelevantInstantEntity( 1L ) ); session.getTransaction().commit(); session.close(); sf.getStatistics().clear(); session = sf.openSession(); session.beginTransaction(); IrrelevantInstantEntity e = (IrrelevantInstantEntity) session.get( IrrelevantInstantEntity.class, 1L ); session.getTransaction().commit(); session.close(); assertEquals( 0, sf.getStatistics().getEntityUpdateCount() ); session = sf.openSession(); session.beginTransaction(); session.delete( e ); session.getTransaction().commit(); session.close(); } finally { sf.close(); } } @Test @TestForIssue(jiraKey = "HHH-8866") public void testEnumConverter() { final StandardServiceRegistry ssr = new StandardServiceRegistryBuilder() .applySetting( AvailableSettings.HBM2DDL_AUTO, "create-drop" ) .build(); try { MetadataImplementor metadata = (MetadataImplementor) new MetadataSources( ssr ) .addAnnotatedClass( EntityWithConvertibleField.class ) .getMetadataBuilder() .applyAttributeConverter( ConvertibleEnumConverter.class, true ) .build(); // first lets validate that the converter was applied... PersistentClass tester = metadata.getEntityBinding( EntityWithConvertibleField.class.getName() ); Property nameProp = tester.getProperty( "convertibleEnum" ); SimpleValue nameValue = (SimpleValue) nameProp.getValue(); Type type = nameValue.getType(); assertNotNull( type ); assertTyping( BasicType.class, type ); if ( !AttributeConverterTypeAdapter.class.isInstance( type ) ) { fail( "AttributeConverter not applied" ); } AbstractStandardBasicType basicType = assertTyping( AbstractStandardBasicType.class, type ); assertTyping( EnumJavaTypeDescriptor.class, basicType.getJavaTypeDescriptor() ); assertEquals( Types.VARCHAR, basicType.getSqlTypeDescriptor().getSqlType() ); // then lets build the SF and verify its use... final SessionFactory sf = metadata.buildSessionFactory(); try { Session s = sf.openSession(); s.getTransaction().begin(); EntityWithConvertibleField entity = new EntityWithConvertibleField(); entity.setId( "ID" ); entity.setConvertibleEnum( ConvertibleEnum.VALUE ); String entityID = entity.getId(); s.persist( entity ); s.getTransaction().commit(); s.close(); s = sf.openSession(); s.beginTransaction(); entity = (EntityWithConvertibleField) s.load( EntityWithConvertibleField.class, entityID ); assertEquals( ConvertibleEnum.VALUE, entity.getConvertibleEnum() ); s.getTransaction().commit(); s.close(); JavaConstantNode javaConstantNode = new JavaConstantNode(); javaConstantNode.setExpectedType( type ); javaConstantNode.setSessionFactory( (SessionFactoryImplementor) sf ); javaConstantNode.setText( "org.hibernate.test.converter.AttributeConverterTest$ConvertibleEnum.VALUE" ); final String outcome = javaConstantNode.getRenderText( (SessionFactoryImplementor) sf ); assertEquals( "'VALUE'", outcome ); s = sf.openSession(); s.beginTransaction(); s.createQuery( "FROM EntityWithConvertibleField e where e.convertibleEnum = org.hibernate.test.converter.AttributeConverterTest$ConvertibleEnum.VALUE" ) .list(); s.getTransaction().commit(); s.close(); s = sf.openSession(); s.beginTransaction(); s.delete( entity ); s.getTransaction().commit(); s.close(); } finally { try { sf.close(); } catch (Exception ignore) { } } } finally { StandardServiceRegistryBuilder.destroy( ssr ); } } // Entity declarations used in the test ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Entity(name = "T1") @SuppressWarnings("UnusedDeclaration") public static class Tester { @Id private Long id; private String name; public Tester() { } public Tester(Long id, String name) { this.id = id; this.name = name; } } @Entity(name = "T2") @SuppressWarnings("UnusedDeclaration") public static class Tester2 { @Id private Long id; @Convert(disableConversion = true) private String name; } @Entity(name = "T3") @SuppressWarnings("UnusedDeclaration") public static class Tester3 { @Id private Long id; @org.hibernate.annotations.Type( type = "string" ) @Convert(disableConversion = true) private String name; } @Entity(name = "T4") @SuppressWarnings("UnusedDeclaration") public static class Tester4 { @Id private Long id; private String name; @Convert( converter = IntegerToVarcharConverter.class ) private Integer code; public Tester4() { } public Tester4(Long id, String name, Integer code) { this.id = id; this.name = name; this.code = code; } } // This class is for mimicking an Instant from Java 8, which a converter might convert to a java.sql.Timestamp public static class Instant implements Serializable { private static final long serialVersionUID = 1L; private long javaMillis; public Instant(long javaMillis) { this.javaMillis = javaMillis; } public long toJavaMillis() { return javaMillis; } public static Instant fromJavaMillis(long javaMillis) { return new Instant( javaMillis ); } public static Instant now() { return new Instant( System.currentTimeMillis() ); } } @Entity @Table(name = "irrelevantInstantEntity") @SuppressWarnings("UnusedDeclaration") public static class IrrelevantInstantEntity { @Id private Long id; private Instant dateCreated; public IrrelevantInstantEntity() { } public IrrelevantInstantEntity(Long id) { this( id, Instant.now() ); } public IrrelevantInstantEntity(Long id, Instant dateCreated) { this.id = id; this.dateCreated = dateCreated; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Instant getDateCreated() { return dateCreated; } public void setDateCreated(Instant dateCreated) { this.dateCreated = dateCreated; } } public static enum ConvertibleEnum { VALUE, DEFAULT; public String convertToString() { switch ( this ) { case VALUE: { return "VALUE"; } default: { return "DEFAULT"; } } } } @Converter(autoApply = true) public static class ConvertibleEnumConverter implements AttributeConverter<ConvertibleEnum, String> { @Override public String convertToDatabaseColumn(ConvertibleEnum attribute) { return attribute.convertToString(); } @Override public ConvertibleEnum convertToEntityAttribute(String dbData) { return ConvertibleEnum.valueOf( dbData ); } } @Entity( name = "EntityWithConvertibleField" ) @Table( name = "EntityWithConvertibleField" ) public static class EntityWithConvertibleField { private String id; private ConvertibleEnum convertibleEnum; @Id @Column(name = "id") public String getId() { return id; } public void setId(String id) { this.id = id; } @Column(name = "testEnum") public ConvertibleEnum getConvertibleEnum() { return convertibleEnum; } public void setConvertibleEnum(ConvertibleEnum convertibleEnum) { this.convertibleEnum = convertibleEnum; } } // Converter declarations used in the test ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Converter(autoApply = false) public static class NotAutoAppliedConverter implements AttributeConverter<String,String> { @Override public String convertToDatabaseColumn(String attribute) { throw new IllegalStateException( "AttributeConverter should not have been applied/called" ); } @Override public String convertToEntityAttribute(String dbData) { throw new IllegalStateException( "AttributeConverter should not have been applied/called" ); } } @Converter( autoApply = true ) public static class IntegerToVarcharConverter implements AttributeConverter<Integer,String> { @Override public String convertToDatabaseColumn(Integer attribute) { return attribute == null ? null : attribute.toString(); } @Override public Integer convertToEntityAttribute(String dbData) { return dbData == null ? null : Integer.valueOf( dbData ); } } @Converter( autoApply = true ) public static class InstantConverter implements AttributeConverter<Instant, Timestamp> { @Override public Timestamp convertToDatabaseColumn(Instant attribute) { return new Timestamp( attribute.toJavaMillis() ); } @Override public Instant convertToEntityAttribute(Timestamp dbData) { return Instant.fromJavaMillis( dbData.getTime() ); } } }