diff --git a/documentation/src/main/asciidoc/userguide/chapters/statistics/Statistics.adoc b/documentation/src/main/asciidoc/userguide/chapters/statistics/Statistics.adoc index 6e92ee57d9..a97ccc398b 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/statistics/Statistics.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/statistics/Statistics.adoc @@ -182,4 +182,4 @@ The `QueryStatistics` instance, which you can get via the `getQueryStatistics(St `getPlanCacheHitCount`:: The number of query plans successfully fetched from the cache. `getQueryPlanCacheMissCount`:: The number of query plans *not* fetched from the cache. -`getQueryPlanCacheMissCount`:: The overall time spent to compile the plan for this particular query. +`getPlanCompilationTotalMicroseconds`:: The overall time spent to compile the plan for this particular query. diff --git a/gradle/base-information.gradle b/gradle/base-information.gradle index 61756e228a..e817b5c337 100644 --- a/gradle/base-information.gradle +++ b/gradle/base-information.gradle @@ -7,8 +7,15 @@ apply plugin: 'base' +File versionFile = file( "${rootProject.projectDir}/gradle/version.properties" ) + ext { - ormVersion = new HibernateVersion( '6.0.0-SNAPSHOT', project ) + ormVersionFile = versionFile + ormVersion = HibernateVersion.fromFile( versionFile, project ) + // Override during releases + if ( project.hasProperty( 'releaseVersion' ) ) { + ormVersion = new HibernateVersion( project.releaseVersion, project ) + } baselineJavaVersion = '1.8' jpaVersion = new JpaVersion('2.2') } @@ -54,6 +61,22 @@ class HibernateVersion { this.osgiVersion = isSnapshot ? family + '.' + hibernateVersionComponents[2] + '.SNAPSHOT' : fullName } + static HibernateVersion fromFile(File file, Project project){ + def fullName = readVersionProperties(file) + return new HibernateVersion(fullName, project) + } + + private static String readVersionProperties(File file) { + if ( !file.exists() ) { + throw new GradleException( "Version file $file.canonicalPath does not exists" ) + } + Properties versionProperties = new Properties() + file.withInputStream { + stream -> versionProperties.load( stream ) + } + return versionProperties.hibernateVersion + } + @Override String toString() { return this.fullName diff --git a/gradle/published-java-module.gradle b/gradle/published-java-module.gradle index 98bcd402d5..9f1a181764 100644 --- a/gradle/published-java-module.gradle +++ b/gradle/published-java-module.gradle @@ -266,6 +266,7 @@ publishing { task ciBuild( dependsOn: [test, publish] ) task release( dependsOn: [test, bintrayUpload] ) +bintrayUpload.mustRunAfter test afterEvaluate { Project project -> project.rootProject.subprojects { Project subproject -> diff --git a/gradle/publishing-repos.gradle b/gradle/publishing-repos.gradle index bdea7cd6f1..bac7d8e014 100644 --- a/gradle/publishing-repos.gradle +++ b/gradle/publishing-repos.gradle @@ -14,8 +14,10 @@ apply plugin: 'com.jfrog.bintray' ext { - bintrayUser = project.hasProperty( 'PERSONAL_BINTRAY_USER' ) ? project.property( 'PERSONAL_BINTRAY_USER' ) : null - bintrayKey = project.hasProperty( 'PERSONAL_BINTRAY_API_KEY' ) ? project.property( 'PERSONAL_BINTRAY_API_KEY' ) : null + bintrayUser = project.findProperty( 'PERSONAL_BINTRAY_USER' ) + bintrayKey = project.findProperty( 'PERSONAL_BINTRAY_API_KEY' ) + sonatypeOssrhUser = project.findProperty( 'SONATYPE_OSSRH_USER' ) + sonatypeOssrhPassword = project.findProperty( 'SONATYPE_OSSRH_PASSWORD' ) } @@ -59,6 +61,8 @@ bintray { ] mavenCentralSync { sync = true + user = project.sonatypeOssrhUser + password = project.sonatypeOssrhPassword } } } diff --git a/gradle/version.properties b/gradle/version.properties new file mode 100644 index 0000000000..b0bf7e7db6 --- /dev/null +++ b/gradle/version.properties @@ -0,0 +1 @@ +hibernateVersion=6.0.0-SNAPSHOT \ No newline at end of file diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java index 918d99fc16..2deadb77dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -6,6 +6,8 @@ */ package org.hibernate.dialect; +import java.sql.Types; + import org.hibernate.PessimisticLockException; import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.cfg.AvailableSettings; @@ -35,6 +37,8 @@ import org.hibernate.query.sqm.mutation.internal.idtable.IdTable; import org.hibernate.query.sqm.mutation.internal.idtable.LocalTemporaryTableStrategy; import org.hibernate.query.sqm.mutation.spi.SqmMultiTableMutationStrategy; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorH2DatabaseImpl; +import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl; +import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.jboss.logging.Logger; @@ -55,6 +59,9 @@ public class H2Dialect extends Dialect { private final boolean cascadeConstraints; + private final SequenceInformationExtractor sequenceInformationExtractor; + private final String querySequenceString; + public H2Dialect() { this(0, 0); } @@ -80,6 +87,18 @@ public class H2Dialect extends Dialect { getDefaultProperties().setProperty( AvailableSettings.STATEMENT_BATCH_SIZE, DEFAULT_BATCH_SIZE ); // http://code.google.com/p/h2database/issues/detail?id=235 getDefaultProperties().setProperty( AvailableSettings.NON_CONTEXTUAL_LOB_CREATION, "true" ); + + if ( buildId >= 32 ) { + this.sequenceInformationExtractor = buildId >= 201 + ? SequenceInformationExtractorLegacyImpl.INSTANCE + : SequenceInformationExtractorH2DatabaseImpl.INSTANCE; + this.querySequenceString = "select * from INFORMATION_SCHEMA.SEQUENCES"; + registerColumnType( Types.DECIMAL, "numeric($p,$s)" ); + } + else { + this.sequenceInformationExtractor = SequenceInformationExtractorNoOpImpl.INSTANCE; + this.querySequenceString = null; + } } private static int parseBuildId(DialectResolutionInfo info) { @@ -215,12 +234,12 @@ public class H2Dialect extends Dialect { @Override public String getQuerySequencesString() { - return "select * from information_schema.sequences"; + return querySequenceString; } @Override public SequenceInformationExtractor getSequenceInformationExtractor() { - return SequenceInformationExtractorH2DatabaseImpl.INSTANCE; + return sequenceInformationExtractor; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java index fd721786d4..7034bcbddc 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/TwoPhaseLoad.java @@ -31,13 +31,13 @@ import org.hibernate.event.spi.PostLoadEventListener; import org.hibernate.event.spi.PreLoadEvent; import org.hibernate.event.spi.PreLoadEventListener; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.FastSessionServices; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; import org.hibernate.property.access.internal.PropertyAccessStrategyBackRefImpl; import org.hibernate.proxy.HibernateProxy; import org.hibernate.stat.internal.StatsHelper; import org.hibernate.stat.spi.StatisticsImplementor; +import org.hibernate.type.EntityType; import org.hibernate.type.Type; import org.hibernate.type.TypeHelper; @@ -116,18 +116,13 @@ public final class TwoPhaseLoad { final boolean readOnly, final SharedSessionContractImplementor session, final PreLoadEvent preLoadEvent) { - final PersistenceContext persistenceContext = session.getPersistenceContext(); - final EntityEntry entityEntry = persistenceContext.getEntry( entity ); - if ( entityEntry == null ) { - throw new AssertionFailure( "possible non-threadsafe access to the session" ); - } final EventListenerGroup listenerGroup = session .getFactory() .getServiceRegistry() .getService( EventListenerRegistry.class ) .getEventListenerGroup( EventType.PRE_LOAD ); final Iterable listeners = listenerGroup.listeners(); - doInitializeEntity( entity, entityEntry, readOnly, session, preLoadEvent, listeners ); + initializeEntity( entity, readOnly, session, preLoadEvent, listeners, EntityResolver.DEFAULT ); } /** @@ -150,22 +145,46 @@ public final class TwoPhaseLoad { final SharedSessionContractImplementor session, final PreLoadEvent preLoadEvent, final Iterable preLoadEventListeners) { + initializeEntity( entity, readOnly, session, preLoadEvent, preLoadEventListeners, EntityResolver.DEFAULT ); + } + + /** + * Perform the second step of 2-phase load. Fully initialize the entity + * instance. + *

+ * After processing a JDBC result set, we "resolve" all the associations + * between the entities which were instantiated and had their state + * "hydrated" into an array + * + * @param entity The entity being loaded + * @param readOnly Is the entity being loaded as read-only + * @param session The Session + * @param preLoadEvent The (re-used) pre-load event + * @param preLoadEventListeners the pre-load event listeners + * @param entityResolver the resolver used for to-one entity associations + * (not used when an entity is a bytecode-enhanced lazy entity) + */ + public static void initializeEntity( + final Object entity, + final boolean readOnly, + final SharedSessionContractImplementor session, + final PreLoadEvent preLoadEvent, + final Iterable preLoadEventListeners, + final EntityResolver entityResolver) { final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); final EntityEntry entityEntry = persistenceContext.getEntry( entity ); if ( entityEntry == null ) { throw new AssertionFailure( "possible non-threadsafe access to the session" ); } - doInitializeEntity( entity, entityEntry, readOnly, session, preLoadEvent, preLoadEventListeners ); + initializeEntityEntryLoadedState( entity, entityEntry, session, entityResolver ); + initializeEntityFromEntityEntryLoadedState( entity, entityEntry, readOnly, session, preLoadEvent, preLoadEventListeners ); } - private static void doInitializeEntity( + public static void initializeEntityEntryLoadedState( final Object entity, final EntityEntry entityEntry, - final boolean readOnly, final SharedSessionContractImplementor session, - final PreLoadEvent preLoadEvent, - final Iterable preLoadEventListeners) throws HibernateException { - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + final EntityResolver entityResolver) throws HibernateException { final EntityPersister persister = entityEntry.getPersister(); final Object id = entityEntry.getId(); final Object[] hydratedState = entityEntry.getLoadedState(); @@ -221,7 +240,9 @@ public final class TwoPhaseLoad { // we know value != LazyPropertyInitializer.UNFETCHED_PROPERTY Boolean overridingEager = getOverridingEager( session, entityName, propertyNames[i], types[i], debugEnabled ); - hydratedState[i] = types[i].resolve( value, session, entity, overridingEager ); + hydratedState[i] = types[i].isEntityType() + ? entityResolver.resolve( (EntityType) types[i], value, session, entity, overridingEager ) + : types[i].resolve( value, session, entity, overridingEager ); } else { if ( debugEnabled ) { @@ -229,6 +250,22 @@ public final class TwoPhaseLoad { } } } + } + + public static void initializeEntityFromEntityEntryLoadedState( + final Object entity, + final EntityEntry entityEntry, + final boolean readOnly, + final SharedSessionContractImplementor session, + final PreLoadEvent preLoadEvent, + final Iterable preLoadEventListeners) throws HibernateException { + + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + final EntityPersister persister = entityEntry.getPersister(); + final Object id = entityEntry.getId(); + final Object[] hydratedState = entityEntry.getLoadedState(); + + final boolean debugEnabled = LOG.isDebugEnabled(); //Must occur after resolving identifiers! if ( session.isEventSource() ) { @@ -531,4 +568,23 @@ public final class TwoPhaseLoad { false ); } + + /** + * Implementations determine how a to-one associations is resolved. + * + * @see #initializeEntity(Object, boolean, SharedSessionContractImplementor, PreLoadEvent, Iterable, EntityResolver) + */ + public interface EntityResolver { + + Object resolve( + EntityType entityType, + Object value, + SharedSessionContractImplementor session, + Object owner, + Boolean overridingEager + ); + + EntityResolver DEFAULT = (entityType, value, session, owner, overridingEager) -> + entityType.resolve( value, session, owner, overridingEager ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java index be5a0caacc..fd4ab26ba8 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/ReflectHelper.java @@ -39,7 +39,7 @@ import org.hibernate.type.Type; public final class ReflectHelper { private static final Pattern JAVA_CONSTANT_PATTERN = Pattern.compile( - "[a-z\\d]+\\.([A-Z]{1}[a-z\\d]+)+\\$?([A-Z]{1}[a-z\\d]+)*\\.[A-Z_\\$]+", Pattern.UNICODE_CHARACTER_CLASS); + "[a-z\\d]+\\.([A-Z]+[a-z\\d]+)+\\$?([A-Z]{1}[a-z\\d]+)*\\.[A-Z_\\$]+", Pattern.UNICODE_CHARACTER_CLASS); public static final Class[] NO_PARAM_SIGNATURE = new Class[0]; public static final Object[] NO_PARAMS = new Object[0]; diff --git a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java index 067893cab6..3efd4d89e8 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/EntityType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/EntityType.java @@ -437,6 +437,18 @@ public abstract class EntityType extends AbstractType implements AssociationType return null; } + /** + * Would an entity be eagerly loaded given the value provided for {@code overridingEager}? + * + * @param overridingEager can override eager from the mapping. + * + * @return If {@code overridingEager} is null, then it does not override. + * If true or false then it overrides the mapping value. + */ + public boolean isEager(Boolean overridingEager) { + return overridingEager != null ? overridingEager : this.eager; + } + @Override public Type getSemiResolvedType(SessionFactoryImplementor factory) { return getAssociatedEntityPersister( factory ).getIdentifierType(); @@ -648,12 +660,10 @@ public abstract class EntityType extends AbstractType implements AssociationType getAssociatedEntityPersister( session.getFactory() ) .isInstrumented(); - boolean eager = overridingEager != null ? overridingEager : this.eager; - Object proxyOrEntity = session.internalLoad( getAssociatedEntityName(), id, - eager, + isEager( overridingEager ), isNullable() ); diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java index 6b017f8e2d..9b30f72329 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcTypeJavaClassMappings.java @@ -108,6 +108,7 @@ public class JdbcTypeJavaClassMappings { workMap.put( BigDecimal.class, Types.NUMERIC ); workMap.put( BigInteger.class, Types.NUMERIC ); workMap.put( Boolean.class, Types.BIT ); + workMap.put( Byte.class, Types.TINYINT ); workMap.put( Short.class, Types.SMALLINT ); workMap.put( Integer.class, Types.INTEGER ); workMap.put( Long.class, Types.BIGINT ); diff --git a/hibernate-core/src/test/java/org/hibernate/internal/util/ReflectHelperTest.java b/hibernate-core/src/test/java/org/hibernate/internal/util/ReflectHelperTest.java index 78e914a910..b6559f39fb 100644 --- a/hibernate-core/src/test/java/org/hibernate/internal/util/ReflectHelperTest.java +++ b/hibernate-core/src/test/java/org/hibernate/internal/util/ReflectHelperTest.java @@ -230,4 +230,15 @@ public class ReflectHelperTest { public void test_setMethod_nestedInterfaces_on_superclasses() { assertNotNull( ReflectHelper.findSetterMethod( E.class, "id", String.class ) ); } + + @TestForIssue(jiraKey = "HHH-14059") + @Test + public void test_getConstantValue_UpperCaseEnum() { + when( sessionFactoryOptionsMock.isConventionalJavaConstants() ).thenReturn( true ); + + when( classLoaderServiceMock.classForName( "com.example.UStatus" ) ).thenReturn( (Class) Status.class ); + Object value = ReflectHelper.getConstantValue( "com.example.UStatus.OFF", sessionFactoryImplementorMock); + assertEquals( OFF, value ); + verify(classLoaderServiceMock, times(1)).classForName( eq("com.example.UStatus") ); + } } \ No newline at end of file diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XMLHelper.java b/hibernate-core/src/test/java/org/hibernate/internal/util/xml/XMLHelper.java similarity index 87% rename from hibernate-core/src/main/java/org/hibernate/internal/util/xml/XMLHelper.java rename to hibernate-core/src/test/java/org/hibernate/internal/util/xml/XMLHelper.java index 4fb90b99bf..e68f9e5674 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/xml/XMLHelper.java +++ b/hibernate-core/src/test/java/org/hibernate/internal/util/xml/XMLHelper.java @@ -16,10 +16,11 @@ import org.xml.sax.EntityResolver; /** * Small helper class that lazily loads DOM and SAX reader and keep them for fast use afterwards. * - * @deprecated Currently only used for integration with HCANN. The rest of Hibernate uses StAX now - * for XML processing. See {@link org.hibernate.boot.jaxb.internal.stax} + * This was part of Hibernate ORM core, but moved into the testsuite helpers to not expose + * access to the dom4j types. It's also used by Hibernate Envers, so we will need two copies + * until Envers is able to remove its reliance on dom4j. + * The rest of Hibernate uses StAX now for XML processing. See {@link org.hibernate.boot.jaxb.internal.stax} */ -@Deprecated public final class XMLHelper { private final DocumentFactory documentFactory; diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralWithSingleQuoteTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralWithSingleQuoteTest.java new file mode 100644 index 0000000000..b643babcaf --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/criteria/literal/CriteriaLiteralWithSingleQuoteTest.java @@ -0,0 +1,138 @@ +/* + * 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 . + */ +package org.hibernate.jpa.test.criteria.literal; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; + +import org.hibernate.dialect.PostgreSQL81Dialect; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.SkipForDialect; +import org.hibernate.testing.TestForIssue; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; + +@TestForIssue( jiraKey = "HHH-14077") +public class CriteriaLiteralWithSingleQuoteTest extends BaseEntityManagerFunctionalTestCase { + + @Test + public void literalSingleQuoteTest() throws Exception { + + doInJPA( + this::entityManagerFactory, + entityManager -> { + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery(); + query.select( cb.literal( '\'' ) ).from( Student.class ); + Object object = entityManager.createQuery( query ).getSingleResult(); + assertEquals( "'", object ); + } + ); + } + + @Test + public void literalProjectionTest() throws Exception { + + doInJPA( + this::entityManagerFactory, + entityManager -> { + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery(); + query.multiselect( cb.literal( "' || aValue || '" ) ).from( Student.class ); + Object object = entityManager.createQuery( query ).getSingleResult(); + assertEquals( "' || aValue || '", object ); + } + ); + } + + @Test + @SkipForDialect(value = PostgreSQL81Dialect.class, comment = "PostgreSQL does not support literals in group by statement") + public void testLiteralProjectionAndGroupBy() throws Exception { + doInJPA( + this::entityManagerFactory, + entityManager -> { + + final String literal = "' || aValue || '"; + + CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery(); + query.multiselect( cb.literal( literal ) ) + .from( Student.class ); + query.groupBy( cb.literal( literal ) ); + + Object object = entityManager.createQuery( query ).getSingleResult(); + assertEquals( literal, object ); + } + ); + } + + @Before + public void setupData() { + doInJPA( + this::entityManagerFactory, + entityManager -> { + Student student = new Student(); + student.setAValue( "A Value" ); + entityManager.persist( student ); + } + ); + } + + @After + public void cleanupData() { + doInJPA( + this::entityManagerFactory, + entityManager -> { + entityManager.createQuery( "delete from Student" ); + } + ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Student.class }; + } + + @Entity(name = "Student") + @Table(name = "Students") + public static class Student { + + @Id + @GeneratedValue + private Long id; + + @Column + private String aValue; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + this.id = id; + } + + public String getAValue() { + return aValue; + } + + public void setAValue(String value) { + this.aValue = value; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/cid/CompositeIdFkGeneratedValueIdentityTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/cid/CompositeIdFkGeneratedValueIdentityTest.java index 495535162c..9bbe458b86 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/cid/CompositeIdFkGeneratedValueIdentityTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/cid/CompositeIdFkGeneratedValueIdentityTest.java @@ -90,7 +90,7 @@ public class CompositeIdFkGeneratedValueIdentityTest extends BaseCoreFunctionalT } ); } - @Entity + @Entity(name = "HeadI") public static class HeadI { @Id @@ -100,7 +100,7 @@ public class CompositeIdFkGeneratedValueIdentityTest extends BaseCoreFunctionalT private String name; } - @Entity + @Entity(name = "NodeI") @IdClass(NodeI.PK.class) public static class NodeI { @@ -148,7 +148,7 @@ public class CompositeIdFkGeneratedValueIdentityTest extends BaseCoreFunctionalT } - @Entity + @Entity(name = "ComplexNodeI") @IdClass(ComplexNodeI.PK.class) public static class ComplexNodeI { diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/cid/CompositeIdFkGeneratedValueTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/cid/CompositeIdFkGeneratedValueTest.java index 612929afb3..03dc62c0de 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/annotations/cid/CompositeIdFkGeneratedValueTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/cid/CompositeIdFkGeneratedValueTest.java @@ -243,7 +243,7 @@ public class CompositeIdFkGeneratedValueTest extends BaseCoreFunctionalTestCase }; } - @Entity + @Entity(name = "Head") public static class Head { @Id @@ -253,7 +253,7 @@ public class CompositeIdFkGeneratedValueTest extends BaseCoreFunctionalTestCase private String name; } - @Entity + @Entity(name = "Node") @IdClass(CompositeIdFkGeneratedValueTest.Node.PK.class) public static class Node { @@ -300,8 +300,7 @@ public class CompositeIdFkGeneratedValueTest extends BaseCoreFunctionalTestCase } - - @Entity + @Entity(name = "HeadS") public static class HeadS { @Id @@ -311,7 +310,7 @@ public class CompositeIdFkGeneratedValueTest extends BaseCoreFunctionalTestCase private String name; } - @Entity + @Entity(name = "NodeS") @IdClass(CompositeIdFkGeneratedValueTest.NodeS.PK.class) public static class NodeS { @@ -359,7 +358,7 @@ public class CompositeIdFkGeneratedValueTest extends BaseCoreFunctionalTestCase } - @Entity + @Entity(name = "HeadA") public static class HeadA { @Id @@ -369,7 +368,7 @@ public class CompositeIdFkGeneratedValueTest extends BaseCoreFunctionalTestCase private String name; } - @Entity + @Entity(name = "NodeA") @IdClass(CompositeIdFkGeneratedValueTest.NodeA.PK.class) public static class NodeA { @@ -417,7 +416,7 @@ public class CompositeIdFkGeneratedValueTest extends BaseCoreFunctionalTestCase } - @Entity + @Entity(name = "HeadT") public static class HeadT { @Id @@ -427,7 +426,7 @@ public class CompositeIdFkGeneratedValueTest extends BaseCoreFunctionalTestCase private String name; } - @Entity + @Entity(name = "NodeT") @IdClass(CompositeIdFkGeneratedValueTest.NodeT.PK.class) public static class NodeT { @@ -475,7 +474,7 @@ public class CompositeIdFkGeneratedValueTest extends BaseCoreFunctionalTestCase } - @Entity + @Entity(name = "ComplexNodeS") @IdClass(CompositeIdFkGeneratedValueTest.ComplexNodeS.PK.class) public static class ComplexNodeS { @@ -526,7 +525,7 @@ public class CompositeIdFkGeneratedValueTest extends BaseCoreFunctionalTestCase } - @Entity + @Entity(name = "ComplexNodeT") @IdClass(CompositeIdFkGeneratedValueTest.ComplexNodeT.PK.class) public static class ComplexNodeT { @@ -577,7 +576,7 @@ public class CompositeIdFkGeneratedValueTest extends BaseCoreFunctionalTestCase } - @Entity + @Entity(name = "ComplexNodeA") @IdClass(CompositeIdFkGeneratedValueTest.ComplexNodeA.PK.class) public static class ComplexNodeA { diff --git a/hibernate-core/src/test/java/org/hibernate/test/converter/AttributeConverterTest.java b/hibernate-core/src/test/java/org/hibernate/test/converter/AttributeConverterTest.java index f3f3b293cf..5989756902 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/converter/AttributeConverterTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/converter/AttributeConverterTest.java @@ -306,6 +306,52 @@ public class AttributeConverterTest extends BaseUnitTestCase { } } + @Test + @TestForIssue(jiraKey = "HHH-14021") + public void testBasicByteUsage() { + Configuration cfg = new Configuration(); + cfg.addAttributeConverter( EnumToByteConverter.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, "George", 150, ConvertibleEnum.DEFAULT ) ); + 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.convertibleEnum = ConvertibleEnum.VALUE; + session.getTransaction().commit(); + session.close(); + + session = sf.openSession(); + session.beginTransaction(); + t4 = (Tester4) session.get( Tester4.class, 1L ); + assertEquals( ConvertibleEnum.VALUE, t4.convertibleEnum ); + session.delete( t4 ); + session.getTransaction().commit(); + session.close(); + } + finally { + sf.close(); + } + } + @Test @TestForIssue(jiraKey = "HHH-8866") public void testEnumConverter() { @@ -384,8 +430,8 @@ public class AttributeConverterTest extends BaseUnitTestCase { StandardServiceRegistryBuilder.destroy( ssr ); } } - - + + // Entity declarations used in the test ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -432,6 +478,8 @@ public class AttributeConverterTest extends BaseUnitTestCase { private String name; @Convert( converter = IntegerToVarcharConverter.class ) private Integer code; + @Convert( converter = EnumToByteConverter.class ) + private ConvertibleEnum convertibleEnum; public Tester4() { } @@ -441,6 +489,13 @@ public class AttributeConverterTest extends BaseUnitTestCase { this.name = name; this.code = code; } + + public Tester4(Long id, String name, Integer code, ConvertibleEnum convertibleEnum) { + this.id = id; + this.name = name; + this.code = code; + this.convertibleEnum = convertibleEnum; + } } // This class is for mimicking an Instant from Java 8, which a converter might convert to a java.sql.Timestamp @@ -601,4 +656,17 @@ public class AttributeConverterTest extends BaseUnitTestCase { return Instant.fromJavaMillis( dbData.getTime() ); } } + + @Converter( autoApply = true ) + public static class EnumToByteConverter implements AttributeConverter { + @Override + public Byte convertToDatabaseColumn(ConvertibleEnum attribute) { + return attribute == null ? null : (byte) attribute.ordinal(); + } + + @Override + public ConvertibleEnum convertToEntityAttribute(Byte dbData) { + return dbData == null ? null : ConvertibleEnum.values()[dbData]; + } + } } diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversService.java b/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversService.java index 55ba23e1be..bdac75af98 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversService.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversService.java @@ -17,7 +17,6 @@ import org.hibernate.envers.internal.revisioninfo.RevisionInfoNumberReader; import org.hibernate.envers.internal.revisioninfo.RevisionInfoQueryCreator; import org.hibernate.envers.internal.synchronization.AuditProcessManager; import org.hibernate.envers.strategy.AuditStrategy; -import org.hibernate.internal.util.xml.XMLHelper; import org.hibernate.service.Service; import org.hibernate.service.ServiceRegistry; @@ -56,8 +55,6 @@ public interface EnversService extends Service { void initialize(MetadataImplementor metadata, MappingCollector mappingCollector); - XMLHelper getXmlHelper(); - GlobalConfiguration getGlobalConfiguration(); AuditEntitiesConfiguration getAuditEntitiesConfiguration(); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversServiceImpl.java b/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversServiceImpl.java index 7a9cf02bd8..8f68358a81 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversServiceImpl.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/boot/internal/EnversServiceImpl.java @@ -30,7 +30,6 @@ import org.hibernate.envers.internal.tools.ReflectionTools; import org.hibernate.envers.strategy.AuditStrategy; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.config.ConfigurationHelper; -import org.hibernate.internal.util.xml.XMLHelper; import org.hibernate.service.ServiceRegistry; import org.hibernate.service.spi.Configurable; import org.hibernate.service.spi.Stoppable; @@ -74,8 +73,6 @@ public class EnversServiceImpl implements EnversService, Configurable, Stoppable private RevisionInfoNumberReader revisionInfoNumberReader; private ModifiedEntityNamesReader modifiedEntityNamesReader; - private XMLHelper xmlHelper; - @Override public void configure(Map configurationValues) { if ( configurationValues.containsKey( LEGACY_AUTO_REGISTER ) ) { @@ -111,7 +108,6 @@ public class EnversServiceImpl implements EnversService, Configurable, Stoppable this.serviceRegistry = metadata.getMetadataBuildingOptions().getServiceRegistry(); this.classLoaderService = serviceRegistry.getService( ClassLoaderService.class ); - this.xmlHelper = new XMLHelper(); doInitialize( metadata, mappingCollector, serviceRegistry ); } @@ -186,11 +182,6 @@ public class EnversServiceImpl implements EnversService, Configurable, Stoppable return strategy; } - @Override - public XMLHelper getXmlHelper() { - return xmlHelper; - } - /** * Load a class by name, preferring our ClassLoader and then the ClassLoaderService. * diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/RevisionInfoConfiguration.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/RevisionInfoConfiguration.java index e8b089ec29..97a7c0c35d 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/RevisionInfoConfiguration.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/RevisionInfoConfiguration.java @@ -55,6 +55,8 @@ public class RevisionInfoConfiguration { private Type revisionInfoTimestampType; private GlobalConfiguration globalCfg; + private XMLHelper xmlHelper; + private String revisionPropType; private String revisionPropSqlType; @@ -75,7 +77,7 @@ public class RevisionInfoConfiguration { } private Document generateDefaultRevisionInfoXmlMapping() { - final Document document = globalCfg.getEnversService().getXmlHelper().getDocumentFactory().createDocument(); + final Document document = getXmlHelper().getDocumentFactory().createDocument(); final Element classMapping = MetadataTools.createEntity( document, @@ -120,6 +122,13 @@ public class RevisionInfoConfiguration { return document; } + private XMLHelper getXmlHelper() { + if ( this.xmlHelper == null ) { + this.xmlHelper = new XMLHelper(); + } + return this.xmlHelper; + } + /** * Generates mapping that represents a set of primitive types.
* @@ -158,7 +167,7 @@ public class RevisionInfoConfiguration { } private Element generateRevisionInfoRelationMapping() { - final Document document = globalCfg.getEnversService().getXmlHelper().getDocumentFactory().createDocument(); + final Document document = getXmlHelper().getDocumentFactory().createDocument(); final Element revRelMapping = document.addElement( "key-many-to-one" ); revRelMapping.addAttribute( "type", revisionPropType ); revRelMapping.addAttribute( "class", revisionInfoEntityName ); diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/XMLHelper.java b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/XMLHelper.java new file mode 100644 index 0000000000..44ab9dbefc --- /dev/null +++ b/hibernate-envers/src/main/java/org/hibernate/envers/configuration/internal/XMLHelper.java @@ -0,0 +1,58 @@ +/* + * 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 . + */ +package org.hibernate.envers.configuration.internal; + +import org.dom4j.DocumentFactory; + +import java.security.AccessController; +import java.security.PrivilegedAction; + +/** + * Small helper class that lazily loads DOM factory and keep them for fast use afterwards. + * + * This was part of Hibernate ORM core, but is used exclusively by Hibernate Envers now: + * keep visibility lower so to not expose Dom4j to public API. + * The rest of Hibernate uses StAX now for XML processing. See {@link org.hibernate.boot.jaxb.internal.stax} + */ +final class XMLHelper { + private final DocumentFactory documentFactory; + + XMLHelper() { + PrivilegedAction action = new PrivilegedAction() { + public DocumentFactory run() { + final ClassLoader originalTccl = Thread.currentThread().getContextClassLoader(); + try { + // We need to make sure we get DocumentFactory + // loaded from the same ClassLoader that loads + // Hibernate classes, to make sure we get the + // proper version of DocumentFactory, This class + // is "internal", and should only be used for XML + // files generated by Envers. + + // Using the (Hibernate) ClassLoader that loads + // this Class will avoid collisions in the case + // that DocumentFactory can be loaded from, + // for example, the application ClassLoader. + Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() ); + return DocumentFactory.getInstance(); + } + finally { + Thread.currentThread().setContextClassLoader( originalTccl ); + } + } + }; + + this.documentFactory = System.getSecurityManager() != null + ? AccessController.doPrivileged( action ) + : action.run(); + } + + DocumentFactory getDocumentFactory() { + return documentFactory; + } + +} diff --git a/release/release.gradle b/release/release.gradle index c0eff87113..9536808995 100644 --- a/release/release.gradle +++ b/release/release.gradle @@ -1,3 +1,7 @@ +import java.nio.charset.StandardCharsets + +import groovy.json.JsonSlurper + /* * Hibernate, Relational Persistence for Idiomatic Java * @@ -9,11 +13,52 @@ apply from: rootProject.file( 'gradle/base-information.gradle' ) apply plugin: 'idea' apply plugin: 'distribution' +ext { + if ( !project.hasProperty( 'gitRemote' ) ) { + gitRemote = 'origin' + } +} + idea.module { } final File documentationDir = mkdir( "${project.buildDir}/documentation" ); +task releaseChecks() { + doFirst { + if ( !project.hasProperty('releaseVersion') || !project.hasProperty('developmentVersion') + || !project.hasProperty('gitRemote') ||!project.hasProperty('gitBranch') ) { + throw new GradleException( + "Release tasks require all of the following properties to be set:" + + "'releaseVersion', 'developmentVersion', 'gitRemote', 'gitBranch'." + ) + } + + logger.lifecycle( "Checking that the working tree is clean..." ) + String uncommittedFiles = executeGitCommand( 'status', '--porcelain' ) + if ( !uncommittedFiles.isEmpty() ) { + throw new GradleException( + "Cannot release because there are uncommitted or untracked files in the working tree." + + "\nCommit or stash your changes first." + + "\nUncommitted files:\n" + uncommittedFiles + ); + } + + logger.lifecycle( "Switching to branch '${project.gitBranch}'..." ) + executeGitCommand( 'checkout', project.gitBranch ) + + logger.lifecycle( "Checking that all commits are pushed..." ) + String diffWithUpstream = executeGitCommand( 'diff', '@{u}' ) + if ( !diffWithUpstream.isEmpty() ) { + throw new GradleException( + "Cannot release because there are commits on the branch to release that haven't been pushed yet." + + "\nPush your commits to the branch to release first." + ); + } + + } +} + /** * Assembles all documentation into the {buildDir}/documentation directory. * @@ -243,5 +288,181 @@ artifacts { bundles distZip } -task release( dependsOn: [uploadDocumentation, uploadBundlesSourceForge] ) +task release( dependsOn: [releaseChecks, uploadDocumentation, uploadBundlesSourceForge] ) + +task changeLogFile( dependsOn: [releaseChecks] ) { + group = "Release" + description = "Updates the changelog.txt file" + + doFirst { + logger.lifecycle( "Appending version '${project.releaseVersion}' to changelog..." ) + ChangeLogFile.update( ormVersion.fullName ); + } +} + + + +task addVersionCommit( dependsOn: [changeLogFile] ) { + group = "Release" + description = "Adds a commit for the released version and push the changes to github" + doFirst{ + logger.lifecycle( "Updating version to '${project.releaseVersion}'..." ) + project.ormVersionFile.text = "hibernateVersion=${project.releaseVersion}" + + logger.lifecycle( "Adding commit to update version to '${project.releaseVersion}'..." ) + executeGitCommand( 'add', '.' ) + executeGitCommand( 'commit', '-m', project.ormVersion.fullName ) + } +} +release.mustRunAfter addVersionCommit + +rootProject.subprojects.each { Project subProject -> + if ( !this.name.equals( subProject.name ) ) { + if ( subProject.tasks.findByName( 'release' ) ) { + this.tasks.release.dependsOn( subProject.tasks.release ) + subProject.tasks.release.mustRunAfter( this.tasks.addVersionCommit ) + } + } +} + +task ciRelease( dependsOn: [releaseChecks, addVersionCommit, release] ) { + group = "Release" + description = "Performs a release: the hibernate version is set and the changelog.txt file updated, the changes are pushed to github, then the release is performed, tagged and the hibernate version is set to the development one." + doLast { + String tag = null + if ( !project.hasProperty( 'noTag' ) ) { + tag = project.ormVersion.fullName + // the release is tagged and the tag is pushed to github + if ( tag.endsWith( ".Final" ) ) { + tag = tag.replace( ".Final", "" ) + } + logger.lifecycle( "Tagging '${tag}'..." ) + executeGitCommand( 'tag', tag ) + } + + logger.lifecycle( "Adding commit to update version to '${project.developmentVersion}'..." ) + project.ormVersionFile.text = "hibernateVersion=${project.developmentVersion}" + executeGitCommand( 'add', '.') + executeGitCommand( 'commit', '-m', project.developmentVersion ) + + if ( tag != null ) { + logger.lifecycle("Pushing branch and tag to remote '${project.gitRemote}'...") + executeGitCommand( 'push', '--atomic', project.gitRemote , project.gitBranch, tag ) + } + else { + logger.lifecycle("Pushing branch to remote '${project.gitRemote}'...") + executeGitCommand( 'push', project.gitRemote , project.gitBranch ) + } + } +} + +static String executeGitCommand(Object ... subcommand){ + List command = ['git'] + Collections.addAll( command, subcommand ) + def proc = command.execute() + def code = proc.waitFor() + def stdout = inputStreamToString( proc.getInputStream() ) + def stderr = inputStreamToString( proc.getErrorStream() ) + if ( code != 0 ) { + throw new GradleException( "An error occurred while executing " + command + "\n\nstdout:\n" + stdout + "\n\nstderr:\n" + stderr ) + } + return stdout +} + +static String inputStreamToString(InputStream inputStream) { + inputStream.withCloseable { ins -> + new BufferedInputStream(ins).withCloseable { bis -> + new ByteArrayOutputStream().withCloseable { buf -> + int result = bis.read(); + while (result != -1) { + buf.write((byte) result); + result = bis.read(); + } + return buf.toString( StandardCharsets.UTF_8.name()); + } + } + } +} + +class ChangeLogFile { + + // Get the Release Notes from Jira and add them to the Hibernate changelog.txt file + static void update(String releaseVersion) { + def text = "" + File changelog = new File( "changelog.txt" ) + def newReleaseNoteBlock = getNewReleaseNoteBlock(releaseVersion) + changelog.eachLine { + line -> + if ( line.startsWith( "Note:" ) ) { + text += line + System.lineSeparator() + System.lineSeparator() + newReleaseNoteBlock + } + else { + text += line + System.lineSeparator() + } + } + changelog.text = text + } + + // Get the Release Notes from Jira + static String getNewReleaseNoteBlock(String releaseVersion) { + def restReleaseVersion; + if ( releaseVersion.endsWith( ".Final" ) ) { + restReleaseVersion = releaseVersion.replace( ".Final", "" ) + } + else { + restReleaseVersion = releaseVersion + } + def apiString = "https://hibernate.atlassian.net/rest/api/2/search/?jql=project=HHH%20AND%20fixVersion=${restReleaseVersion}%20order%20by%20issuetype%20ASC" + def apiUrl = new URL( apiString ) + def releseNotes = new JsonSlurper().parse( apiUrl ) + + + def releaseDate = new Date().format( 'MMMM dd, YYYY' ) + def versionId = releseNotes.issues.get( 0 ).fields.fixVersions.get( 0 ).id + ReleaseNote releaseNotes = new ReleaseNote( releaseVersion, releaseDate, versionId ) + + def issuetype + releseNotes.issues.each { + issue -> + if ( issuetype != issue.fields.issuetype.name ) { + issuetype = issue.fields.issuetype.name + releaseNotes.addEmptyLine(); + releaseNotes.addLine( "** ${issue.fields.issuetype.name}" ) + } + releaseNotes.addLine( " * [" + issue.key + "] - " + issue.fields.summary ) + } + releaseNotes.addEmptyLine() + return releaseNotes.notes + } +} + +class ReleaseNote { + String notes; + String notesHeaderSeparator = "------------------------------------------------------------------------------------------------------------------------" + + ReleaseNote(String releaseVersion, String releaseDate, String versionId) { + notes = "Changes in ${releaseVersion} (${releaseDate})" + System.lineSeparator() + addHeaderSeparator() + addEmptyLine() + addLine( "https://hibernate.atlassian.net/projects/HHH/versions/${versionId}" ) + } + + void addLine(String text) { + notes += text + System.lineSeparator() + } + + void addHeaderSeparator() { + addLine( notesHeaderSeparator ) + } + + void addEmptyLine() { + notes += System.lineSeparator() + } + + void addEmptyLines(int numberOfLines) { + for ( i in 1..numberOfLines ) { + notes += System.lineSeparator() + } + } +}