Merge remote-tracking branch 'upstream/master' into wip/6.0_merge_46

This commit is contained in:
Andrea Boriero 2020-07-07 15:43:17 +01:00
commit 9ba18b8c5d
21 changed files with 667 additions and 59 deletions

View File

@ -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.

View File

@ -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

View File

@ -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 ->

View File

@ -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
}
}
}

View File

@ -0,0 +1 @@
hibernateVersion=6.0.0-SNAPSHOT

View File

@ -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

View File

@ -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<PreLoadEventListener> listenerGroup = session
.getFactory()
.getServiceRegistry()
.getService( EventListenerRegistry.class )
.getEventListenerGroup( EventType.PRE_LOAD );
final Iterable<PreLoadEventListener> 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<PreLoadEventListener> preLoadEventListeners) {
initializeEntity( entity, readOnly, session, preLoadEvent, preLoadEventListeners, EntityResolver.DEFAULT );
}
/**
* Perform the second step of 2-phase load. Fully initialize the entity
* instance.
* <p/>
* 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<PreLoadEventListener> 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<PreLoadEventListener> 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<PreLoadEventListener> 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 );
}
}

View File

@ -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];

View File

@ -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()
);

View File

@ -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 );

View File

@ -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") );
}
}

View File

@ -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;

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<Object> 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<Object> 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<Object> 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;
}
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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<ConvertibleEnum, Byte> {
@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];
}
}
}

View File

@ -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();

View File

@ -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.
*

View File

@ -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.<br />
* <code>
@ -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 );

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<DocumentFactory> action = new PrivilegedAction<DocumentFactory>() {
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;
}
}

View File

@ -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<Object> 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()
}
}
}