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;
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
public SimpleValue(MetadataImplementor metadata) {
this.metadata = metadata;
}
@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 ( defaultSchema!=null ) {
params.setProperty(PersistentIdentifierGenerator.SCHEMA, defaultSchema);
}
if ( defaultCatalog!=null ) {
params.setProperty(PersistentIdentifierGenerator.CATALOG, defaultCatalog);
}
if (rootClass!=null) {
params.setProperty( IdentifierGenerator.ENTITY_NAME, rootClass.getEntityName() );
params.setProperty( IdentifierGenerator.JPA_ENTITY_NAME, rootClass.getJpaEntityName() );
}
String tableName = getTable().getQuotedName(dialect);
params.setProperty( PersistentIdentifierGenerator.TABLE, tableName );
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);
}
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() {
return true;
}
public FetchMode getFetchMode() {
return FetchMode.SELECT;
}
public Properties getIdentifierGeneratorProperties() {
return identifierGeneratorProperties;
}
public String getNullValue() {
return nullValue;
}
public Table getTable() {
return table;
}
public String getIdentifierGeneratorStrategy() {
return identifierGeneratorStrategy;
}
public boolean isIdentityColumn(IdentifierGeneratorFactory identifierGeneratorFactory, Dialect dialect) {
identifierGeneratorFactory.setDialect( dialect );
return IdentityGenerator.class.isAssignableFrom(identifierGeneratorFactory.getIdentifierGeneratorClass( identifierGeneratorStrategy ));
}
public void setIdentifierGeneratorProperties(Properties identifierGeneratorProperties) {
this.identifierGeneratorProperties = identifierGeneratorProperties;
}
public void setIdentifierGeneratorStrategy(String identifierGeneratorStrategy) {
this.identifierGeneratorStrategy = identifierGeneratorStrategy;
}
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 ) {
return true;
}
else if ( !( (Column) selectable ).isNullable() ) {
return false;
}
}
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 ( 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 {
if ( typeName != null ) {
return;
}
if ( type != null ) {
return;
}
if ( attributeConverterDescriptor == null ) {
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();
return;
}
type = buildAttributeConverterTypeAdapter();
}
@SuppressWarnings("unchecked")
private Type buildAttributeConverterTypeAdapter() {
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();
final SqlTypeDescriptor recommendedSqlType = jpaAttributeConverter.getRelationalJavaTypeDescriptor().getJdbcRecommendedSqlType(
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 );
}
final SqlTypeDescriptor sqlTypeDescriptor = getMetadata()
.getMetadataBuildingOptions()
.getServiceRegistry()
.getService( JdbcServices.class )
.getJdbcEnvironment()
.getDialect()
.remapSqlTypeDescriptor(
metadata.getTypeConfiguration()
.getSqlTypeDescriptorRegistry()
.getDescriptor( jdbcTypeCode ) );
final SqlTypeDescriptor sqlTypeDescriptorAdapter = new AttributeConverterSqlTypeDescriptorAdapter(
jpaAttributeConverter,
sqlTypeDescriptor,
jpaAttributeConverter.getRelationalJavaTypeDescriptor()
);
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[] (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 );
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;
}
}
}