/*
 * 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.mapping;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.Objects;
import javax.persistence.AttributeConverter;

import org.hibernate.FetchMode;
import org.hibernate.MappingException;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor;
import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
import org.hibernate.boot.model.convert.spi.JpaAttributeConverterCreationContext;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.config.spi.StandardConverters;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.IdentityGenerator;
import org.hibernate.id.PersistentIdentifierGenerator;
import org.hibernate.id.factory.IdentifierGeneratorFactory;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.metamodel.model.convert.spi.JpaAttributeConverter;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.BinaryType;
import org.hibernate.type.RowVersionType;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.JdbcTypeNameMapper;
import org.hibernate.type.descriptor.converter.AttributeConverterSqlTypeDescriptorAdapter;
import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter;
import org.hibernate.type.descriptor.java.BasicJavaDescriptor;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.descriptor.spi.JdbcRecommendedSqlTypeMappingContext;
import org.hibernate.type.descriptor.sql.JdbcTypeJavaClassMappings;
import org.hibernate.type.descriptor.sql.LobTypeMappings;
import org.hibernate.type.descriptor.sql.NationalizedTypeMappings;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
import org.hibernate.type.spi.TypeConfiguration;
import org.hibernate.usertype.DynamicParameterizedType;

Any value that maps to columns.
Author:Gavin King
/** * Any value that maps to columns. * @author Gavin King */
public class SimpleValue implements KeyValue { private static final CoreMessageLogger log = CoreLogging.messageLogger( SimpleValue.class ); public static final String DEFAULT_ID_GEN_STRATEGY = "assigned"; private MetadataBuildingContext buildingContext; private final MetadataImplementor metadata; private final List<Selectable> columns = new ArrayList<>(); private final List<Boolean> insertability = new ArrayList<>(); private final List<Boolean> updatability = new ArrayList<>(); private String typeName; private Properties typeParameters; private boolean isVersion; private boolean isNationalized; private boolean isLob; private Properties identifierGeneratorProperties; private String identifierGeneratorStrategy = DEFAULT_ID_GEN_STRATEGY; private String nullValue; private Table table; private String foreignKeyName; private String foreignKeyDefinition; private boolean alternateUniqueKey; private boolean cascadeDeleteEnabled; private ConverterDescriptor attributeConverterDescriptor; private Type type;
Deprecated:Use SimpleValue(MetadataBuildingContext) instead.
/** * @deprecated Use {@link SimpleValue#SimpleValue(MetadataBuildingContext)} instead. */
@Deprecated public SimpleValue(MetadataImplementor metadata) { this.metadata = metadata; }
Deprecated:Use SimpleValue(MetadataBuildingContext, Table) instead.
/** * @deprecated Use {@link SimpleValue#SimpleValue(MetadataBuildingContext, Table)} instead. */
@Deprecated public SimpleValue(MetadataImplementor metadata, Table table) { this( metadata ); this.table = table; } public SimpleValue(MetadataBuildingContext buildingContext) { this(buildingContext.getMetadataCollector()); this.buildingContext = buildingContext; } public SimpleValue(MetadataBuildingContext buildingContext, Table table) { this( buildingContext ); this.table = table; } public MetadataImplementor getMetadata() { return metadata; } @Override public ServiceRegistry getServiceRegistry() { return getMetadata().getMetadataBuildingOptions().getServiceRegistry(); } @Override public boolean isCascadeDeleteEnabled() { return cascadeDeleteEnabled; } public void setCascadeDeleteEnabled(boolean cascadeDeleteEnabled) { this.cascadeDeleteEnabled = cascadeDeleteEnabled; } public void addColumn(Column column) { addColumn( column, true, true ); } public void addColumn(Column column, boolean isInsertable, boolean isUpdatable) { int index = columns.indexOf( column ); if ( index == -1 ) { columns.add(column); insertability.add( isInsertable ); updatability.add( isUpdatable ); } else { if ( insertability.get( index ) != isInsertable ) { throw new IllegalStateException( "Same column is added more than once with different values for isInsertable" ); } if ( updatability.get( index ) != isUpdatable ) { throw new IllegalStateException( "Same column is added more than once with different values for isUpdatable" ); } } column.setValue( this ); column.setTypeIndex( columns.size() - 1 ); } public void addFormula(Formula formula) { columns.add( formula ); insertability.add( false ); updatability.add( false ); } @Override public boolean hasFormula() { Iterator iter = getColumnIterator(); while ( iter.hasNext() ) { Object o = iter.next(); if (o instanceof Formula) { return true; } } return false; } @Override public int getColumnSpan() { return columns.size(); } @Override public Iterator<Selectable> getColumnIterator() { return columns.iterator(); } public List getConstraintColumns() { return columns; } public String getTypeName() { return typeName; } public void setTypeName(String typeName) { if ( typeName != null && typeName.startsWith( AttributeConverterTypeAdapter.NAME_PREFIX ) ) { final String converterClassName = typeName.substring( AttributeConverterTypeAdapter.NAME_PREFIX.length() ); final ClassLoaderService cls = getMetadata() .getMetadataBuildingOptions() .getServiceRegistry() .getService( ClassLoaderService.class ); try { final Class<? extends AttributeConverter> converterClass = cls.classForName( converterClassName ); this.attributeConverterDescriptor = new ClassBasedConverterDescriptor( converterClass, false, ( (InFlightMetadataCollector) getMetadata() ).getClassmateContext() ); return; } catch (Exception e) { log.logBadHbmAttributeConverterType( typeName, e.getMessage() ); } } this.typeName = typeName; } public void makeVersion() { this.isVersion = true; } public boolean isVersion() { return isVersion; } public void makeNationalized() { this.isNationalized = true; } public boolean isNationalized() { return isNationalized; } public void makeLob() { this.isLob = true; } public boolean isLob() { return isLob; } public void setTable(Table table) { this.table = table; } @Override public void createForeignKey() throws MappingException {} @Override public void createForeignKeyOfEntity(String entityName) { if ( !hasFormula() && !"none".equals(getForeignKeyName())) { ForeignKey fk = table.createForeignKey( getForeignKeyName(), getConstraintColumns(), entityName, getForeignKeyDefinition() ); fk.setCascadeDeleteEnabled(cascadeDeleteEnabled); } } private IdentifierGenerator identifierGenerator; @Override public IdentifierGenerator createIdentifierGenerator( IdentifierGeneratorFactory identifierGeneratorFactory, Dialect dialect, String defaultCatalog, String defaultSchema, RootClass rootClass) throws MappingException { if ( identifierGenerator != null ) { return identifierGenerator; } Properties params = new Properties(); //if the hibernate-mapping did not specify a schema/catalog, use the defaults //specified by properties - but note that if the schema/catalog were specified //in hibernate-mapping, or as params, they will already be initialized and //will override the values set here (they are in identifierGeneratorProperties) if ( defaultSchema!=null ) { params.setProperty(PersistentIdentifierGenerator.SCHEMA, defaultSchema); } if ( defaultCatalog!=null ) { params.setProperty(PersistentIdentifierGenerator.CATALOG, defaultCatalog); } //pass the entity-name, if not a collection-id if (rootClass!=null) { params.setProperty( IdentifierGenerator.ENTITY_NAME, rootClass.getEntityName() ); params.setProperty( IdentifierGenerator.JPA_ENTITY_NAME, rootClass.getJpaEntityName() ); } //init the table here instead of earlier, so that we can get a quoted table name //TODO: would it be better to simply pass the qualified table name, instead of // splitting it up into schema/catalog/table names String tableName = getTable().getQuotedName(dialect); params.setProperty( PersistentIdentifierGenerator.TABLE, tableName ); //pass the column name (a generated id almost always has a single column) String columnName = ( (Column) getColumnIterator().next() ).getQuotedName(dialect); params.setProperty( PersistentIdentifierGenerator.PK, columnName ); if (rootClass!=null) { StringBuilder tables = new StringBuilder(); Iterator iter = rootClass.getIdentityTables().iterator(); while ( iter.hasNext() ) { Table table= (Table) iter.next(); tables.append( table.getQuotedName(dialect) ); if ( iter.hasNext() ) { tables.append(", "); } } params.setProperty( PersistentIdentifierGenerator.TABLES, tables.toString() ); } else { params.setProperty( PersistentIdentifierGenerator.TABLES, tableName ); } if (identifierGeneratorProperties!=null) { params.putAll(identifierGeneratorProperties); } // TODO : we should pass along all settings once "config lifecycle" is hashed out... final ConfigurationService cs = metadata.getMetadataBuildingOptions().getServiceRegistry() .getService( ConfigurationService.class ); params.put( AvailableSettings.PREFER_POOLED_VALUES_LO, cs.getSetting( AvailableSettings.PREFER_POOLED_VALUES_LO, StandardConverters.BOOLEAN, false ) ); if ( cs.getSettings().get( AvailableSettings.PREFERRED_POOLED_OPTIMIZER ) != null ) { params.put( AvailableSettings.PREFERRED_POOLED_OPTIMIZER, cs.getSettings().get( AvailableSettings.PREFERRED_POOLED_OPTIMIZER ) ); } identifierGeneratorFactory.setDialect( dialect ); identifierGenerator = identifierGeneratorFactory.createIdentifierGenerator( identifierGeneratorStrategy, getType(), params ); return identifierGenerator; } public boolean isUpdateable() { //needed to satisfy KeyValue return true; } public FetchMode getFetchMode() { return FetchMode.SELECT; } public Properties getIdentifierGeneratorProperties() { return identifierGeneratorProperties; } public String getNullValue() { return nullValue; } public Table getTable() { return table; }
Returns the identifierGeneratorStrategy.
Returns:String
/** * Returns the identifierGeneratorStrategy. * @return String */
public String getIdentifierGeneratorStrategy() { return identifierGeneratorStrategy; } public boolean isIdentityColumn(IdentifierGeneratorFactory identifierGeneratorFactory, Dialect dialect) { identifierGeneratorFactory.setDialect( dialect ); return IdentityGenerator.class.isAssignableFrom(identifierGeneratorFactory.getIdentifierGeneratorClass( identifierGeneratorStrategy )); }
Sets the identifierGeneratorProperties.
Params:
  • identifierGeneratorProperties – The identifierGeneratorProperties to set
/** * Sets the identifierGeneratorProperties. * @param identifierGeneratorProperties The identifierGeneratorProperties to set */
public void setIdentifierGeneratorProperties(Properties identifierGeneratorProperties) { this.identifierGeneratorProperties = identifierGeneratorProperties; }
Sets the identifierGeneratorStrategy.
Params:
  • identifierGeneratorStrategy – The identifierGeneratorStrategy to set
/** * Sets the identifierGeneratorStrategy. * @param identifierGeneratorStrategy The identifierGeneratorStrategy to set */
public void setIdentifierGeneratorStrategy(String identifierGeneratorStrategy) { this.identifierGeneratorStrategy = identifierGeneratorStrategy; }
Sets the nullValue.
Params:
  • nullValue – The nullValue to set
/** * Sets the nullValue. * @param nullValue The nullValue to set */
public void setNullValue(String nullValue) { this.nullValue = nullValue; } public String getForeignKeyName() { return foreignKeyName; } public void setForeignKeyName(String foreignKeyName) { this.foreignKeyName = foreignKeyName; } public String getForeignKeyDefinition() { return foreignKeyDefinition; } public void setForeignKeyDefinition(String foreignKeyDefinition) { this.foreignKeyDefinition = foreignKeyDefinition; } public boolean isAlternateUniqueKey() { return alternateUniqueKey; } public void setAlternateUniqueKey(boolean unique) { this.alternateUniqueKey = unique; } public boolean isNullable() { Iterator itr = getColumnIterator(); while ( itr.hasNext() ) { final Object selectable = itr.next(); if ( selectable instanceof Formula ) { // if there are *any* formulas, then the Value overall is // considered nullable return true; } else if ( !( (Column) selectable ).isNullable() ) { // if there is a single non-nullable column, the Value // overall is considered non-nullable. return false; } } // nullable by default return true; } public boolean isSimpleValue() { return true; } public boolean isValid(Mapping mapping) throws MappingException { return getColumnSpan()==getType().getColumnSpan(mapping); } public Type getType() throws MappingException { if ( type != null ) { return type; } if ( typeName == null ) { throw new MappingException( "No type name" ); } if ( typeParameters != null && Boolean.valueOf( typeParameters.getProperty( DynamicParameterizedType.IS_DYNAMIC ) ) && typeParameters.get( DynamicParameterizedType.PARAMETER_TYPE ) == null ) { createParameterImpl(); } Type result = getMetadata().getTypeConfiguration().getTypeResolver().heuristicType( typeName, typeParameters ); // if this is a byte[] version/timestamp, then we need to use RowVersionType // instead of BinaryType (HHH-10413) if ( isVersion && BinaryType.class.isInstance( result ) ) { log.debug( "version is BinaryType; changing to RowVersionType" ); result = RowVersionType.INSTANCE; } if ( result == null ) { String msg = "Could not determine type for: " + typeName; if ( table != null ) { msg += ", at table: " + table.getName(); } if ( columns != null && columns.size() > 0 ) { msg += ", for columns: " + columns; } throw new MappingException( msg ); } return result; } @Override public void setTypeUsingReflection(String className, String propertyName) throws MappingException { // NOTE : this is called as the last piece in setting SimpleValue type information, and implementations // rely on that fact, using it as a signal that all information it is going to get is defined at this point... if ( typeName != null ) { // assume either (a) explicit type was specified or (b) determine was already performed return; } if ( type != null ) { return; } if ( attributeConverterDescriptor == null ) { // this is here to work like legacy. This should change when we integrate with metamodel to // look for SqlTypeDescriptor and JavaTypeDescriptor individually and create the BasicType (well, really // keep a registry of [SqlTypeDescriptor,JavaTypeDescriptor] -> BasicType...) if ( className == null ) { throw new MappingException( "Attribute types for a dynamic entity must be explicitly specified: " + propertyName ); } typeName = ReflectHelper.reflectedPropertyClass( className, propertyName, getMetadata() .getMetadataBuildingOptions() .getServiceRegistry() .getService( ClassLoaderService.class ) ).getName(); // todo : to fully support isNationalized here we need do the process hinted at above // essentially, much of the logic from #buildAttributeConverterTypeAdapter wrt resolving // a (1) SqlTypeDescriptor, a (2) JavaTypeDescriptor and dynamically building a BasicType // combining them. return; } // we had an AttributeConverter... type = buildAttributeConverterTypeAdapter(); }
Build a Hibernate Type that incorporates the JPA AttributeConverter. AttributeConverter works totally in memory, meaning it converts between one Java representation (the entity attribute representation) and another (the value bound into JDBC statements or extracted from results). However, the Hibernate Type system operates at the lower level of actually dealing directly with those JDBC objects. So even though we have an AttributeConverter, we still need to "fill out" the rest of the BasicType data and bridge calls to bind/extract through the converter.

Essentially the idea here is that an intermediate Java type needs to be used. Let's use an example as a means to illustrate... Consider an AttributeConverter<Integer,String>. This tells Hibernate that the domain model defines this attribute as an Integer value (the 'entityAttributeJavaType'), but that we need to treat the value as a String (the 'databaseColumnJavaType') when dealing with JDBC (aka, the database type is a VARCHAR/CHAR):
Returns:The built AttributeConverter -> Type adapter
@todo: ultimately I want to see attributeConverterJavaType and attributeConverterJdbcTypeCode specify-able separately then we can "play them against each other" in terms of determining proper typing
@todo: see if we already have previously built a custom on-the-fly BasicType for this AttributeConverter; see note below about caching
/** * Build a Hibernate Type that incorporates the JPA AttributeConverter. AttributeConverter works totally in * memory, meaning it converts between one Java representation (the entity attribute representation) and another * (the value bound into JDBC statements or extracted from results). However, the Hibernate Type system operates * at the lower level of actually dealing directly with those JDBC objects. So even though we have an * AttributeConverter, we still need to "fill out" the rest of the BasicType data and bridge calls * to bind/extract through the converter. * <p/> * Essentially the idea here is that an intermediate Java type needs to be used. Let's use an example as a means * to illustrate... Consider an {@code AttributeConverter<Integer,String>}. This tells Hibernate that the domain * model defines this attribute as an Integer value (the 'entityAttributeJavaType'), but that we need to treat the * value as a String (the 'databaseColumnJavaType') when dealing with JDBC (aka, the database type is a * VARCHAR/CHAR):<ul> * <li> * When binding values to PreparedStatements we need to convert the Integer value from the entity * into a String and pass that String to setString. The conversion is handled by calling * {@link AttributeConverter#convertToDatabaseColumn(Object)} * </li> * <li> * When extracting values from ResultSets (or CallableStatement parameters) we need to handle the * value via getString, and convert that returned String to an Integer. That conversion is handled * by calling {@link AttributeConverter#convertToEntityAttribute(Object)} * </li> * </ul> * * @return The built AttributeConverter -> Type adapter * * @todo : ultimately I want to see attributeConverterJavaType and attributeConverterJdbcTypeCode specify-able separately * then we can "play them against each other" in terms of determining proper typing * * @todo : see if we already have previously built a custom on-the-fly BasicType for this AttributeConverter; see note below about caching */
@SuppressWarnings("unchecked") private Type buildAttributeConverterTypeAdapter() { // todo : validate the number of columns present here? final JpaAttributeConverter jpaAttributeConverter = attributeConverterDescriptor.createJpaAttributeConverter( new JpaAttributeConverterCreationContext() { @Override public ManagedBeanRegistry getManagedBeanRegistry() { return getMetadata() .getMetadataBuildingOptions() .getServiceRegistry() .getService( ManagedBeanRegistry.class ); } @Override public org.hibernate.type.descriptor.java.spi.JavaTypeDescriptorRegistry getJavaTypeDescriptorRegistry() { return metadata.getTypeConfiguration().getJavaTypeDescriptorRegistry(); } } ); final BasicJavaDescriptor entityAttributeJavaTypeDescriptor = jpaAttributeConverter.getDomainJavaTypeDescriptor(); // build the SqlTypeDescriptor adapter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Going back to the illustration, this should be a SqlTypeDescriptor that handles the Integer <-> String // conversions. This is the more complicated piece. First we need to determine the JDBC type code // corresponding to the AttributeConverter's declared "databaseColumnJavaType" (how we read that value out // of ResultSets). See JdbcTypeJavaClassMappings for details. Again, given example, this should return // VARCHAR/CHAR final SqlTypeDescriptor recommendedSqlType = jpaAttributeConverter.getRelationalJavaTypeDescriptor().getJdbcRecommendedSqlType( // todo (6.0) : handle the other JdbcRecommendedSqlTypeMappingContext methods metadata::getTypeConfiguration ); int jdbcTypeCode = recommendedSqlType.getSqlType(); if ( isLob() ) { if ( LobTypeMappings.INSTANCE.hasCorrespondingLobCode( jdbcTypeCode ) ) { jdbcTypeCode = LobTypeMappings.INSTANCE.getCorrespondingLobCode( jdbcTypeCode ); } else { if ( Serializable.class.isAssignableFrom( entityAttributeJavaTypeDescriptor.getJavaType() ) ) { jdbcTypeCode = Types.BLOB; } else { throw new IllegalArgumentException( String.format( Locale.ROOT, "JDBC type-code [%s (%s)] not known to have a corresponding LOB equivalent, and Java type is not Serializable (to use BLOB)", jdbcTypeCode, JdbcTypeNameMapper.getTypeName( jdbcTypeCode ) ) ); } } } if ( isNationalized() ) { jdbcTypeCode = NationalizedTypeMappings.INSTANCE.getCorrespondingNationalizedCode( jdbcTypeCode ); } // find the standard SqlTypeDescriptor for that JDBC type code (allow itr to be remapped if needed!) final SqlTypeDescriptor sqlTypeDescriptor = getMetadata() .getMetadataBuildingOptions() .getServiceRegistry() .getService( JdbcServices.class ) .getJdbcEnvironment() .getDialect() .remapSqlTypeDescriptor( metadata.getTypeConfiguration() .getSqlTypeDescriptorRegistry() .getDescriptor( jdbcTypeCode ) ); // and finally construct the adapter, which injects the AttributeConverter calls into the binding/extraction // process... final SqlTypeDescriptor sqlTypeDescriptorAdapter = new AttributeConverterSqlTypeDescriptorAdapter( jpaAttributeConverter, sqlTypeDescriptor, jpaAttributeConverter.getRelationalJavaTypeDescriptor() ); // todo : cache the AttributeConverterTypeAdapter in case that AttributeConverter is applied multiple times. final String name = AttributeConverterTypeAdapter.NAME_PREFIX + jpaAttributeConverter.getConverterJavaTypeDescriptor().getJavaType().getName(); final String description = String.format( "BasicType adapter for AttributeConverter<%s,%s>", jpaAttributeConverter.getDomainJavaTypeDescriptor().getJavaType().getSimpleName(), jpaAttributeConverter.getRelationalJavaTypeDescriptor().getJavaType().getSimpleName() ); return new AttributeConverterTypeAdapter( name, description, jpaAttributeConverter, sqlTypeDescriptorAdapter, jpaAttributeConverter.getDomainJavaTypeDescriptor().getJavaType(), jpaAttributeConverter.getRelationalJavaTypeDescriptor().getJavaType(), entityAttributeJavaTypeDescriptor ); } public boolean isTypeSpecified() { return typeName!=null; } public void setTypeParameters(Properties parameterMap) { this.typeParameters = parameterMap; } public Properties getTypeParameters() { return typeParameters; } public void copyTypeFrom( SimpleValue sourceValue ) { setTypeName( sourceValue.getTypeName() ); setTypeParameters( sourceValue.getTypeParameters() ); type = sourceValue.type; attributeConverterDescriptor = sourceValue.attributeConverterDescriptor; } @Override public boolean isSame(Value other) { return this == other || other instanceof SimpleValue && isSame( (SimpleValue) other ); } protected static boolean isSame(Value v1, Value v2) { return v1 == v2 || v1 != null && v2 != null && v1.isSame( v2 ); } public boolean isSame(SimpleValue other) { return Objects.equals( columns, other.columns ) && Objects.equals( typeName, other.typeName ) && Objects.equals( typeParameters, other.typeParameters ) && Objects.equals( table, other.table ) && Objects.equals( foreignKeyName, other.foreignKeyName ) && Objects.equals( foreignKeyDefinition, other.foreignKeyDefinition ); } @Override public String toString() { return getClass().getName() + '(' + columns.toString() + ')'; } public Object accept(ValueVisitor visitor) { return visitor.accept(this); } public boolean[] getColumnInsertability() { return extractBooleansFromList( insertability ); } public boolean[] getColumnUpdateability() { return extractBooleansFromList( updatability ); } private static boolean[] extractBooleansFromList(List<Boolean> list) { final boolean[] array = new boolean[ list.size() ]; int i = 0; for ( Boolean value : list ) { array[ i++ ] = value; } return array; } public void setJpaAttributeConverterDescriptor(ConverterDescriptor descriptor) { this.attributeConverterDescriptor = descriptor; } private void createParameterImpl() { try { String[] columnsNames = new String[columns.size()]; for ( int i = 0; i < columns.size(); i++ ) { Selectable column = columns.get(i); if (column instanceof Column){ columnsNames[i] = ((Column) column).getName(); } } final XProperty xProperty = (XProperty) typeParameters.get( DynamicParameterizedType.XPROPERTY ); // todo : not sure this works for handling @MapKeyEnumerated final Annotation[] annotations = xProperty == null ? null : xProperty.getAnnotations(); final ClassLoaderService classLoaderService = getMetadata() .getMetadataBuildingOptions() .getServiceRegistry() .getService( ClassLoaderService.class ); typeParameters.put( DynamicParameterizedType.PARAMETER_TYPE, new ParameterTypeImpl( classLoaderService.classForName( typeParameters.getProperty( DynamicParameterizedType.RETURNED_CLASS ) ), annotations, table.getCatalog(), table.getSchema(), table.getName(), Boolean.valueOf( typeParameters.getProperty( DynamicParameterizedType.IS_PRIMARY_KEY ) ), columnsNames ) ); } catch ( ClassLoadingException e ) { throw new MappingException( "Could not create DynamicParameterizedType for type: " + typeName, e ); } } private static final class ParameterTypeImpl implements DynamicParameterizedType.ParameterType { private final Class returnedClass; private final Annotation[] annotationsMethod; private final String catalog; private final String schema; private final String table; private final boolean primaryKey; private final String[] columns; private ParameterTypeImpl(Class returnedClass, Annotation[] annotationsMethod, String catalog, String schema, String table, boolean primaryKey, String[] columns) { this.returnedClass = returnedClass; this.annotationsMethod = annotationsMethod; this.catalog = catalog; this.schema = schema; this.table = table; this.primaryKey = primaryKey; this.columns = columns; } @Override public Class getReturnedClass() { return returnedClass; } @Override public Annotation[] getAnnotationsMethod() { return annotationsMethod; } @Override public String getCatalog() { return catalog; } @Override public String getSchema() { return schema; } @Override public String getTable() { return table; } @Override public boolean isPrimaryKey() { return primaryKey; } @Override public String[] getColumns() { return columns; } } }