Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
HHH-13103 - Allow Hibernate Types to get access to the current config…
…uration properties
  • Loading branch information
vladmihalcea committed Feb 18, 2020
commit 9ad9873979efb5d043c957b2754c0f84ab7bdd89
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,25 @@ public static Constructor getConstructor(Class clazz, Type[] types) throws Prope

}

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() );
Expand Down
23 changes: 22 additions & 1 deletion hibernate-core/src/main/java/org/hibernate/type/TypeFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -88,7 +92,24 @@ public Type byClass(Class clazz, Properties parameters) {

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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a fan of creating a new TypeBootstrapContext every time. I'd pass it the TypeConfiguration and be done with it, or have TypeConfiguration implement TypeBootstrapContext (after making it an interface - which it ought to be anyway imo)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or better yet, have TypeFactory be the TypeBootstrapContext

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can work on this change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

configurationSettings
) );
}
else {
type = typeClass.newInstance();
}

injectParameters( type, parameters );
return type;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 );
Expand All @@ -33,4 +43,7 @@ public String getName() {
return "comma-separated-array";
}

public Map<String, Object> getSettings() {
sebersole marked this conversation as resolved.
Show resolved Hide resolved
return settings;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,66 +6,67 @@
*/
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() {
return new Class[] { CorporateUser.class };
}

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

Expand All @@ -75,8 +76,8 @@ public void test() {

@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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}


}