org.hibernate.cfg.annotations.CollectionBinder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate-annotations
Show all versions of hibernate-annotations
Annotations metadata for Hibernate
package org.hibernate.cfg.annotations;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Embeddable;
import javax.persistence.FetchType;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.MapKey;
import javax.persistence.OneToMany;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.FetchMode;
import org.hibernate.MappingException;
import org.hibernate.engine.ExecuteUpdateResultCheckStyle;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CollectionId;
import org.hibernate.annotations.CollectionOfElements;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.Filter;
import org.hibernate.annotations.FilterJoinTable;
import org.hibernate.annotations.FilterJoinTables;
import org.hibernate.annotations.Filters;
import org.hibernate.annotations.ForeignKey;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.hibernate.annotations.OrderBy;
import org.hibernate.annotations.Sort;
import org.hibernate.annotations.SortType;
import org.hibernate.annotations.Where;
import org.hibernate.annotations.WhereJoinTable;
import org.hibernate.annotations.SQLInsert;
import org.hibernate.annotations.SQLUpdate;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLDeleteAll;
import org.hibernate.annotations.Loader;
import org.hibernate.cfg.AnnotatedClassType;
import org.hibernate.cfg.AnnotationBinder;
import org.hibernate.cfg.BinderHelper;
import org.hibernate.cfg.CollectionSecondPass;
import org.hibernate.cfg.Ejb3Column;
import org.hibernate.cfg.Ejb3JoinColumn;
import org.hibernate.cfg.ExtendedMappings;
import org.hibernate.cfg.IndexColumn;
import org.hibernate.cfg.PropertyData;
import org.hibernate.cfg.PropertyHolder;
import org.hibernate.cfg.PropertyHolderBuilder;
import org.hibernate.cfg.PropertyPreloadedData;
import org.hibernate.cfg.SecondPass;
import org.hibernate.mapping.Backref;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.DependantValue;
import org.hibernate.mapping.IdGenerator;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.KeyValue;
import org.hibernate.mapping.ManyToOne;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.Table;
import org.hibernate.reflection.XClass;
import org.hibernate.reflection.XProperty;
import org.hibernate.util.StringHelper;
/**
* Collection binder
*
* @author inger
* @author Emmanuel Bernard
*/
public abstract class CollectionBinder {
private static final Log log = LogFactory.getLog( CollectionBinder.class );
protected Collection collection;
protected String propertyName;
PropertyHolder propertyHolder;
int batchSize;
private String mappedBy;
private XClass collectionType;
private XClass targetEntity;
private ExtendedMappings mappings;
private Ejb3JoinColumn[] inverseJoinColumns;
private String cascadeStrategy;
String cacheConcurrencyStrategy;
String cacheRegionName;
private boolean oneToMany;
protected IndexColumn indexColumn;
private String orderBy;
protected String hqlOrderBy;
private boolean isSorted;
private Class comparator;
private boolean hasToBeSorted;
protected boolean cascadeDeleteEnabled;
protected String mapKeyPropertyName;
private boolean insertable = true;
private boolean updatable = true;
private Ejb3JoinColumn[] fkJoinColumns;
private boolean isExplicitAssociationTable;
private Ejb3Column[] elementColumns;
private boolean isEmbedded;
private XProperty property;
private boolean ignoreNotFound;
private TableBinder tableBinder;
private Ejb3Column[] mapKeyColumns;
private Ejb3JoinColumn[] mapKeyManyToManyColumns;
protected HashMap localGenerators;
public void setUpdatable(boolean updatable) {
this.updatable = updatable;
}
public void setInsertable(boolean insertable) {
this.insertable = insertable;
}
public void setCascadeStrategy(String cascadeStrategy) {
this.cascadeStrategy = cascadeStrategy;
}
public void setPropertyAccessorName(String propertyAccessorName) {
this.propertyAccessorName = propertyAccessorName;
}
private String propertyAccessorName;
public void setInverseJoinColumns(Ejb3JoinColumn[] inverseJoinColumns) {
this.inverseJoinColumns = inverseJoinColumns;
}
public void setJoinColumns(Ejb3JoinColumn[] joinColumns) {
this.joinColumns = joinColumns;
}
private Ejb3JoinColumn[] joinColumns;
public void setPropertyHolder(PropertyHolder propertyHolder) {
this.propertyHolder = propertyHolder;
}
public void setBatchSize(BatchSize batchSize) {
this.batchSize = batchSize == null ? -1 : batchSize.size();
}
public void setEjb3OrderBy(javax.persistence.OrderBy orderByAnn) {
if ( orderByAnn != null ) {
hqlOrderBy = orderByAnn.value();
}
}
public void setSqlOrderBy(OrderBy orderByAnn) {
if ( orderByAnn != null ) {
if ( ! BinderHelper.isDefault( orderByAnn.clause() ) ) orderBy = orderByAnn.clause();
}
}
public void setSort(Sort sortAnn) {
if ( sortAnn != null ) {
isSorted = ! SortType.UNSORTED.equals( sortAnn.type() );
if ( isSorted && SortType.COMPARATOR.equals( sortAnn.type() ) ) {
comparator = sortAnn.comparator();
}
}
}
/**
* collection binder factory
*/
public static CollectionBinder getCollectionBinder(
String entityName, XProperty property,
boolean isIndexed
) {
if ( property.isArray() ) {
if ( property.getElementClass().isPrimitive() ) {
return new PrimitiveArrayBinder();
}
else {
return new ArrayBinder();
}
}
else if ( property.isCollection() ) {
//TODO consider using an XClass
Class returnedClass = property.getCollectionClass();
if ( java.util.Set.class.equals( returnedClass ) ) {
return new SetBinder();
}
else if ( java.util.SortedSet.class.equals( returnedClass ) ) {
return new SetBinder( true );
}
else if ( java.util.Map.class.equals( returnedClass ) ) {
return new MapBinder();
}
else if ( java.util.SortedMap.class.equals( returnedClass ) ) {
return new MapBinder(true);
}
else if ( java.util.Collection.class.equals( returnedClass ) ) {
if ( property.isAnnotationPresent( CollectionId.class ) ) {
return new IdBagBinder();
}
else {
return new BagBinder();
}
}
else if ( java.util.List.class.equals( returnedClass ) ) {
if ( isIndexed ) {
return new ListBinder();
}
else if ( property.isAnnotationPresent( CollectionId.class ) ) {
return new IdBagBinder();
}
else {
return new BagBinder();
}
}
else {
throw new AnnotationException(
returnedClass.getName() + " collection not yet supported: "
+ StringHelper.qualify( entityName, property.getName() )
);
}
}
else {
throw new AnnotationException(
"Illegal attempt to map a non collection as a @OneToMany, @ManyToMany or @CollectionOfElements: "
+ StringHelper.qualify( entityName, property.getName() )
);
}
}
protected CollectionBinder() {
}
protected CollectionBinder(boolean sorted) {
this.hasToBeSorted = sorted;
}
public void setMappedBy(String mappedBy) {
this.mappedBy = mappedBy;
}
public void setTableBinder(TableBinder tableBinder) {
this.tableBinder = tableBinder;
}
public void setCollectionType(XClass collectionType) {
this.collectionType = collectionType;
}
public void setTargetEntity(XClass targetEntity) {
this.targetEntity = targetEntity;
}
public void setMappings(ExtendedMappings mappings) {
this.mappings = mappings;
}
protected abstract Collection createCollection(PersistentClass persistentClass);
public Collection getCollection() {
return collection;
}
public void setPropertyName(String propertyName) {
this.propertyName = propertyName;
}
public void bind() {
this.collection = createCollection( propertyHolder.getPersistentClass() );
log.debug( "Collection role: " + StringHelper.qualify( propertyHolder.getPath(), propertyName ) );
collection.setRole( StringHelper.qualify( propertyHolder.getPath(), propertyName ) );
if ( property.isAnnotationPresent( org.hibernate.annotations.MapKey.class ) && mapKeyPropertyName != null ) {
throw new AnnotationException(
"Cannot mix @javax.persistence.MapKey and @org.hibernate.annotations.MapKey "
+ "on the same collection: " + StringHelper.qualify(
propertyHolder.getPath(), propertyName
)
);
}
//set laziness
defineFetchingStrategy();
//collection.setFetchMode( fetchMode );
//collection.setLazy( fetchMode == FetchMode.SELECT );
collection.setBatchSize( batchSize );
if ( orderBy != null && hqlOrderBy != null ) {
throw new AnnotationException(
"Cannot use sql order by clause in conjunction of EJB3 order by clause: " + safeCollectionRole()
);
}
// set ordering
if ( orderBy != null ) collection.setOrderBy( orderBy );
if ( isSorted ) {
collection.setSorted( true );
if ( comparator != null ) {
try {
collection.setComparator( (Comparator) comparator.newInstance() );
}
catch (ClassCastException e) {
throw new AnnotationException(
"Comparator not implementing java.util.Comparator class: "
+ comparator.getName() + "(" + safeCollectionRole() + ")"
);
}
catch (Exception e) {
throw new AnnotationException(
"Could not instantiate comparator class: "
+ comparator.getName() + "(" + safeCollectionRole() + ")"
);
}
}
}
else {
if ( hasToBeSorted ) {
throw new AnnotationException(
"A sorted collection has to define @Sort: "
+ safeCollectionRole()
);
}
}
//set cache
if ( StringHelper.isNotEmpty( cacheConcurrencyStrategy ) ) {
collection.setCacheConcurrencyStrategy( cacheConcurrencyStrategy );
collection.setCacheRegionName( cacheRegionName );
}
//SQL overriding
SQLInsert sqlInsert = property.getAnnotation( SQLInsert.class );
SQLUpdate sqlUpdate = property.getAnnotation( SQLUpdate.class );
SQLDelete sqlDelete = property.getAnnotation( SQLDelete.class );
SQLDeleteAll sqlDeleteAll = property.getAnnotation( SQLDeleteAll.class );
Loader loader = property.getAnnotation( Loader.class );
if ( sqlInsert != null ) {
collection.setCustomSQLInsert( sqlInsert.sql().trim(), sqlInsert.callable(),
ExecuteUpdateResultCheckStyle.parse( sqlInsert.check().toString().toLowerCase() )
);
}
if ( sqlUpdate != null ) {
collection.setCustomSQLUpdate( sqlUpdate.sql(), sqlUpdate.callable(),
ExecuteUpdateResultCheckStyle.parse( sqlUpdate.check().toString().toLowerCase() )
);
}
if ( sqlDelete != null ) {
collection.setCustomSQLDelete( sqlDelete.sql(), sqlDelete.callable(),
ExecuteUpdateResultCheckStyle.parse( sqlDelete.check().toString().toLowerCase() )
);
}
if ( sqlDeleteAll != null ) {
collection.setCustomSQLDelete( sqlDeleteAll.sql(), sqlDeleteAll.callable(),
ExecuteUpdateResultCheckStyle.parse( sqlDeleteAll.check().toString().toLowerCase() )
);
}
if ( loader != null ) {
collection.setLoaderName( loader.namedQuery() );
}
//work on association
boolean isMappedBy = ! BinderHelper.isDefault( mappedBy );
collection.setInverse( isMappedBy );
String collType = getCollectionType().getName();
//many to many may need some second pass informations
if ( ! oneToMany && isMappedBy ) {
mappings.addMappedBy( collType, mappedBy, propertyName );
}
//TODO reducce tableBinder != null and oneToMany
//FIXME collection of elements shouldn't be executed as a secondpass
SecondPass sp = getSecondPass(
fkJoinColumns,
joinColumns,
inverseJoinColumns,
elementColumns,
mapKeyColumns, mapKeyManyToManyColumns, isEmbedded,
property, collType,
ignoreNotFound, oneToMany,
tableBinder, mappings
);
XClass collectionType = getCollectionType();
if ( collectionType.isAnnotationPresent( Embeddable.class )
|| property.isAnnotationPresent( CollectionOfElements.class ) ) {
// do it right away, otherwise @ManyToon on composite element call addSecondPass
// and raise a ConcurrentModificationException
//sp.doSecondPass( CollectionHelper.EMPTY_MAP );
mappings.addSecondPass( sp, ! isMappedBy );
}
else {
mappings.addSecondPass( sp, ! isMappedBy );
}
mappings.addCollection( collection );
//property building
PropertyBinder binder = new PropertyBinder();
binder.setName( propertyName );
binder.setValue( collection );
binder.setCascade( cascadeStrategy );
if ( cascadeStrategy != null && cascadeStrategy.indexOf( "delete-orphan" ) >= 0 ) {
collection.setOrphanDelete( true );
}
binder.setPropertyAccessorName( propertyAccessorName );
binder.setProperty( property );
binder.setInsertable( insertable );
binder.setUpdatable( updatable );
Property prop = binder.make();
//we don't care about the join stuffs because the column is on the association table.
propertyHolder.addProperty( prop );
}
private void defineFetchingStrategy() {
LazyCollection lazy = property.getAnnotation( LazyCollection.class );
Fetch fetch = property.getAnnotation( Fetch.class );
OneToMany oneToMany = property.getAnnotation( OneToMany.class );
ManyToMany manyToMany = property.getAnnotation( ManyToMany.class );
CollectionOfElements elements = property.getAnnotation( CollectionOfElements.class );
FetchType fetchType;
if ( oneToMany != null ) {
fetchType = oneToMany.fetch();
}
else if ( manyToMany != null ) {
fetchType = manyToMany.fetch();
}
else if ( elements != null ) {
fetchType = elements.fetch();
}
else {
throw new AssertionFailure(
"Define fetch strategy on a property not annotated with @ManyToOne nor @OneToMany nor @CollectionOfElements"
);
}
if ( lazy != null ) {
collection.setLazy( ! ( lazy.value() == LazyCollectionOption.FALSE ) );
collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA );
}
else {
collection.setLazy( fetchType == FetchType.LAZY );
collection.setExtraLazy( false );
}
if ( fetch != null ) {
if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) {
collection.setFetchMode( FetchMode.JOIN );
collection.setLazy( false );
}
else if ( fetch.value() == org.hibernate.annotations.FetchMode.SELECT ) {
collection.setFetchMode( FetchMode.SELECT );
}
else if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) {
collection.setFetchMode( FetchMode.SELECT );
collection.setSubselectLoadable( true );
collection.getOwner().setSubselectLoadableCollections( true );
}
else {
throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() );
}
}
else {
collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) );
}
}
private XClass getCollectionType() {
if ( AnnotationBinder.isDefault( targetEntity, mappings ) ) {
if ( collectionType != null ) {
return collectionType;
}
else {
String errorMsg = "Collection has neither generic type or OneToMany.targetEntity() defined: "
+ safeCollectionRole();
throw new AnnotationException( errorMsg );
}
}
else {
return targetEntity;
}
}
public SecondPass getSecondPass(
final Ejb3JoinColumn[] fkJoinColumns, final Ejb3JoinColumn[] keyColumns,
final Ejb3JoinColumn[] inverseColumns,
final Ejb3Column[] elementColumns,
final Ejb3Column[] mapKeyColumns, final Ejb3JoinColumn[] mapKeyManyToManyColumns, final boolean isEmbedded,
final XProperty property, final String collType,
final boolean ignoreNotFound, final boolean unique,
final TableBinder assocTableBinder, final ExtendedMappings mappings
) {
return new CollectionSecondPass( mappings, collection ) {
public void secondPass(java.util.Map persistentClasses, java.util.Map inheritedMetas)
throws MappingException {
bindStarToManySecondPass(
persistentClasses, collType, fkJoinColumns, keyColumns, inverseColumns, elementColumns,
isEmbedded, property, unique, assocTableBinder, ignoreNotFound, mappings
);
}
};
}
/**
* return true if it's a Fk, false if it's an association table
*/
protected boolean bindStarToManySecondPass(
Map persistentClasses, String collType, Ejb3JoinColumn[] fkJoinColumns,
Ejb3JoinColumn[] keyColumns, Ejb3JoinColumn[] inverseColumns, Ejb3Column[] elementColumns,
boolean isEmbedded,
XProperty property, boolean unique,
TableBinder associationTableBinder, boolean ignoreNotFound, ExtendedMappings mappings
) {
PersistentClass persistentClass = (PersistentClass) persistentClasses.get( collType );
boolean reversePropertyInJoin = false;
if ( persistentClass != null && StringHelper.isNotEmpty( this.mappedBy ) ) {
try {
reversePropertyInJoin = 0 != persistentClass.getJoinNumber(
persistentClass.getRecursiveProperty( this.mappedBy )
);
}
catch (MappingException e) {
StringBuilder error = new StringBuilder( 80 );
error.append( "mappedBy reference an unknown target entity property: " )
.append( collType ).append( "." ).append( this.mappedBy )
.append( " in " )
.append( collection.getOwnerEntityName() )
.append( "." )
.append( property.getName() );
throw new AnnotationException( error.toString() );
}
}
if ( persistentClass != null
&& ! reversePropertyInJoin
&& oneToMany
&& ! this.isExplicitAssociationTable
&& ( joinColumns[0].isImplicit() && ! BinderHelper.isDefault( this.mappedBy ) //implicit @JoinColumn
|| ! fkJoinColumns[0].isImplicit() ) //this is an explicit @JoinColumn
) {
//this is a Foreign key
bindOneToManySecondPass(
getCollection(),
persistentClasses,
fkJoinColumns,
collType,
cascadeDeleteEnabled,
ignoreNotFound, hqlOrderBy,
mappings
);
return true;
}
else {
//this is an association table
bindManyToManySecondPass(
this.collection,
persistentClasses,
keyColumns,
inverseColumns,
elementColumns,
isEmbedded, collType,
ignoreNotFound, unique,
cascadeDeleteEnabled,
associationTableBinder, property, propertyHolder, hqlOrderBy, mappings
);
return false;
}
}
protected void bindOneToManySecondPass(
Collection collection, Map persistentClasses, Ejb3JoinColumn[] fkJoinColumns,
String collectionType,
boolean cascadeDeleteEnabled, boolean ignoreNotFound, String hqlOrderBy, ExtendedMappings extendedMappings
) {
if ( log.isDebugEnabled() ) {
log.debug(
"Binding a OneToMany: " + propertyHolder.getEntityName() + "." + propertyName + " through a foreign key"
);
}
org.hibernate.mapping.OneToMany oneToMany = new org.hibernate.mapping.OneToMany( collection.getOwner() );
collection.setElement( oneToMany );
oneToMany.setReferencedEntityName( collectionType );
oneToMany.setIgnoreNotFound( ignoreNotFound );
String assocClass = oneToMany.getReferencedEntityName();
PersistentClass associatedClass = (PersistentClass) persistentClasses.get( assocClass );
String orderBy = buildOrderByClauseFromHql( hqlOrderBy, associatedClass, collection.getRole() );
if ( orderBy != null ) collection.setOrderBy( orderBy );
if ( mappings == null ) {
throw new AssertionFailure(
"CollectionSecondPass for oneToMany should not be called with null mappings"
);
}
Map joins = mappings.getJoins( assocClass );
if ( associatedClass == null ) {
throw new MappingException(
"Association references unmapped class: " + assocClass
);
}
oneToMany.setAssociatedClass( associatedClass );
for ( Ejb3JoinColumn column : fkJoinColumns ) {
column.setPersistentClass( associatedClass, joins );
column.setJoins( joins );
collection.setCollectionTable( column.getTable() );
}
log.info(
"Mapping collection: " + collection.getRole() + " -> " + collection.getCollectionTable().getName()
);
bindFilters(false);
bindCollectionSecondPass( collection, null, fkJoinColumns, cascadeDeleteEnabled, property, mappings );
if ( !collection.isInverse()
&& !collection.getKey().isNullable() ) {
// for non-inverse one-to-many, with a not-null fk, add a backref!
String entityName = oneToMany.getReferencedEntityName();
PersistentClass referenced = mappings.getClass( entityName );
Backref prop = new Backref();
prop.setName( '_' + fkJoinColumns[0].getPropertyName() + "Backref" );
prop.setUpdateable( false );
prop.setSelectable( false );
prop.setCollectionRole( collection.getRole() );
prop.setEntityName( collection.getOwner().getEntityName() );
prop.setValue( collection.getKey() );
referenced.addProperty( prop );
}
}
private void bindFilters(boolean hasAssociationTable) {
Filter simpleFilter = property.getAnnotation( Filter.class );
//set filtering
//test incompatible choices
//if ( StringHelper.isNotEmpty( where ) ) collection.setWhere( where );
if (simpleFilter != null) {
if (hasAssociationTable) {
collection.addManyToManyFilter( simpleFilter.name(), getCondition( simpleFilter ) );
}
else {
collection.addFilter( simpleFilter.name(), getCondition( simpleFilter ) );
}
}
Filters filters = property.getAnnotation( Filters.class );
if (filters != null) {
for ( Filter filter : filters.value() ) {
if (hasAssociationTable) {
collection.addManyToManyFilter( filter.name(), getCondition( filter ) );
}
else {
collection.addFilter( filter.name(), getCondition( filter ) );
}
}
}
FilterJoinTable simpleFilterJoinTable = property.getAnnotation( FilterJoinTable.class );
if (simpleFilterJoinTable != null) {
if (hasAssociationTable) {
collection.addFilter( simpleFilterJoinTable.name(), getCondition( simpleFilterJoinTable ) );
}
else {
throw new AnnotationException(
"Illegal use of @FilterJoinTable on an association without join table:"
+ StringHelper.qualify( propertyHolder.getPath(), propertyName )
);
}
}
FilterJoinTables filterJoinTables = property.getAnnotation( FilterJoinTables.class );
if (filterJoinTables != null) {
for ( FilterJoinTable filter : filterJoinTables.value() ) {
if (hasAssociationTable) {
collection.addFilter( filter.name(), getCondition( filter ) );
}
else {
throw new AnnotationException(
"Illegal use of @FilterJoinTable on an association without join table:"
+ StringHelper.qualify( propertyHolder.getPath(), propertyName )
);
}
}
}
Where where = property.getAnnotation( Where.class );
String whereClause = where == null ? null : where.clause();
if ( StringHelper.isNotEmpty( whereClause ) ) {
if (hasAssociationTable) {
collection.setManyToManyWhere( whereClause );
}
else {
collection.setWhere( whereClause );
}
}
WhereJoinTable whereJoinTable = property.getAnnotation( WhereJoinTable.class );
String whereJoinTableClause = whereJoinTable == null ? null : whereJoinTable.clause();
if ( StringHelper.isNotEmpty( whereJoinTableClause ) ) {
if (hasAssociationTable) {
collection.setWhere( whereJoinTableClause );
}
else {
throw new AnnotationException(
"Illegal use of @WhereJoinTable on an association without join table:"
+ StringHelper.qualify( propertyHolder.getPath(), propertyName )
);
}
}
// This cannot happen in annotations since the second fetch is hardcoded to join
// if ( ( ! collection.getManyToManyFilterMap().isEmpty() || collection.getManyToManyWhere() != null ) &&
// collection.getFetchMode() == FetchMode.JOIN &&
// collection.getElement().getFetchMode() != FetchMode.JOIN ) {
// throw new MappingException(
// "association with join table defining filter or where without join fetching " +
// "not valid within collection using join fetching [" + collection.getRole() + "]"
// );
// }
}
private String getCondition(FilterJoinTable filter) {
//set filtering
String name = filter.name();
String cond = filter.condition();
return getCondition( cond, name );
}
private String getCondition(Filter filter) {
//set filtering
String name = filter.name();
String cond = filter.condition();
return getCondition( cond, name );
}
private String getCondition(String cond, String name) {
if ( BinderHelper.isDefault( cond ) ) {
cond = mappings.getFilterDefinition( name ).getDefaultFilterCondition();
if ( StringHelper.isEmpty( cond ) ) {
throw new AnnotationException(
"no filter condition found for filter " + name + " in "
+ StringHelper.qualify( propertyHolder.getPath(), propertyName )
);
}
}
return cond;
}
public void setCache(Cache cacheAnn) {
if ( cacheAnn != null ) {
cacheRegionName = BinderHelper.isDefault( cacheAnn.region() ) ? null : cacheAnn.region();
cacheConcurrencyStrategy = EntityBinder.getCacheConcurrencyStrategy( cacheAnn.usage() );
}
else {
cacheConcurrencyStrategy = null;
cacheRegionName = null;
}
}
public void setOneToMany(boolean oneToMany) {
this.oneToMany = oneToMany;
}
public void setIndexColumn(IndexColumn indexColumn) {
this.indexColumn = indexColumn;
}
public void setMapKey(MapKey key) {
if ( key != null ) {
mapKeyPropertyName = key.name();
}
}
private static String buildOrderByClauseFromHql(String hqlOrderBy, PersistentClass associatedClass, String role) {
String orderByString = null;
if ( hqlOrderBy != null ) {
List properties = new ArrayList();
List ordering = new ArrayList();
StringBuilder orderByBuffer = new StringBuilder();
if ( hqlOrderBy.length() == 0 ) {
//order by id
Iterator it = associatedClass.getIdentifier().getColumnIterator();
while ( it.hasNext() ) {
Selectable col = (Selectable) it.next();
orderByBuffer.append( col.getText() ).append( " asc" ).append( ", " );
}
}
else {
StringTokenizer st = new StringTokenizer( hqlOrderBy, " ,", false );
String currentOrdering = null;
//FIXME make this code decent
while ( st.hasMoreTokens() ) {
String token = st.nextToken();
if ( isNonPropertyToken( token ) ) {
if ( currentOrdering != null ) {
throw new AnnotationException(
"Error while parsing HQL orderBy clause: " + hqlOrderBy
+ " (" + role + ")"
);
}
currentOrdering = token;
}
else {
//Add ordering of the previous
if ( currentOrdering == null ) {
//default ordering
ordering.add( "asc" );
}
else {
ordering.add( currentOrdering );
currentOrdering = null;
}
properties.add( token );
}
}
ordering.remove( 0 ); //first one is the algorithm starter
// add last one ordering
if ( currentOrdering == null ) {
//default ordering
ordering.add( "asc" );
}
else {
ordering.add( currentOrdering );
currentOrdering = null;
}
int index = 0;
for ( String property : properties ) {
Property p = BinderHelper.findPropertyByName( associatedClass, property );
if ( p == null ) {
throw new AnnotationException(
"property from @OrderBy clause not found: "
+ associatedClass.getEntityName() + "." + property
);
}
Iterator propertyColumns = p.getColumnIterator();
while ( propertyColumns.hasNext() ) {
Selectable column = (Selectable) propertyColumns.next();
orderByBuffer.append( column.getText() )
.append( " " )
.append( ordering.get( index ) )
.append( ", " );
}
index++;
}
}
orderByString = orderByBuffer.substring( 0, orderByBuffer.length() - 2 );
}
return orderByString;
}
private static String buildOrderByClauseFromHql(String hqlOrderBy, Component component, String role) {
String orderByString = null;
if ( hqlOrderBy != null ) {
List properties = new ArrayList();
List ordering = new ArrayList();
StringBuilder orderByBuffer = new StringBuilder();
if ( hqlOrderBy.length() == 0 ) {
//TODO : Check that. Maybe order by key for maps
}
else {
StringTokenizer st = new StringTokenizer( hqlOrderBy, " ,", false );
String currentOrdering = null;
//FIXME make this code decent
while ( st.hasMoreTokens() ) {
String token = st.nextToken();
if ( isNonPropertyToken( token ) ) {
if ( currentOrdering != null ) {
throw new AnnotationException(
"Error while parsing HQL orderBy clause: " + hqlOrderBy
+ " (" + role + ")"
);
}
currentOrdering = token;
}
else {
//Add ordering of the previous
if ( currentOrdering == null ) {
//default ordering
ordering.add( "asc" );
}
else {
ordering.add( currentOrdering );
currentOrdering = null;
}
properties.add( token );
}
}
ordering.remove( 0 ); //first one is the algorithm starter
// add last one ordering
if ( currentOrdering == null ) {
//default ordering
ordering.add( "asc" );
}
else {
ordering.add( currentOrdering );
currentOrdering = null;
}
int index = 0;
for ( String property : properties ) {
Property p = component.getProperty( property );
if ( p == null ) {
throw new AnnotationException(
"property from @OrderBy clause not found: "
+ role + "." + property
);
}
Iterator propertyColumns = p.getColumnIterator();
while ( propertyColumns.hasNext() ) {
Selectable column = (Selectable) propertyColumns.next();
orderByBuffer.append( column.getText() )
.append( " " )
.append( ordering.get( index ) )
.append( ", " );
}
index++;
}
if ( orderByBuffer.length() >= 2 ) {
orderByString = orderByBuffer.substring( 0, orderByBuffer.length() - 2 );
}
}
}
return orderByString;
}
private static boolean isNonPropertyToken(String token) {
if ( " ".equals( token ) ) return true;
if ( ",".equals( token ) ) return true;
if ( token.equalsIgnoreCase( "desc" ) ) return true;
if ( token.equalsIgnoreCase( "asc" ) ) return true;
return false;
}
private static SimpleValue buildCollectionKey(
Collection collValue, Ejb3JoinColumn[] joinColumns, boolean cascadeDeleteEnabled,
XProperty property, ExtendedMappings mappings
) {
//binding key reference using column
KeyValue keyVal;
//give a chance to override the referenced property name
//has to do that here because the referencedProperty creation happens in a FKSecondPass for Many to one yuk!
if ( joinColumns.length > 0 && StringHelper.isNotEmpty( joinColumns[0].getMappedBy() ) ) {
String entityName = joinColumns[0].getManyToManyOwnerSideEntityName() != null ?
"inverse__" + joinColumns[0].getManyToManyOwnerSideEntityName() :
joinColumns[0].getPropertyHolder().getEntityName();
String propRef = mappings.getPropertyReferencedAssociation(
entityName,
joinColumns[0].getMappedBy()
);
if ( propRef != null ) {
collValue.setReferencedPropertyName( propRef );
mappings.addPropertyReference( collValue.getOwnerEntityName(), propRef );
}
}
String propRef = collValue.getReferencedPropertyName();
if ( propRef == null ) {
keyVal = collValue.getOwner().getIdentifier();
}
else {
keyVal = (KeyValue) collValue.getOwner()
.getRecursiveProperty( propRef )
.getValue();
}
DependantValue key = new DependantValue( collValue.getCollectionTable(), keyVal );
key.setTypeName( null );
Ejb3Column.checkPropertyConsistency( joinColumns, collValue.getOwnerEntityName() );
key.setNullable( joinColumns.length == 0 || joinColumns[0].isNullable() );
key.setUpdateable( joinColumns.length == 0 || joinColumns[0].isUpdatable() );
key.setCascadeDeleteEnabled( cascadeDeleteEnabled );
collValue.setKey( key );
ForeignKey fk = property != null ? property.getAnnotation( ForeignKey.class ) : null;
String fkName = fk != null ? fk.name() : "";
if ( ! BinderHelper.isDefault( fkName ) ) key.setForeignKeyName( fkName );
return key;
}
protected void bindManyToManySecondPass(
Collection collValue,
Map persistentClasses,
Ejb3JoinColumn[] joinColumns,
Ejb3JoinColumn[] inverseJoinColumns,
Ejb3Column[] elementColumns,
boolean isEmbedded,
String collType,
boolean ignoreNotFound, boolean unique,
boolean cascadeDeleteEnabled,
TableBinder associationTableBinder, XProperty property, PropertyHolder parentPropertyHolder,
String hqlOrderBy, ExtendedMappings mappings
) throws MappingException {
PersistentClass collectionEntity = (PersistentClass) persistentClasses.get( collType );
boolean isCollectionOfEntities = collectionEntity != null;
if ( log.isDebugEnabled() ) {
String path = collValue.getOwnerEntityName() + "." + joinColumns[0].getPropertyName();
if ( isCollectionOfEntities && unique ) {
log.debug( "Binding a OneToMany: " + path + " through an association table" );
}
else if ( isCollectionOfEntities ) {
log.debug( "Binding as ManyToMany: " + path );
}
else {
log.debug( "Binding a collection of element: " + path );
}
}
//check for user error
if ( ! isCollectionOfEntities ) {
if ( property.isAnnotationPresent( ManyToMany.class ) || property.isAnnotationPresent( OneToMany.class ) ) {
String path = collValue.getOwnerEntityName() + "." + joinColumns[0].getPropertyName();
throw new AnnotationException(
"Use of @OneToMany or @ManyToMany targeting an unmapped class: " + path + "[" + collType + "]"
);
}
else {
JoinTable joinTableAnn = property.getAnnotation( JoinTable.class );
if ( joinTableAnn != null && joinTableAnn.inverseJoinColumns().length > 0 ) {
String path = collValue.getOwnerEntityName() + "." + joinColumns[0].getPropertyName();
throw new AnnotationException(
"Use of @JoinTable.inverseJoinColumns targeting an unmapped class: " + path + "[" + collType + "]"
);
}
}
}
boolean mappedBy = ! BinderHelper.isDefault( joinColumns[0].getMappedBy() );
if ( mappedBy ) {
if ( ! isCollectionOfEntities ) {
StringBuilder error = new StringBuilder( 80 )
.append(
"Collection of elements must not have mappedBy or association reference an unmapped entity: "
)
.append( collValue.getOwnerEntityName() )
.append( "." )
.append( joinColumns[0].getPropertyName() );
throw new AnnotationException( error.toString() );
}
Property otherSideProperty;
try {
otherSideProperty = collectionEntity.getRecursiveProperty( joinColumns[0].getMappedBy() );
}
catch (MappingException e) {
StringBuilder error = new StringBuilder( 80 );
error.append( "mappedBy reference an unknown target entity property: " )
.append( collType ).append( "." ).append( joinColumns[0].getMappedBy() )
.append( " in " )
.append( collValue.getOwnerEntityName() )
.append( "." )
.append( joinColumns[0].getPropertyName() );
throw new AnnotationException( error.toString() );
}
Table table;
if ( otherSideProperty.getValue() instanceof Collection ) {
//this is a collection on the other side
table = ( (Collection) otherSideProperty.getValue() ).getCollectionTable();
}
else {
//This is a ToOne with a @JoinTable or a regular property
table = otherSideProperty.getValue().getTable();
}
collValue.setCollectionTable( table );
String entityName = collectionEntity.getEntityName();
for ( Ejb3JoinColumn column : joinColumns ) {
//column.setDefaultColumnHeader( joinColumns[0].getMappedBy() ); //seems not to be used, make sense
column.setManyToManyOwnerSideEntityName( entityName );
}
}
else {
//TODO: only for implicit columns?
//FIXME NamingStrategy
for ( Ejb3JoinColumn column : joinColumns ) {
String mappedByProperty = mappings.getFromMappedBy(
collValue.getOwnerEntityName(), column.getPropertyName()
);
Table ownerTable = collValue.getOwner().getTable();
column.setMappedBy(
collValue.getOwner().getEntityName(), mappings.getLogicalTableName( ownerTable ),
mappedByProperty
);
// String header = ( mappedByProperty == null ) ? mappings.getLogicalTableName( ownerTable ) : mappedByProperty;
// column.setDefaultColumnHeader( header );
}
if ( StringHelper.isEmpty( associationTableBinder.getName() ) ) {
//default value
associationTableBinder.setDefaultName(
collValue.getOwner().getEntityName(),
mappings.getLogicalTableName( collValue.getOwner().getTable() ),
collectionEntity != null ? collectionEntity.getEntityName() : null,
collectionEntity != null ? mappings.getLogicalTableName( collectionEntity.getTable() ) : null,
joinColumns[0].getPropertyName()
);
}
collValue.setCollectionTable( associationTableBinder.bind() );
}
bindFilters( isCollectionOfEntities );
bindCollectionSecondPass( collValue, collectionEntity, joinColumns, cascadeDeleteEnabled, property, mappings );
ManyToOne element = null;
if ( isCollectionOfEntities ) {
element =
new ManyToOne( collValue.getCollectionTable() );
collValue.setElement( element );
element.setReferencedEntityName( collType );
//element.setFetchMode( fetchMode );
//element.setLazy( fetchMode != FetchMode.JOIN );
//make the second join non lazy
element.setFetchMode( FetchMode.JOIN );
element.setLazy( false );
element.setIgnoreNotFound( ignoreNotFound );
if ( StringHelper.isNotEmpty( hqlOrderBy ) ) {
collValue.setManyToManyOrdering(
buildOrderByClauseFromHql( hqlOrderBy, collectionEntity, collValue.getRole() )
);
}
ForeignKey fk = property != null ? property.getAnnotation( ForeignKey.class ) : null;
String fkName = fk != null ? fk.inverseName() : "";
if ( ! BinderHelper.isDefault( fkName ) ) element.setForeignKeyName( fkName );
}
else {
XClass elementClass;
AnnotatedClassType classType;
// Map columnOverrides = PropertyHolderBuilder.buildColumnOverride(
// property, StringHelper.qualify( collValue.getRole(), "element" )
// );
//FIXME the "element" is lost
PropertyHolder holder = null;
if ( BinderHelper.PRIMITIVE_NAMES.contains( collType ) ) {
classType = AnnotatedClassType.NONE;
elementClass = null;
}
else {
try {
elementClass = mappings.getReflectionManager().classForName( collType, CollectionBinder.class );
}
catch (ClassNotFoundException e) {
throw new AnnotationException( "Unable to find class: " + collType, e );
}
classType = mappings.getClassType( elementClass );
holder = PropertyHolderBuilder.buildPropertyHolder(
collValue,
collValue.getRole(), // + ".element",
elementClass,
property, parentPropertyHolder, mappings
);
//force in case of attribute override
boolean attributeOverride = property.isAnnotationPresent( AttributeOverride.class )
|| property.isAnnotationPresent( AttributeOverrides.class );
if ( isEmbedded || attributeOverride ) {
classType = AnnotatedClassType.EMBEDDABLE;
}
}
if ( AnnotatedClassType.EMBEDDABLE.equals( classType ) ) {
EntityBinder entityBinder = new EntityBinder();
PersistentClass owner = collValue.getOwner();
boolean isPropertyAnnotated;
//FIXME support @Access for collection of elements
//String accessType = access != null ? access.value() : null;
if ( owner.getIdentifierProperty() != null ) {
isPropertyAnnotated = owner.getIdentifierProperty().getPropertyAccessorName().equals( "property" );
}
else if ( owner.getIdentifierMapper() != null && owner.getIdentifierMapper().getPropertySpan() > 0 ) {
Property prop = (Property) owner.getIdentifierMapper().getPropertyIterator().next();
isPropertyAnnotated = prop.getPropertyAccessorName().equals( "property" );
}
else {
throw new AssertionFailure( "Unable to guess collection property accessor name" );
}
//boolean propertyAccess = embeddable == null || AccessType.PROPERTY.equals( embeddable.access() );
PropertyData inferredData = new PropertyPreloadedData( "property", "element", elementClass );
//TODO be smart with isNullable
Component component = AnnotationBinder.fillComponent(
holder, inferredData, isPropertyAnnotated, isPropertyAnnotated ? "property" : "field", true,
entityBinder, false, false,
true, mappings
);
collValue.setElement( component );
if ( StringHelper.isNotEmpty( hqlOrderBy ) ) {
String path = collValue.getOwnerEntityName() + "." + joinColumns[0].getPropertyName();
String orderBy = buildOrderByClauseFromHql( hqlOrderBy, component, path );
if ( orderBy != null ) {
collValue.setOrderBy( orderBy );
}
}
}
else {
SimpleValueBinder elementBinder = new SimpleValueBinder();
elementBinder.setMappings( mappings );
elementBinder.setReturnedClassName( collType );
if ( elementColumns == null || elementColumns.length == 0 ) {
elementColumns = new Ejb3Column[1];
Ejb3Column column = new Ejb3Column();
column.setImplicit( false );
//not following the spec but more clean
column.setNullable( true );
column.setLength( Ejb3Column.DEFAULT_COLUMN_LENGTH );
column.setLogicalColumnName( Collection.DEFAULT_ELEMENT_COLUMN_NAME );
//TODO create an EMPTY_JOINS collection
column.setJoins( new HashMap() );
column.setMappings( mappings );
column.bind();
elementColumns[0] = column;
}
//override the table
for ( Ejb3Column column : elementColumns ) {
column.setTable( collValue.getCollectionTable() );
}
elementBinder.setColumns( elementColumns );
elementBinder.setType( property, elementClass );
collValue.setElement( elementBinder.make() );
}
}
checkFilterConditions( collValue );
//FIXME: do optional = false
if ( isCollectionOfEntities ) {
bindManytoManyInverseFk( collectionEntity, inverseJoinColumns, element, unique, mappings );
}
}
private static void checkFilterConditions(Collection collValue) {
//for now it can't happen, but sometime soon...
if ( ( collValue.getFilterMap().size() != 0 || StringHelper.isNotEmpty( collValue.getWhere() ) ) &&
collValue.getFetchMode() == FetchMode.JOIN &&
collValue.getElement().getFetchMode() != FetchMode.JOIN ) {
throw new MappingException(
"@ManyToMany or @CollectionOfElements defining filter or where without join fetching "
+ "not valid within collection using join fetching[" + collValue.getRole() + "]"
);
}
}
private static void bindCollectionSecondPass(
Collection collValue, PersistentClass collectionEntity, Ejb3JoinColumn[] joinColumns,
boolean cascadeDeleteEnabled, XProperty property,
ExtendedMappings mappings
) {
BinderHelper.createSyntheticPropertyReference(
joinColumns, collValue.getOwner(), collectionEntity, collValue, false, mappings
);
SimpleValue key = buildCollectionKey( collValue, joinColumns, cascadeDeleteEnabled, property, mappings );
TableBinder.bindFk( collValue.getOwner(), collectionEntity, joinColumns, key, false, mappings );
}
public void setCascadeDeleteEnabled(boolean onDeleteCascade) {
this.cascadeDeleteEnabled = onDeleteCascade;
}
private String safeCollectionRole() {
if ( propertyHolder != null ) {
return propertyHolder.getEntityName() + "." + propertyName;
}
else {
return "";
}
}
/**
* bind the inverse FK of a ManyToMany
* If we are in a mappedBy case, read the columns from the associated
* colletion element
* Otherwise delegates to the usual algorithm
*/
public static void bindManytoManyInverseFk(
PersistentClass referencedEntity, Ejb3JoinColumn[] columns, SimpleValue value, boolean unique,
ExtendedMappings mappings
) {
final String mappedBy = columns[0].getMappedBy();
if ( StringHelper.isNotEmpty( mappedBy ) ) {
final Property property = referencedEntity.getRecursiveProperty( mappedBy );
Iterator mappedByColumns;
if ( property.getValue() instanceof Collection ) {
mappedByColumns = ( (Collection) property.getValue() ).getKey().getColumnIterator();
}
else {
//find the appropriate reference key, can be in a join
Iterator joinsIt = referencedEntity.getJoinIterator();
KeyValue key = null;
while ( joinsIt.hasNext() ) {
Join join = (Join) joinsIt.next();
if ( join.containsProperty( property ) ) {
key = join.getKey();
break;
}
}
if ( key == null ) key = property.getPersistentClass().getIdentifier();
mappedByColumns = key.getColumnIterator();
}
while ( mappedByColumns.hasNext() ) {
Column column = (Column) mappedByColumns.next();
columns[0].linkValueUsingAColumnCopy( column, value );
}
String referencedPropertyName =
mappings.getPropertyReferencedAssociation(
"inverse__" + referencedEntity.getEntityName(), mappedBy
);
if ( referencedPropertyName != null ) {
//TODO always a many to one?
( (ManyToOne) value ).setReferencedPropertyName( referencedPropertyName );
mappings.addUniquePropertyReference( referencedEntity.getEntityName(), referencedPropertyName );
}
value.createForeignKey();
}
else {
BinderHelper.createSyntheticPropertyReference( columns, referencedEntity, null, value, true, mappings );
TableBinder.bindFk( referencedEntity, null, columns, value, unique, mappings );
}
}
public void setFkJoinColumns(Ejb3JoinColumn[] ejb3JoinColumns) {
this.fkJoinColumns = ejb3JoinColumns;
}
public void setExplicitAssociationTable(boolean explicitAssocTable) {
this.isExplicitAssociationTable = explicitAssocTable;
}
public void setElementColumns(Ejb3Column[] elementColumns) {
this.elementColumns = elementColumns;
}
public void setEmbedded(boolean annotationPresent) {
this.isEmbedded = annotationPresent;
}
public void setProperty(XProperty property) {
this.property = property;
}
public void setIgnoreNotFound(boolean ignoreNotFound) {
this.ignoreNotFound = ignoreNotFound;
}
public void setMapKeyColumns(Ejb3Column[] mapKeyColumns) {
this.mapKeyColumns = mapKeyColumns;
}
public void setMapKeyManyToManyColumns(Ejb3JoinColumn[] mapJoinColumns) {
this.mapKeyManyToManyColumns = mapJoinColumns;
}
public void setLocalGenerators(HashMap localGenerators) {
this.localGenerators = localGenerators;
}
}