HHH-13103 - Allow Hibernate Types to get access to the current configuration properties

This commit is contained in:
Vlad Mihalcea 2018-11-14 18:32:14 +02:00 committed by Steve Ebersole
parent 9084ce497e
commit c346171b23
6 changed files with 243 additions and 25 deletions

View File

@ -347,6 +347,25 @@ public final class ReflectHelper {
}
public static <T> Constructor<T> getConstructor(
Class<T> clazz,
Class... constructorArgs) {
Constructor<T> constructor = null;
try {
constructor = clazz.getDeclaredConstructor( constructorArgs );
try {
ReflectHelper.ensureAccessibility( constructor );
}
catch ( SecurityException e ) {
constructor = null;
}
}
catch ( NoSuchMethodException ignore ) {
}
return constructor;
}
public static Method getMethod(Class clazz, Method method) {
try {
return clazz.getMethod( method.getName(), method.getParameterTypes() );

View File

@ -7,15 +7,19 @@
package org.hibernate.type;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.Comparator;
import java.util.Map;
import java.util.Properties;
import org.hibernate.MappingException;
import org.hibernate.classic.Lifecycle;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.tuple.component.ComponentMetamodel;
import org.hibernate.type.spi.TypeBootstrapContext;
import org.hibernate.type.spi.TypeConfiguration;
import org.hibernate.type.spi.TypeConfigurationAware;
import org.hibernate.usertype.CompositeUserType;
@ -88,7 +92,24 @@ public final class TypeFactory implements Serializable {
public Type type(Class<Type> typeClass, Properties parameters) {
try {
Type type = typeClass.newInstance();
Type type;
Constructor<Type> bootstrapContextAwareTypeConstructor = ReflectHelper.getConstructor(
typeClass,
TypeBootstrapContext.class
);
if ( bootstrapContextAwareTypeConstructor != null ) {
ConfigurationService configurationService = typeConfiguration.getServiceRegistry().getService(
ConfigurationService.class );
Map<String, Object> configurationSettings = configurationService.getSettings();
type = bootstrapContextAwareTypeConstructor.newInstance( new TypeBootstrapContext(
configurationSettings
) );
}
else {
type = typeClass.newInstance();
}
injectParameters( type, parameters );
return type;
}

View File

@ -0,0 +1,33 @@
/*
* 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.type.spi;
import java.util.Map;
/**
* Provide a way to customize the {@link org.hibernate.type.Type} instantiation process.
* <p/>
* If a custom {@link org.hibernate.type.Type} defines a constructor which takes the
* {@link TypeBootstrapContext} argument, Hibernate will use this instead of the
* default constructor.
*
* @author Vlad Mihalcea
*
* @since 5.4
*/
public class TypeBootstrapContext {
private final Map<String, Object> configurationSettings;
public TypeBootstrapContext(Map<String, Object> configurationSettings) {
this.configurationSettings = configurationSettings;
}
public Map<String, Object> getConfigurationSettings() {
return configurationSettings;
}
}

View File

@ -1,9 +1,12 @@
package org.hibernate.test.type.contributor;
import java.util.Map;
import org.hibernate.dialect.Dialect;
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.DiscriminatorType;
import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor;
import org.hibernate.type.spi.TypeBootstrapContext;
/**
* @author Vlad Mihalcea
@ -14,10 +17,17 @@ public class ArrayType
public static final ArrayType INSTANCE = new ArrayType();
private Map<String, Object> settings;
public ArrayType() {
super( VarcharTypeDescriptor.INSTANCE, ArrayTypeDescriptor.INSTANCE );
}
public ArrayType(TypeBootstrapContext typeBootstrapContext) {
super( VarcharTypeDescriptor.INSTANCE, ArrayTypeDescriptor.INSTANCE );
this.settings = typeBootstrapContext.getConfigurationSettings();
}
@Override
public Array stringToObject(String xml) throws Exception {
return fromString( xml );
@ -33,4 +43,7 @@ public class ArrayType
return "comma-separated-array";
}
public Map<String, Object> getSettings() {
return settings;
}
}

View File

@ -6,29 +6,33 @@
*/
package org.hibernate.test.type.contributor;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.Session;
import org.hibernate.annotations.Type;
import org.hibernate.cfg.Configuration;
import org.hibernate.boot.spi.MetadataBuilderContributor;
import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.query.Query;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.test.collection.custom.basic.MyList;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* @author Vlad Mihalcea
*/
@TestForIssue( jiraKey = "HHH-11409" )
public class ArrayTypeContributorTest extends BaseCoreFunctionalTestCase {
public class ArrayTypeContributorTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
@ -36,36 +40,33 @@ public class ArrayTypeContributorTest extends BaseCoreFunctionalTestCase {
}
@Override
protected Configuration constructAndConfigureConfiguration() {
Configuration configuration = super.constructAndConfigureConfiguration();
configuration.registerTypeContributor( (typeContributions, serviceRegistry) -> {
typeContributions.contributeType( ArrayType.INSTANCE );
} );
return configuration;
protected void addConfigOptions(Map options) {
options.put(
EntityManagerFactoryBuilderImpl.METADATA_BUILDER_CONTRIBUTOR,
(MetadataBuilderContributor) metadataBuilder ->
metadataBuilder.applyTypes( (typeContributions, serviceRegistry) -> {
typeContributions.contributeType( ArrayType.INSTANCE );
} ));
}
@Override
protected void prepareTest() throws Exception {
doInHibernate( this::sessionFactory, session -> {
protected void afterEntityManagerFactoryBuilt() {
doInJPA( this::entityManagerFactory, entityManager -> {
CorporateUser user = new CorporateUser();
user.setUserName( "Vlad" );
session.persist( user );
entityManager.persist( user );
user.getEmailAddresses().add( "vlad@hibernate.info" );
user.getEmailAddresses().add( "vlad@hibernate.net" );
} );
}
@Override
protected boolean isCleanupTestDataRequired() {
return true;
}
@Test
public void test() {
doInHibernate( this::sessionFactory, session -> {
List<CorporateUser> users = session.createQuery(
doInJPA( this::entityManagerFactory, entityManager -> {
List<CorporateUser> users = entityManager.createQuery(
"select u from CorporateUser u where u.emailAddresses = :address", CorporateUser.class )
.unwrap( Query.class )
.setParameter( "address", new Array(), ArrayType.INSTANCE )
.getResultList();
@ -75,8 +76,8 @@ public class ArrayTypeContributorTest extends BaseCoreFunctionalTestCase {
@Test
public void testNativeSQL() {
doInHibernate( this::sessionFactory, session -> {
List<Array> emails = session.createNativeQuery(
doInJPA( this::entityManagerFactory, entityManager -> {
List<Array> emails = entityManager.createNativeQuery(
"select u.emailAddresses from CorporateUser u where u.userName = :name" )
.setParameter( "name", "Vlad" )
.getResultList();

View File

@ -0,0 +1,131 @@
/*
* 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.type.contributor;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.transaction.Transactional;
import org.hibernate.Session;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.hibernate.boot.spi.MetadataBuilderContributor;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.Query;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* @author Vlad Mihalcea
*/
@TestForIssue( jiraKey = "HHH-13103" )
public class ArrayTypePropertiesTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] { CorporateUser.class };
}
@Override
protected void addConfigOptions(Map options) {
options.put( "hibernate.type.array.config", new Integer[]{1, 2, 3} );
}
@Override
protected void afterEntityManagerFactoryBuilt() {
doInJPA( this::entityManagerFactory, entityManager -> {
CorporateUser user = new CorporateUser();
user.setUserName( "Vlad" );
entityManager.persist( user );
user.getEmailAddresses().add( "vlad@hibernate.info" );
user.getEmailAddresses().add( "vlad@hibernate.net" );
} );
}
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
List<CorporateUser> users = entityManager.createQuery(
"select u from CorporateUser u where u.emailAddresses = :address", CorporateUser.class )
.unwrap( Query.class )
.setParameter( "address", new Array(), ArrayType.INSTANCE )
.getResultList();
assertTrue( users.isEmpty() );
} );
}
@Test
public void testNativeSQL() {
doInJPA( this::entityManagerFactory, entityManager -> {
List<Array> emails = entityManager.createNativeQuery(
"select u.emailAddresses from CorporateUser u where u.userName = :name" )
.setParameter( "name", "Vlad" )
.getResultList();
assertEquals( 1, emails.size() );
} );
}
@Test
public void testConfigurationSettings() {
doInJPA( this::entityManagerFactory, entityManager -> {
SharedSessionContractImplementor session = entityManager.unwrap( SharedSessionContractImplementor.class );
CorporateUser corporateUser = entityManager.find( CorporateUser.class, "Vlad" );
PersistenceContext persistenceContext = session.getPersistenceContext();
EntityPersister entityPersister = persistenceContext.getEntry( corporateUser ).getPersister();
ArrayType arrayType = (ArrayType) entityPersister.getPropertyType( "emailAddresses" );
Map<String, Object> settings = arrayType.getSettings();
Integer[] arrayConfig = (Integer[]) settings.get( "hibernate.type.array.config" );
assertNotNull( arrayConfig );
assertArrayEquals( new Integer[]{1, 2, 3}, arrayConfig );
} );
}
@Entity(name = "CorporateUser")
@TypeDef( typeClass = ArrayType.class, defaultForType = Array.class )
public static class CorporateUser {
@Id
private String userName;
private Array emailAddresses = new Array();
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Array getEmailAddresses() {
return emailAddresses;
}
}
}