org.hibernate.boot.model.source.internal.hbm.RelationalValueSourceHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate-core Show documentation
Show all versions of hibernate-core Show documentation
Hibernate's core ORM functionality
/*
* 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 .
*/
package org.hibernate.boot.model.source.internal.hbm;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.hibernate.boot.MappingException;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmColumnType;
import org.hibernate.boot.model.TruthValue;
import org.hibernate.boot.model.source.spi.ColumnSource;
import org.hibernate.boot.model.source.spi.DerivedValueSource;
import org.hibernate.boot.model.source.spi.RelationalValueSource;
import org.hibernate.boot.model.source.spi.SizeSource;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
/**
* @author Steve Ebersole
*/
public class RelationalValueSourceHelper {
/**
* Internal unifying contract used in creating {@link org.hibernate.mapping.Column}
* and {@link org.hibernate.mapping.Formula} instances. It adapts the variances across
* the different mappings which contribute column/formula information. For example,
* consider the {@code } mapping which might have:
* - a {@code column} XML attribute
* - a {@code formula} XML attribute
* - one or more nested {@code
} XML elements
* - a nested {@code
} XML element
*
* as opposed to a {@code } mapping, which can only have:
* - a {@code column} XML attribute
*
*/
public interface ColumnsAndFormulasSource {
/**
* What kind of XML element does this information come from?
*
* @return The source XML element type
*/
XmlElementMetadata getSourceType();
/**
* The name of the source. May be {@code null} if none was specified. Will be treated
* as {@code null} if the nature says it cannot be named.
*
* @return The name of the source.
*
* @see XmlElementMetadata#canBeNamed()
*/
String getSourceName();
/**
* Access to any formula defined via XML attribute.
*
* @return formula, if one, as defined via XML attribute.
*/
String getFormulaAttribute();
/**
* Access to any nested {@code } or {@code } XML elements.
*
* @return columns or formulas defined via nested XML elements.
*/
List getColumnOrFormulaElements();
/**
* Access to any column defined via XML attribute.
*
* @return column, if one, as defined via XML attribute.
*/
String getColumnAttribute();
SizeSource getSizeSource();
Boolean isNullable();
boolean isUnique();
Set getIndexConstraintNames();
Set getUniqueKeyConstraintNames();
}
public abstract static class AbstractColumnsAndFormulasSource implements ColumnsAndFormulasSource {
@Override
public String getFormulaAttribute() {
return null;
}
@Override
public String getColumnAttribute() {
return null;
}
@Override
public List getColumnOrFormulaElements() {
return Collections.emptyList();
}
@Override
public SizeSource getSizeSource() {
return null;
}
@Override
public Boolean isNullable() {
return null;
}
@Override
public Set getIndexConstraintNames() {
return Collections.emptySet();
}
@Override
public boolean isUnique() {
return false;
}
@Override
public Set getUniqueKeyConstraintNames() {
return Collections.emptySet();
}
}
/**
* Given a {@link ColumnsAndFormulasSource}, build a single {@link RelationalValueSource}.
* More than one {@link RelationalValueSource} will result in an exception.
*
* @param mappingDocument the mapping document
* @param containingTableName The logical name of the table containing the relational values
* @param columnsAndFormulasSource the adapter describing the value sources.
*
* @return The single RelationalValueSource.
*/
public static RelationalValueSource buildValueSource(
MappingDocument mappingDocument,
String containingTableName,
RelationalValueSourceHelper.ColumnsAndFormulasSource columnsAndFormulasSource) {
final List sources = buildValueSources(
mappingDocument,
containingTableName,
columnsAndFormulasSource
);
if ( sources.size() > 1 ) {
final String errorMessage;
if ( columnsAndFormulasSource.getSourceType().canBeNamed()
&& StringHelper.isNotEmpty( columnsAndFormulasSource.getSourceName() ) ) {
errorMessage = String.format(
Locale.ENGLISH,
"Expecting just a single formula/column in context of <%s name=\"%s\"/>",
columnsAndFormulasSource.getSourceType().getElementName(),
columnsAndFormulasSource.getSourceName()
);
}
else {
errorMessage = String.format(
Locale.ENGLISH,
"Expecting just a single formula/column in context of <%s/>",
columnsAndFormulasSource.getSourceType().getElementName()
);
}
throw new MappingException( errorMessage, mappingDocument.getOrigin() );
}
return sources.get( 0 );
}
/**
* Given a {@link ColumnsAndFormulasSource}, build a single {@link RelationalValueSource}
* which is required to be a column. More than one {@link RelationalValueSource} will result
* in an exception. A formula, rather than a column, will result in an exception.
*
* @param mappingDocument the mapping document
* @param containingTableName The logical name of the table containing the relational values
* @param columnsAndFormulasSource the adapter describing the value sources.
*
* @return The single ColumnSource.
*/
public static ColumnSource buildColumnSource(
MappingDocument mappingDocument,
String containingTableName,
RelationalValueSourceHelper.ColumnsAndFormulasSource columnsAndFormulasSource) {
final List sources = buildValueSources(
mappingDocument,
containingTableName,
columnsAndFormulasSource
);
if ( sources.size() > 1 ) {
final String errorMessage;
if ( columnsAndFormulasSource.getSourceType().canBeNamed()
&& StringHelper.isNotEmpty( columnsAndFormulasSource.getSourceName() ) ) {
errorMessage = String.format(
Locale.ENGLISH,
"Expecting just a single formula/column in context of <%s name=\"%s\"/>",
columnsAndFormulasSource.getSourceType().getElementName(),
columnsAndFormulasSource.getSourceName()
);
}
else {
errorMessage = String.format(
Locale.ENGLISH,
"Expecting just a single formula/column in context of <%s/>",
columnsAndFormulasSource.getSourceType().getElementName()
);
}
throw new MappingException( errorMessage, mappingDocument.getOrigin() );
}
final RelationalValueSource result = sources.get( 0 );
if ( !ColumnSource.class.isInstance( result ) ) {
final String errorMessage;
if ( columnsAndFormulasSource.getSourceType().canBeNamed()
&& StringHelper.isNotEmpty( columnsAndFormulasSource.getSourceName() ) ) {
errorMessage = String.format(
Locale.ENGLISH,
"Expecting single column in context of <%s name=\"%s\"/>, but found formula [%s]",
columnsAndFormulasSource.getSourceType().getElementName(),
columnsAndFormulasSource.getSourceName(),
( (DerivedValueSource) result ).getExpression()
);
}
else {
errorMessage = String.format(
Locale.ENGLISH,
"Expecting single column in context of <%s/>, but found formula [%s]",
columnsAndFormulasSource.getSourceType().getElementName(),
( (DerivedValueSource) result ).getExpression()
);
}
throw new MappingException( errorMessage, mappingDocument.getOrigin() );
}
return (ColumnSource) result;
}
/**
* Given a {@link ColumnsAndFormulasSource}, build the corresponding list of
* {@link ColumnSource}. Any formula, rather than a column, will result in an exception.
*
* @param mappingDocument the mapping document
* @param containingTableName The logical name of the table containing the relational values
* @param columnsAndFormulasSource the adapter describing the value sources.
*
* @return The corresponding list.
*/
public static List buildColumnSources(
MappingDocument mappingDocument,
String containingTableName,
RelationalValueSourceHelper.ColumnsAndFormulasSource columnsAndFormulasSource) {
final List sources = buildValueSources(
mappingDocument,
containingTableName,
columnsAndFormulasSource
);
final List columnSources = CollectionHelper.arrayList( sources.size() );
for ( RelationalValueSource source : sources ) {
if ( !ColumnSource.class.isInstance( source ) ) {
final String errorMessage;
if ( columnsAndFormulasSource.getSourceType().canBeNamed()
&& StringHelper.isNotEmpty( columnsAndFormulasSource.getSourceName() ) ) {
errorMessage = String.format(
Locale.ENGLISH,
"Expecting only columns in context of <%s name=\"%s\"/>, but found formula [%s]",
columnsAndFormulasSource.getSourceType().getElementName(),
columnsAndFormulasSource.getSourceName(),
( (DerivedValueSource) source ).getExpression()
);
}
else {
errorMessage = String.format(
Locale.ENGLISH,
"Expecting only columns in context of <%s/>, but found formula [%s]",
columnsAndFormulasSource.getSourceType().getElementName(),
( (DerivedValueSource) source ).getExpression()
);
}
throw new MappingException( errorMessage, mappingDocument.getOrigin() );
}
columnSources.add( (ColumnSource) source );
}
return columnSources;
}
/**
* Given a {@link ColumnsAndFormulasSource}, build the corresponding list of
* {@link RelationalValueSource}
*
* @param mappingDocument the mapping document
* @param containingTableName The logical name of the table containing the relational values
* @param columnsAndFormulasSource the adapter describing the value sources.
*
* @return The corresponding list.
*/
public static List buildValueSources(
MappingDocument mappingDocument,
String containingTableName,
RelationalValueSourceHelper.ColumnsAndFormulasSource columnsAndFormulasSource) {
List result = new ArrayList();
if ( StringHelper.isNotEmpty( columnsAndFormulasSource.getFormulaAttribute() ) ) {
// we have an explicit formula attribute (i.e., )
validateUseOfFormulaAttribute( mappingDocument, columnsAndFormulasSource );
result.add(
new FormulaImpl(
mappingDocument,
containingTableName,
columnsAndFormulasSource.getFormulaAttribute()
)
);
}
else if ( CollectionHelper.isNotEmpty( columnsAndFormulasSource.getColumnOrFormulaElements() ) ) {
validateUseOfColumnOrFormulaNestedElements( mappingDocument, columnsAndFormulasSource );
for ( Object selectable : columnsAndFormulasSource.getColumnOrFormulaElements() ) {
if ( selectable instanceof JaxbHbmColumnType ) {
final JaxbHbmColumnType columnElement = (JaxbHbmColumnType) selectable;
result.add(
new ColumnSourceImpl(
mappingDocument,
containingTableName,
columnElement,
columnsAndFormulasSource.getIndexConstraintNames(),
columnsAndFormulasSource.getUniqueKeyConstraintNames()
)
);
}
else if ( selectable instanceof String ) {
result.add(
new FormulaImpl( mappingDocument, containingTableName, (String) selectable )
);
}
else {
throw new MappingException(
"Unexpected column/formula JAXB type : " + selectable.getClass().getName(),
mappingDocument.getOrigin()
);
}
}
}
else {
// we have either an explicitly named column via the column attribute, or an implicit
// column reference. Aside from applying an implicit naming strategy (or not), these 2
// case are handled the exact same way
result.add(
new ColumnAttributeSourceImpl(
mappingDocument,
containingTableName,
columnsAndFormulasSource.getColumnAttribute(),
columnsAndFormulasSource.getSizeSource(),
interpretNullabilityToTruthValue( columnsAndFormulasSource.isNullable() ),
columnsAndFormulasSource.isUnique() ? TruthValue.TRUE : TruthValue.FALSE,
columnsAndFormulasSource.getIndexConstraintNames(),
columnsAndFormulasSource.getUniqueKeyConstraintNames()
)
);
}
return result;
}
private static TruthValue interpretNullabilityToTruthValue(Boolean nullable) {
if ( nullable == null ) {
return TruthValue.UNKNOWN;
}
else {
return nullable ? TruthValue.TRUE : TruthValue.FALSE;
}
}
private static void validateUseOfFormulaAttribute(
MappingDocument sourceDocument,
RelationalValueSourceHelper.ColumnsAndFormulasSource columnsAndFormulasSource) {
// 1) make sure there is no column attribute
if ( StringHelper.isNotEmpty( columnsAndFormulasSource.getColumnAttribute() ) ) {
final String errorMessage;
if ( columnsAndFormulasSource.getSourceType().canBeNamed()
&& StringHelper.isNotEmpty( columnsAndFormulasSource.getSourceName() ) ) {
errorMessage = String.format(
Locale.ENGLISH,
"column attribute and formula attribute may not be specified together near <%s name=\"%s\" column=\"%s\" formula=\"%s\" />",
columnsAndFormulasSource.getSourceType().getElementName(),
columnsAndFormulasSource.getSourceName(),
columnsAndFormulasSource.getColumnAttribute(),
columnsAndFormulasSource.getFormulaAttribute()
);
}
else {
errorMessage = String.format(
Locale.ENGLISH,
"column attribute and formula attribute may not be specified together near <%s column=\"%s\" formula=\"%s\" />",
columnsAndFormulasSource.getSourceType().getElementName(),
columnsAndFormulasSource.getColumnAttribute(),
columnsAndFormulasSource.getFormulaAttribute()
);
}
throw new MappingException( errorMessage, sourceDocument.getOrigin() );
}
// 2) and no column/formula nested elements
if ( CollectionHelper.isNotEmpty( columnsAndFormulasSource.getColumnOrFormulaElements() ) ) {
final String errorMessage;
if ( columnsAndFormulasSource.getSourceType().canBeNamed()
&& StringHelper.isNotEmpty( columnsAndFormulasSource.getSourceName() ) ) {
errorMessage = String.format(
Locale.ENGLISH,
"formula attribute may not be specified along with or subelement(s) near <%s name=\"%s\" formula=\"%s\" />",
columnsAndFormulasSource.getSourceType().getElementName(),
columnsAndFormulasSource.getSourceName(),
columnsAndFormulasSource.getFormulaAttribute()
);
}
else {
errorMessage = String.format(
Locale.ENGLISH,
"formula attribute may not be specified along with or subelement(s) near <%s formula=\"%s\" />",
columnsAndFormulasSource.getSourceType().getElementName(),
columnsAndFormulasSource.getFormulaAttribute()
);
}
throw new MappingException( errorMessage, sourceDocument.getOrigin() );
}
}
private static void validateUseOfColumnOrFormulaNestedElements(
MappingDocument sourceDocument,
RelationalValueSourceHelper.ColumnsAndFormulasSource columnsAndFormulasSource) {
if ( StringHelper.isNotEmpty( columnsAndFormulasSource.getColumnAttribute() ) ) {
final String errorMessage;
if ( columnsAndFormulasSource.getSourceType().canBeNamed()
&& StringHelper.isNotEmpty( columnsAndFormulasSource.getSourceName() ) ) {
errorMessage = String.format(
Locale.ENGLISH,
"column attribute may not be specified along with or subelement(s) near <%s name=\"%s\" column=\"%s\" />",
columnsAndFormulasSource.getSourceType().getElementName(),
columnsAndFormulasSource.getSourceName(),
columnsAndFormulasSource.getColumnAttribute()
);
}
else {
errorMessage = String.format(
Locale.ENGLISH,
"column attribute may not be specified along with or subelement(s) near <%s column=\"%s\" />",
columnsAndFormulasSource.getSourceType().getElementName(),
columnsAndFormulasSource.getColumnAttribute()
);
}
throw new MappingException( errorMessage, sourceDocument.getOrigin() );
}
}
private static void validateCustomWriteFragment(
MappingDocument sourceDocument,
RelationalValueSourceHelper.ColumnsAndFormulasSource columnsAndFormulasSource,
JaxbHbmColumnType columnMapping,
String customWrite) {
if ( customWrite != null && !customWrite.matches("[^?]*\\?[^?]*") ) {
final String errorMessage;
if ( columnsAndFormulasSource.getSourceType().canBeNamed()
&& StringHelper.isNotEmpty( columnsAndFormulasSource.getSourceName() ) ) {
errorMessage = String.format(
Locale.ENGLISH,
"write expression must contain exactly one value placeholder ('?') character near for <%s name=\"%s\" />",
columnMapping.getName(),
customWrite,
columnsAndFormulasSource.getSourceType().getElementName(),
columnsAndFormulasSource.getSourceName()
);
}
else {
errorMessage = String.format(
Locale.ENGLISH,
"write expression must contain exactly one value placeholder ('?') character near for <%s />",
columnMapping.getName(),
customWrite,
columnsAndFormulasSource.getSourceType().getElementName()
);
}
throw new MappingException( errorMessage, sourceDocument.getOrigin() );
}
}
}