Save This Page
Home » hibernate-distribution-3.3.1.GA-dist » org.hibernate » cfg » annotations » [javadoc | source]
    1   package org.hibernate.cfg.annotations;
    2   
    3   import java.util.ArrayList;
    4   import java.util.Comparator;
    5   import java.util.HashMap;
    6   import java.util.Iterator;
    7   import java.util.List;
    8   import java.util.Map;
    9   import java.util.StringTokenizer;
   10   import javax.persistence.AttributeOverride;
   11   import javax.persistence.AttributeOverrides;
   12   import javax.persistence.Embeddable;
   13   import javax.persistence.FetchType;
   14   import javax.persistence.JoinTable;
   15   import javax.persistence.ManyToMany;
   16   import javax.persistence.MapKey;
   17   import javax.persistence.OneToMany;
   18   
   19   import org.apache.commons.logging.Log;
   20   import org.apache.commons.logging.LogFactory;
   21   import org.hibernate.AnnotationException;
   22   import org.hibernate.AssertionFailure;
   23   import org.hibernate.FetchMode;
   24   import org.hibernate.MappingException;
   25   import org.hibernate.engine.ExecuteUpdateResultCheckStyle;
   26   import org.hibernate.annotations.BatchSize;
   27   import org.hibernate.annotations.Cache;
   28   import org.hibernate.annotations.CollectionId;
   29   import org.hibernate.annotations.CollectionOfElements;
   30   import org.hibernate.annotations.Fetch;
   31   import org.hibernate.annotations.Filter;
   32   import org.hibernate.annotations.FilterJoinTable;
   33   import org.hibernate.annotations.FilterJoinTables;
   34   import org.hibernate.annotations.Filters;
   35   import org.hibernate.annotations.ForeignKey;
   36   import org.hibernate.annotations.LazyCollection;
   37   import org.hibernate.annotations.LazyCollectionOption;
   38   import org.hibernate.annotations.OrderBy;
   39   import org.hibernate.annotations.Sort;
   40   import org.hibernate.annotations.SortType;
   41   import org.hibernate.annotations.Where;
   42   import org.hibernate.annotations.WhereJoinTable;
   43   import org.hibernate.annotations.SQLInsert;
   44   import org.hibernate.annotations.SQLUpdate;
   45   import org.hibernate.annotations.SQLDelete;
   46   import org.hibernate.annotations.SQLDeleteAll;
   47   import org.hibernate.annotations.Loader;
   48   import org.hibernate.annotations.Immutable;
   49   import org.hibernate.annotations.OptimisticLock;
   50   import org.hibernate.annotations.Persister;
   51   import org.hibernate.cfg.AnnotatedClassType;
   52   import org.hibernate.cfg.AnnotationBinder;
   53   import org.hibernate.cfg.BinderHelper;
   54   import org.hibernate.cfg.CollectionSecondPass;
   55   import org.hibernate.cfg.Ejb3Column;
   56   import org.hibernate.cfg.Ejb3JoinColumn;
   57   import org.hibernate.cfg.ExtendedMappings;
   58   import org.hibernate.cfg.IndexColumn;
   59   import org.hibernate.cfg.PropertyData;
   60   import org.hibernate.cfg.PropertyHolder;
   61   import org.hibernate.cfg.PropertyHolderBuilder;
   62   import org.hibernate.cfg.PropertyPreloadedData;
   63   import org.hibernate.cfg.SecondPass;
   64   import org.hibernate.mapping.Backref;
   65   import org.hibernate.mapping.Collection;
   66   import org.hibernate.mapping.Column;
   67   import org.hibernate.mapping.Component;
   68   import org.hibernate.mapping.DependantValue;
   69   import org.hibernate.mapping.IdGenerator;
   70   import org.hibernate.mapping.Join;
   71   import org.hibernate.mapping.KeyValue;
   72   import org.hibernate.mapping.ManyToOne;
   73   import org.hibernate.mapping.PersistentClass;
   74   import org.hibernate.mapping.Property;
   75   import org.hibernate.mapping.Selectable;
   76   import org.hibernate.mapping.SimpleValue;
   77   import org.hibernate.mapping.Table;
   78   import org.hibernate.annotations.common.reflection.XClass;
   79   import org.hibernate.annotations.common.reflection.XProperty;
   80   import org.hibernate.util.StringHelper;
   81   
   82   /**
   83    * Collection binder
   84    *
   85    * @author inger
   86    * @author Emmanuel Bernard
   87    */
   88   public abstract class CollectionBinder {
   89   
   90   	private static final Log log = LogFactory.getLog( CollectionBinder.class );
   91   
   92   	protected Collection collection;
   93   	protected String propertyName;
   94   	PropertyHolder propertyHolder;
   95   	int batchSize;
   96   	private String mappedBy;
   97   	private XClass collectionType;
   98   	private XClass targetEntity;
   99   	private ExtendedMappings mappings;
  100   	private Ejb3JoinColumn[] inverseJoinColumns;
  101   	private String cascadeStrategy;
  102   	String cacheConcurrencyStrategy;
  103   	String cacheRegionName;
  104   	private boolean oneToMany;
  105   	protected IndexColumn indexColumn;
  106   	private String orderBy;
  107   	protected String hqlOrderBy;
  108   	private boolean isSorted;
  109   	private Class comparator;
  110   	private boolean hasToBeSorted;
  111   	protected boolean cascadeDeleteEnabled;
  112   	protected String mapKeyPropertyName;
  113   	private boolean insertable = true;
  114   	private boolean updatable = true;
  115   	private Ejb3JoinColumn[] fkJoinColumns;
  116   	private boolean isExplicitAssociationTable;
  117   	private Ejb3Column[] elementColumns;
  118   	private boolean isEmbedded;
  119   	private XProperty property;
  120   	private boolean ignoreNotFound;
  121   	private TableBinder tableBinder;
  122   	private Ejb3Column[] mapKeyColumns;
  123   	private Ejb3JoinColumn[] mapKeyManyToManyColumns;
  124   	protected HashMap<String, IdGenerator> localGenerators;
  125   
  126   	public void setUpdatable(boolean updatable) {
  127   		this.updatable = updatable;
  128   	}
  129   
  130   	public void setInsertable(boolean insertable) {
  131   		this.insertable = insertable;
  132   	}
  133   
  134   
  135   	public void setCascadeStrategy(String cascadeStrategy) {
  136   		this.cascadeStrategy = cascadeStrategy;
  137   	}
  138   
  139   	public void setPropertyAccessorName(String propertyAccessorName) {
  140   		this.propertyAccessorName = propertyAccessorName;
  141   	}
  142   
  143   	private String propertyAccessorName;
  144   
  145   	public void setInverseJoinColumns(Ejb3JoinColumn[] inverseJoinColumns) {
  146   		this.inverseJoinColumns = inverseJoinColumns;
  147   	}
  148   
  149   	public void setJoinColumns(Ejb3JoinColumn[] joinColumns) {
  150   		this.joinColumns = joinColumns;
  151   	}
  152   
  153   	private Ejb3JoinColumn[] joinColumns;
  154   
  155   	public void setPropertyHolder(PropertyHolder propertyHolder) {
  156   		this.propertyHolder = propertyHolder;
  157   	}
  158   
  159   	public void setBatchSize(BatchSize batchSize) {
  160   		this.batchSize = batchSize == null ? -1 : batchSize.size();
  161   	}
  162   
  163   	public void setEjb3OrderBy(javax.persistence.OrderBy orderByAnn) {
  164   		if ( orderByAnn != null ) {
  165   			hqlOrderBy = orderByAnn.value();
  166   		}
  167   	}
  168   
  169   	public void setSqlOrderBy(OrderBy orderByAnn) {
  170   		if ( orderByAnn != null ) {
  171   			if ( ! BinderHelper.isDefault( orderByAnn.clause() ) ) orderBy = orderByAnn.clause();
  172   		}
  173   	}
  174   
  175   	public void setSort(Sort sortAnn) {
  176   		if ( sortAnn != null ) {
  177   			isSorted = ! SortType.UNSORTED.equals( sortAnn.type() );
  178   			if ( isSorted && SortType.COMPARATOR.equals( sortAnn.type() ) ) {
  179   				comparator = sortAnn.comparator();
  180   			}
  181   		}
  182   	}
  183   
  184   	/**
  185   	 * collection binder factory
  186   	 */
  187   	public static CollectionBinder getCollectionBinder(
  188   			String entityName, XProperty property,
  189   			boolean isIndexed
  190   	) {
  191   		if ( property.isArray() ) {
  192   			if ( property.getElementClass().isPrimitive() ) {
  193   				return new PrimitiveArrayBinder();
  194   			}
  195   			else {
  196   				return new ArrayBinder();
  197   			}
  198   		}
  199   		else if ( property.isCollection() ) {
  200   			//TODO consider using an XClass
  201   			Class returnedClass = property.getCollectionClass();
  202   			if ( java.util.Set.class.equals( returnedClass ) ) {
  203   				return new SetBinder();
  204   			}
  205   			else if ( java.util.SortedSet.class.equals( returnedClass ) ) {
  206   				return new SetBinder( true );
  207   			}
  208   			else if ( java.util.Map.class.equals( returnedClass ) ) {
  209   				return new MapBinder();
  210   			}
  211   			else if ( java.util.SortedMap.class.equals( returnedClass ) ) {
  212   				return new MapBinder(true);
  213   			}
  214   			else if ( java.util.Collection.class.equals( returnedClass ) ) {
  215   				if ( property.isAnnotationPresent( CollectionId.class ) ) {
  216   					return new IdBagBinder();
  217   				}
  218   				else {
  219   					return new BagBinder();
  220   				}
  221   			}
  222   			else if ( java.util.List.class.equals( returnedClass ) ) {
  223   				if ( isIndexed ) {
  224   					return new ListBinder();
  225   				}
  226   				else if ( property.isAnnotationPresent( CollectionId.class ) ) {
  227   					return new IdBagBinder();
  228   				}
  229   				else {
  230   					return new BagBinder();
  231   				}
  232   			}
  233   			else {
  234   				throw new AnnotationException(
  235   						returnedClass.getName() + " collection not yet supported: "
  236   								+ StringHelper.qualify( entityName, property.getName() )
  237   				);
  238   			}
  239   		}
  240   		else {
  241   			throw new AnnotationException(
  242   					"Illegal attempt to map a non collection as a @OneToMany, @ManyToMany or @CollectionOfElements: "
  243   							+ StringHelper.qualify( entityName, property.getName() )
  244   			);
  245   		}
  246   	}
  247   
  248   	protected CollectionBinder() {
  249   	}
  250   
  251   	protected CollectionBinder(boolean sorted) {
  252   		this.hasToBeSorted = sorted;
  253   	}
  254   
  255   	public void setMappedBy(String mappedBy) {
  256   		this.mappedBy = mappedBy;
  257   	}
  258   
  259   	public void setTableBinder(TableBinder tableBinder) {
  260   		this.tableBinder = tableBinder;
  261   	}
  262   
  263   	public void setCollectionType(XClass collectionType) {
  264   		this.collectionType = collectionType;
  265   	}
  266   
  267   	public void setTargetEntity(XClass targetEntity) {
  268   		this.targetEntity = targetEntity;
  269   	}
  270   
  271   	public void setMappings(ExtendedMappings mappings) {
  272   		this.mappings = mappings;
  273   	}
  274   
  275   	protected abstract Collection createCollection(PersistentClass persistentClass);
  276   
  277   	public Collection getCollection() {
  278   		return collection;
  279   	}
  280   
  281   	public void setPropertyName(String propertyName) {
  282   		this.propertyName = propertyName;
  283   	}
  284   
  285   	public void bind() {
  286   		this.collection = createCollection( propertyHolder.getPersistentClass() );
  287   		log.debug( "Collection role: " + StringHelper.qualify( propertyHolder.getPath(), propertyName ) );
  288   		collection.setRole( StringHelper.qualify( propertyHolder.getPath(), propertyName ) );
  289   		collection.setNodeName( propertyName );
  290   
  291   		if ( property.isAnnotationPresent( org.hibernate.annotations.MapKey.class ) && mapKeyPropertyName != null ) {
  292   			throw new AnnotationException(
  293   					"Cannot mix @javax.persistence.MapKey and @org.hibernate.annotations.MapKey "
  294   							+ "on the same collection: " + StringHelper.qualify(
  295   							propertyHolder.getPath(), propertyName
  296   					)
  297   			);
  298   		}
  299   
  300   		//set laziness
  301   		defineFetchingStrategy();
  302   		//collection.setFetchMode( fetchMode );
  303   		//collection.setLazy( fetchMode == FetchMode.SELECT );
  304   		collection.setBatchSize( batchSize );
  305   		if ( orderBy != null && hqlOrderBy != null ) {
  306   			throw new AnnotationException(
  307   					"Cannot use sql order by clause in conjunction of EJB3 order by clause: " + safeCollectionRole()
  308   			);
  309   		}
  310   
  311   		collection.setMutable( ! property.isAnnotationPresent( Immutable.class ) );
  312   		OptimisticLock lockAnn = property.getAnnotation( OptimisticLock.class );
  313   		if (lockAnn != null) collection.setOptimisticLocked( ! lockAnn.excluded() );
  314   		Persister persisterAnn = property.getAnnotation( Persister.class );
  315   		if ( persisterAnn != null ) collection.setCollectionPersisterClass( persisterAnn.impl() );		
  316   
  317   		// set ordering
  318   		if ( orderBy != null ) collection.setOrderBy( orderBy );
  319   		if ( isSorted ) {
  320   			collection.setSorted( true );
  321   			if ( comparator != null ) {
  322   				try {
  323   					collection.setComparator( (Comparator) comparator.newInstance() );
  324   				}
  325   				catch (ClassCastException e) {
  326   					throw new AnnotationException(
  327   							"Comparator not implementing java.util.Comparator class: "
  328   									+ comparator.getName() + "(" + safeCollectionRole() + ")"
  329   					);
  330   				}
  331   				catch (Exception e) {
  332   					throw new AnnotationException(
  333   							"Could not instantiate comparator class: "
  334   									+ comparator.getName() + "(" + safeCollectionRole() + ")"
  335   					);
  336   				}
  337   			}
  338   		}
  339   		else {
  340   			if ( hasToBeSorted ) {
  341   				throw new AnnotationException(
  342   						"A sorted collection has to define @Sort: "
  343   								+ safeCollectionRole()
  344   				);
  345   			}
  346   		}
  347   
  348   		//set cache
  349   		if ( StringHelper.isNotEmpty( cacheConcurrencyStrategy ) ) {
  350   			collection.setCacheConcurrencyStrategy( cacheConcurrencyStrategy );
  351   			collection.setCacheRegionName( cacheRegionName );
  352   		}
  353   
  354   		//SQL overriding
  355   		SQLInsert sqlInsert = property.getAnnotation( SQLInsert.class );
  356   		SQLUpdate sqlUpdate = property.getAnnotation( SQLUpdate.class );
  357   		SQLDelete sqlDelete = property.getAnnotation( SQLDelete.class );
  358   		SQLDeleteAll sqlDeleteAll = property.getAnnotation( SQLDeleteAll.class );
  359   		Loader loader = property.getAnnotation( Loader.class );
  360   		if ( sqlInsert != null ) {
  361   			collection.setCustomSQLInsert( sqlInsert.sql().trim(), sqlInsert.callable(),
  362   					ExecuteUpdateResultCheckStyle.parse( sqlInsert.check().toString().toLowerCase() )
  363   			);
  364   
  365   		}
  366   		if ( sqlUpdate != null ) {
  367   			collection.setCustomSQLUpdate( sqlUpdate.sql(), sqlUpdate.callable(),
  368   					ExecuteUpdateResultCheckStyle.parse( sqlUpdate.check().toString().toLowerCase() )
  369   			);
  370   		}
  371   		if ( sqlDelete != null ) {
  372   			collection.setCustomSQLDelete( sqlDelete.sql(), sqlDelete.callable(),
  373   					ExecuteUpdateResultCheckStyle.parse( sqlDelete.check().toString().toLowerCase() )
  374   			);
  375   		}
  376   		if ( sqlDeleteAll != null ) {
  377   			collection.setCustomSQLDeleteAll( sqlDeleteAll.sql(), sqlDeleteAll.callable(),
  378   					ExecuteUpdateResultCheckStyle.parse( sqlDeleteAll.check().toString().toLowerCase() )
  379   			);
  380   		}
  381   		if ( loader != null ) {
  382   			collection.setLoaderName( loader.namedQuery() );
  383   		}
  384   
  385   		//work on association
  386   		boolean isMappedBy = ! BinderHelper.isDefault( mappedBy );
  387   		collection.setInverse( isMappedBy );
  388   		
  389   		//many to many may need some second pass informations
  390   		if ( ! oneToMany && isMappedBy ) {
  391   			mappings.addMappedBy( getCollectionType().getName(), mappedBy, propertyName );
  392   		}
  393   		//TODO reducce tableBinder != null and oneToMany
  394   		//FIXME collection of elements shouldn't be executed as a secondpass
  395   		SecondPass sp = getSecondPass(
  396   				fkJoinColumns,
  397   				joinColumns,
  398   				inverseJoinColumns,
  399   				elementColumns,
  400   				mapKeyColumns, mapKeyManyToManyColumns, isEmbedded,
  401   				property, getCollectionType(),
  402   				ignoreNotFound, oneToMany,
  403   				tableBinder, mappings
  404   		);
  405   		XClass collectionType = getCollectionType();
  406   		if ( collectionType.isAnnotationPresent( Embeddable.class )
  407   				|| property.isAnnotationPresent( CollectionOfElements.class ) ) {
  408   			// do it right away, otherwise @ManyToon on composite element call addSecondPass 
  409   			// and raise a ConcurrentModificationException
  410   			//sp.doSecondPass( CollectionHelper.EMPTY_MAP );
  411   			mappings.addSecondPass( sp, ! isMappedBy );
  412   		}
  413   		else {
  414   			mappings.addSecondPass( sp, ! isMappedBy );
  415   		}
  416   
  417   		mappings.addCollection( collection );
  418   
  419   		//property building
  420   		PropertyBinder binder = new PropertyBinder();
  421   		binder.setName( propertyName );
  422   		binder.setValue( collection );
  423   		binder.setCascade( cascadeStrategy );
  424   		if ( cascadeStrategy != null && cascadeStrategy.indexOf( "delete-orphan" ) >= 0 ) {
  425   			collection.setOrphanDelete( true );
  426   		}
  427   		binder.setPropertyAccessorName( propertyAccessorName );
  428   		binder.setProperty( property );
  429   		binder.setInsertable( insertable );
  430   		binder.setUpdatable( updatable );
  431   		Property prop = binder.make();
  432   		//we don't care about the join stuffs because the column is on the association table.
  433   		propertyHolder.addProperty( prop );
  434   	}
  435   
  436   	private void defineFetchingStrategy() {
  437   		LazyCollection lazy = property.getAnnotation( LazyCollection.class );
  438   		Fetch fetch = property.getAnnotation( Fetch.class );
  439   		OneToMany oneToMany = property.getAnnotation( OneToMany.class );
  440   		ManyToMany manyToMany = property.getAnnotation( ManyToMany.class );
  441   		CollectionOfElements elements = property.getAnnotation( CollectionOfElements.class );
  442   		FetchType fetchType;
  443   		if ( oneToMany != null ) {
  444   			fetchType = oneToMany.fetch();
  445   		}
  446   		else if ( manyToMany != null ) {
  447   			fetchType = manyToMany.fetch();
  448   		}
  449   		else if ( elements != null ) {
  450   			fetchType = elements.fetch();
  451   		}
  452   		else {
  453   			throw new AssertionFailure(
  454   					"Define fetch strategy on a property not annotated with @ManyToOne nor @OneToMany nor @CollectionOfElements"
  455   			);
  456   		}
  457   		if ( lazy != null ) {
  458   			collection.setLazy( ! ( lazy.value() == LazyCollectionOption.FALSE ) );
  459   			collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA );
  460   		}
  461   		else {
  462   			collection.setLazy( fetchType == FetchType.LAZY );
  463   			collection.setExtraLazy( false );
  464   		}
  465   		if ( fetch != null ) {
  466   			if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) {
  467   				collection.setFetchMode( FetchMode.JOIN );
  468   				collection.setLazy( false );
  469   			}
  470   			else if ( fetch.value() == org.hibernate.annotations.FetchMode.SELECT ) {
  471   				collection.setFetchMode( FetchMode.SELECT );
  472   			}
  473   			else if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) {
  474   				collection.setFetchMode( FetchMode.SELECT );
  475   				collection.setSubselectLoadable( true );
  476   				collection.getOwner().setSubselectLoadableCollections( true );
  477   			}
  478   			else {
  479   				throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() );
  480   			}
  481   		}
  482   		else {
  483   			collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) );
  484   		}
  485   	}
  486   
  487   	private XClass getCollectionType() {
  488   		if ( AnnotationBinder.isDefault( targetEntity, mappings ) ) {
  489   			if ( collectionType != null ) {
  490   				return collectionType;
  491   			}
  492   			else {
  493   				String errorMsg = "Collection has neither generic type or OneToMany.targetEntity() defined: "
  494   						+ safeCollectionRole();
  495   				throw new AnnotationException( errorMsg );
  496   			}
  497   		}
  498   		else {
  499   			return targetEntity;
  500   		}
  501   	}
  502   
  503   	public SecondPass getSecondPass(
  504   			final Ejb3JoinColumn[] fkJoinColumns, final Ejb3JoinColumn[] keyColumns,
  505   			final Ejb3JoinColumn[] inverseColumns,
  506   			final Ejb3Column[] elementColumns,
  507   			final Ejb3Column[] mapKeyColumns, final Ejb3JoinColumn[] mapKeyManyToManyColumns, final boolean isEmbedded,
  508   			final XProperty property, final XClass collType,
  509   			final boolean ignoreNotFound, final boolean unique,
  510   			final TableBinder assocTableBinder, final ExtendedMappings mappings
  511   	) {
  512   
  513   		return new CollectionSecondPass( mappings, collection ) {
  514   
  515   			public void secondPass(java.util.Map persistentClasses, java.util.Map inheritedMetas)
  516   					throws MappingException {
  517   				bindStarToManySecondPass(
  518   						persistentClasses, collType, fkJoinColumns, keyColumns, inverseColumns, elementColumns,
  519   						isEmbedded, property, unique, assocTableBinder, ignoreNotFound, mappings
  520   				);
  521   
  522   			}
  523   		};
  524   	}
  525   
  526   	/**
  527   	 * return true if it's a Fk, false if it's an association table
  528   	 */
  529   	protected boolean bindStarToManySecondPass(
  530   			Map persistentClasses, XClass collType, Ejb3JoinColumn[] fkJoinColumns,
  531   			Ejb3JoinColumn[] keyColumns, Ejb3JoinColumn[] inverseColumns, Ejb3Column[] elementColumns,
  532   			boolean isEmbedded,
  533   			XProperty property, boolean unique,
  534   			TableBinder associationTableBinder, boolean ignoreNotFound, ExtendedMappings mappings
  535   	) {
  536   		PersistentClass persistentClass = (PersistentClass) persistentClasses.get( collType.getName() );
  537   		boolean reversePropertyInJoin = false;
  538   		if ( persistentClass != null && StringHelper.isNotEmpty( this.mappedBy ) ) {
  539   			try {
  540   				reversePropertyInJoin = 0 != persistentClass.getJoinNumber(
  541   						persistentClass.getRecursiveProperty( this.mappedBy )
  542   				);
  543   			}
  544   			catch (MappingException e) {
  545   				StringBuilder error = new StringBuilder( 80 );
  546   				error.append( "mappedBy reference an unknown target entity property: " )
  547   						.append( collType ).append( "." ).append( this.mappedBy )
  548   						.append( " in " )
  549   						.append( collection.getOwnerEntityName() )
  550   						.append( "." )
  551   						.append( property.getName() );
  552   				throw new AnnotationException( error.toString() );
  553   			}
  554   		}
  555   		if ( persistentClass != null
  556   				&& ! reversePropertyInJoin
  557   				&& oneToMany
  558   				&& ! this.isExplicitAssociationTable
  559   				&& ( joinColumns[0].isImplicit() && ! BinderHelper.isDefault( this.mappedBy ) //implicit @JoinColumn
  560   				|| ! fkJoinColumns[0].isImplicit() ) //this is an explicit @JoinColumn
  561   				) {
  562   			//this is a Foreign key
  563   			bindOneToManySecondPass(
  564   					getCollection(),
  565   					persistentClasses,
  566   					fkJoinColumns,
  567   					collType,
  568   					cascadeDeleteEnabled,
  569   					ignoreNotFound, hqlOrderBy,
  570   					mappings
  571   			);
  572   			return true;
  573   		}
  574   		else {
  575   			//this is an association table
  576   			bindManyToManySecondPass(
  577   					this.collection,
  578   					persistentClasses,
  579   					keyColumns,
  580   					inverseColumns,
  581   					elementColumns,
  582   					isEmbedded, collType,
  583   					ignoreNotFound, unique,
  584   					cascadeDeleteEnabled,
  585   					associationTableBinder, property, propertyHolder, hqlOrderBy, mappings
  586   			);
  587   			return false;
  588   		}
  589   	}
  590   
  591   	protected void bindOneToManySecondPass(
  592   			Collection collection, Map persistentClasses, Ejb3JoinColumn[] fkJoinColumns,
  593               XClass collectionType,
  594   			boolean cascadeDeleteEnabled, boolean ignoreNotFound, String hqlOrderBy, ExtendedMappings extendedMappings
  595   	) {
  596   		if ( log.isDebugEnabled() ) {
  597   			log.debug(
  598   					"Binding a OneToMany: " + propertyHolder.getEntityName() + "." + propertyName + " through a foreign key"
  599   			);
  600   		}
  601   		org.hibernate.mapping.OneToMany oneToMany = new org.hibernate.mapping.OneToMany( collection.getOwner() );
  602   		collection.setElement( oneToMany );
  603   		oneToMany.setReferencedEntityName( collectionType.getName() );
  604   		oneToMany.setIgnoreNotFound( ignoreNotFound );
  605   
  606   		String assocClass = oneToMany.getReferencedEntityName();
  607   		PersistentClass associatedClass = (PersistentClass) persistentClasses.get( assocClass );
  608   		String orderBy = buildOrderByClauseFromHql( hqlOrderBy, associatedClass, collection.getRole() );
  609   		if ( orderBy != null ) collection.setOrderBy( orderBy );
  610   		if ( mappings == null ) {
  611   			throw new AssertionFailure(
  612   					"CollectionSecondPass for oneToMany should not be called with null mappings"
  613   			);
  614   		}
  615   		Map<String, Join> joins = mappings.getJoins( assocClass );
  616   		if ( associatedClass == null ) {
  617   			throw new MappingException(
  618   					"Association references unmapped class: " + assocClass
  619   			);
  620   		}
  621   		oneToMany.setAssociatedClass( associatedClass );
  622   		for ( Ejb3JoinColumn column : fkJoinColumns ) {
  623   			column.setPersistentClass( associatedClass, joins );
  624   			column.setJoins( joins );
  625   			collection.setCollectionTable( column.getTable() );
  626   		}
  627   		log.info(
  628   				"Mapping collection: " + collection.getRole() + " -> " + collection.getCollectionTable().getName()
  629   		);
  630   		bindFilters(false);
  631   		bindCollectionSecondPass( collection, null, fkJoinColumns, cascadeDeleteEnabled, property, mappings );
  632   		if ( !collection.isInverse()
  633   				&& !collection.getKey().isNullable() ) {
  634   			// for non-inverse one-to-many, with a not-null fk, add a backref!
  635   			String entityName = oneToMany.getReferencedEntityName();
  636   			PersistentClass referenced = mappings.getClass( entityName );
  637   			Backref prop = new Backref();
  638   			prop.setName( '_' + fkJoinColumns[0].getPropertyName() + "Backref" );
  639   			prop.setUpdateable( false );
  640   			prop.setSelectable( false );
  641   			prop.setCollectionRole( collection.getRole() );
  642   			prop.setEntityName( collection.getOwner().getEntityName() );
  643   			prop.setValue( collection.getKey() );
  644   			referenced.addProperty( prop );
  645   		}
  646   	}
  647   
  648   	private void bindFilters(boolean hasAssociationTable) {
  649   		Filter simpleFilter = property.getAnnotation( Filter.class );
  650   		//set filtering
  651   		//test incompatible choices
  652   		//if ( StringHelper.isNotEmpty( where ) ) collection.setWhere( where );
  653   		if (simpleFilter != null) {
  654   			if (hasAssociationTable) {
  655   				collection.addManyToManyFilter( simpleFilter.name(), getCondition( simpleFilter ) );
  656   			}
  657   			else {
  658   				collection.addFilter( simpleFilter.name(), getCondition( simpleFilter ) );
  659   			}
  660   		}
  661   		Filters filters = property.getAnnotation( Filters.class );
  662   		if (filters != null) {
  663   			for ( Filter filter : filters.value() ) {
  664   				if (hasAssociationTable) {
  665   					collection.addManyToManyFilter( filter.name(), getCondition( filter ) );
  666   				}
  667   				else {
  668   					collection.addFilter( filter.name(), getCondition( filter ) );
  669   				}
  670   			}
  671   		}
  672   		FilterJoinTable simpleFilterJoinTable = property.getAnnotation( FilterJoinTable.class );
  673   		if (simpleFilterJoinTable != null) {
  674   			if (hasAssociationTable) {
  675   				collection.addFilter( simpleFilterJoinTable.name(), getCondition( simpleFilterJoinTable ) );
  676   			}
  677   			else {
  678   				throw new AnnotationException(
  679   						"Illegal use of @FilterJoinTable on an association without join table:"
  680   								+ StringHelper.qualify( propertyHolder.getPath(), propertyName )
  681   				);
  682   			}
  683   		}
  684   		FilterJoinTables filterJoinTables = property.getAnnotation( FilterJoinTables.class );
  685   		if (filterJoinTables != null) {
  686   			for ( FilterJoinTable filter : filterJoinTables.value() ) {
  687   				if (hasAssociationTable) {
  688   					collection.addFilter( filter.name(), getCondition( filter ) );
  689   				}
  690   				else {
  691   					throw new AnnotationException(
  692   							"Illegal use of @FilterJoinTable on an association without join table:"
  693   									+ StringHelper.qualify( propertyHolder.getPath(), propertyName )
  694   					);
  695   				}
  696   			}
  697   		}
  698   
  699   		Where where = property.getAnnotation( Where.class );
  700   		String whereClause = where == null ? null : where.clause();
  701   		if ( StringHelper.isNotEmpty( whereClause ) ) {
  702   			if (hasAssociationTable) {
  703   				collection.setManyToManyWhere( whereClause );
  704   			}
  705   			else {
  706   				collection.setWhere( whereClause );
  707   			}
  708   		}
  709   
  710   		WhereJoinTable whereJoinTable = property.getAnnotation( WhereJoinTable.class );
  711   		String whereJoinTableClause = whereJoinTable == null ? null : whereJoinTable.clause();
  712   		if ( StringHelper.isNotEmpty( whereJoinTableClause ) ) {
  713   			if (hasAssociationTable) {
  714   				collection.setWhere( whereJoinTableClause );
  715   			}
  716   			else {
  717   				throw new AnnotationException(
  718   						"Illegal use of @WhereJoinTable on an association without join table:"
  719   								+ StringHelper.qualify( propertyHolder.getPath(), propertyName )
  720   				);
  721   			}
  722   		}
  723   //		This cannot happen in annotations since the second fetch is hardcoded to join
  724   //		if ( ( ! collection.getManyToManyFilterMap().isEmpty() || collection.getManyToManyWhere() != null ) &&
  725   //		        collection.getFetchMode() == FetchMode.JOIN &&
  726   //		        collection.getElement().getFetchMode() != FetchMode.JOIN ) {
  727   //			throw new MappingException(
  728   //			        "association with join table  defining filter or where without join fetching " +
  729   //			        "not valid within collection using join fetching [" + collection.getRole() + "]"
  730   //				);
  731   //		}
  732   	}
  733   
  734   	private String getCondition(FilterJoinTable filter) {
  735   		//set filtering
  736   		String name = filter.name();
  737   		String cond = filter.condition();
  738   		return getCondition( cond, name );
  739   	}
  740   
  741   	private String getCondition(Filter filter) {
  742   		//set filtering
  743   		String name = filter.name();
  744   		String cond = filter.condition();
  745   		return getCondition( cond, name );
  746   	}
  747   
  748   	private String getCondition(String cond, String name) {
  749   		if ( BinderHelper.isDefault( cond ) ) {
  750   			cond = mappings.getFilterDefinition( name ).getDefaultFilterCondition();
  751   			if ( StringHelper.isEmpty( cond ) ) {
  752   				throw new AnnotationException(
  753   						"no filter condition found for filter " + name + " in "
  754   								+ StringHelper.qualify( propertyHolder.getPath(), propertyName )
  755   				);
  756   			}
  757   		}
  758   		return cond;
  759   	}
  760   
  761   	public void setCache(Cache cacheAnn) {
  762   		if ( cacheAnn != null ) {
  763   			cacheRegionName = BinderHelper.isDefault( cacheAnn.region() ) ? null : cacheAnn.region();
  764   			cacheConcurrencyStrategy = EntityBinder.getCacheConcurrencyStrategy( cacheAnn.usage() );
  765   		}
  766   		else {
  767   			cacheConcurrencyStrategy = null;
  768   			cacheRegionName = null;
  769   		}
  770   	}
  771   
  772   	public void setOneToMany(boolean oneToMany) {
  773   		this.oneToMany = oneToMany;
  774   	}
  775   
  776   	public void setIndexColumn(IndexColumn indexColumn) {
  777   		this.indexColumn = indexColumn;
  778   	}
  779   
  780   	public void setMapKey(MapKey key) {
  781   		if ( key != null ) {
  782   			mapKeyPropertyName = key.name();
  783   		}
  784   	}
  785   
  786   	private static String buildOrderByClauseFromHql(String hqlOrderBy, PersistentClass associatedClass, String role) {
  787   		String orderByString = null;
  788   		if ( hqlOrderBy != null ) {
  789   			List<String> properties = new ArrayList<String>();
  790   			List<String> ordering = new ArrayList<String>();
  791   			StringBuilder orderByBuffer = new StringBuilder();
  792   			if ( hqlOrderBy.length() == 0 ) {
  793   				//order by id
  794   				Iterator it = associatedClass.getIdentifier().getColumnIterator();
  795   				while ( it.hasNext() ) {
  796   					Selectable col = (Selectable) it.next();
  797   					orderByBuffer.append( col.getText() ).append( " asc" ).append( ", " );
  798   				}
  799   			}
  800   			else {
  801   				StringTokenizer st = new StringTokenizer( hqlOrderBy, " ,", false );
  802   				String currentOrdering = null;
  803   				//FIXME make this code decent
  804   				while ( st.hasMoreTokens() ) {
  805   					String token = st.nextToken();
  806   					if ( isNonPropertyToken( token ) ) {
  807   						if ( currentOrdering != null ) {
  808   							throw new AnnotationException(
  809   									"Error while parsing HQL orderBy clause: " + hqlOrderBy
  810   											+ " (" + role + ")"
  811   							);
  812   						}
  813   						currentOrdering = token;
  814   					}
  815   					else {
  816   						//Add ordering of the previous
  817   						if ( currentOrdering == null ) {
  818   							//default ordering
  819   							ordering.add( "asc" );
  820   						}
  821   						else {
  822   							ordering.add( currentOrdering );
  823   							currentOrdering = null;
  824   						}
  825   						properties.add( token );
  826   					}
  827   				}
  828   				ordering.remove( 0 ); //first one is the algorithm starter
  829   				// add last one ordering
  830   				if ( currentOrdering == null ) {
  831   					//default ordering
  832   					ordering.add( "asc" );
  833   				}
  834   				else {
  835   					ordering.add( currentOrdering );
  836   					currentOrdering = null;
  837   				}
  838   				int index = 0;
  839   
  840   				for ( String property : properties ) {
  841   					Property p = BinderHelper.findPropertyByName( associatedClass, property );
  842   					if ( p == null ) {
  843   						throw new AnnotationException(
  844   								"property from @OrderBy clause not found: "
  845   										+ associatedClass.getEntityName() + "." + property
  846   						);
  847   					}
  848   					PersistentClass pc = p.getPersistentClass();
  849   					String table;
  850   					if (pc != associatedClass) {
  851   						table = pc.getTable().getQuotedName() + ".";
  852   					}
  853   					else {
  854   						table = "";
  855   					}
  856   					Iterator propertyColumns = p.getColumnIterator();
  857   					while ( propertyColumns.hasNext() ) {
  858   						Selectable column = (Selectable) propertyColumns.next();
  859   						orderByBuffer.append( table )
  860   								.append( column.getText() )
  861   								.append( " " )
  862   								.append( ordering.get( index ) )
  863   								.append( ", " );
  864   					}
  865   					index++;
  866   				}
  867   			}
  868   			orderByString = orderByBuffer.substring( 0, orderByBuffer.length() - 2 );
  869   		}
  870   		return orderByString;
  871   	}
  872   
  873   	private static String buildOrderByClauseFromHql(String hqlOrderBy, Component component, String role) {
  874   		String orderByString = null;
  875   		if ( hqlOrderBy != null ) {
  876   			List<String> properties = new ArrayList<String>();
  877   			List<String> ordering = new ArrayList<String>();
  878   			StringBuilder orderByBuffer = new StringBuilder();
  879   			if ( hqlOrderBy.length() == 0 ) {
  880   				//TODO : Check that. Maybe order by key for maps
  881   			}
  882   			else {
  883   				StringTokenizer st = new StringTokenizer( hqlOrderBy, " ,", false );
  884   				String currentOrdering = null;
  885   				//FIXME make this code decent
  886   				while ( st.hasMoreTokens() ) {
  887   					String token = st.nextToken();
  888   					if ( isNonPropertyToken( token ) ) {
  889   						if ( currentOrdering != null ) {
  890   							throw new AnnotationException(
  891   									"Error while parsing HQL orderBy clause: " + hqlOrderBy
  892   											+ " (" + role + ")"
  893   							);
  894   						}
  895   						currentOrdering = token;
  896   					}
  897   					else {
  898   						//Add ordering of the previous
  899   						if ( currentOrdering == null ) {
  900   							//default ordering
  901   							ordering.add( "asc" );
  902   						}
  903   						else {
  904   							ordering.add( currentOrdering );
  905   							currentOrdering = null;
  906   						}
  907   						properties.add( token );
  908   					}
  909   				}
  910   				ordering.remove( 0 ); //first one is the algorithm starter
  911   				// add last one ordering
  912   				if ( currentOrdering == null ) {
  913   					//default ordering
  914   					ordering.add( "asc" );
  915   				}
  916   				else {
  917   					ordering.add( currentOrdering );
  918   					currentOrdering = null;
  919   				}
  920   				int index = 0;
  921   
  922   				for ( String property : properties ) {
  923   					Property p = component.getProperty( property );
  924   					if ( p == null ) {
  925   						throw new AnnotationException(
  926   								"property from @OrderBy clause not found: "
  927   										+ role + "." + property
  928   						);
  929   					}
  930   
  931   					Iterator propertyColumns = p.getColumnIterator();
  932   					while ( propertyColumns.hasNext() ) {
  933   						Selectable column = (Selectable) propertyColumns.next();
  934   						orderByBuffer.append( column.getText() )
  935   								.append( " " )
  936   								.append( ordering.get( index ) )
  937   								.append( ", " );
  938   					}
  939   					index++;
  940   				}
  941   
  942   				if ( orderByBuffer.length() >= 2 ) {
  943   					orderByString = orderByBuffer.substring( 0, orderByBuffer.length() - 2 );
  944   				}
  945   			}
  946   		}
  947   		return orderByString;
  948   	}
  949   
  950   	private static boolean isNonPropertyToken(String token) {
  951   		if ( " ".equals( token ) ) return true;
  952   		if ( ",".equals( token ) ) return true;
  953   		if ( token.equalsIgnoreCase( "desc" ) ) return true;
  954   		if ( token.equalsIgnoreCase( "asc" ) ) return true;
  955   		return false;
  956   	}
  957   
  958   	private static SimpleValue buildCollectionKey(
  959   			Collection collValue, Ejb3JoinColumn[] joinColumns, boolean cascadeDeleteEnabled,
  960   			XProperty property, ExtendedMappings mappings
  961   	) {
  962   		//binding key reference using column
  963   		KeyValue keyVal;
  964   		//give a chance to override the referenced property name
  965   		//has to do that here because the referencedProperty creation happens in a FKSecondPass for Many to one yuk!
  966   		if ( joinColumns.length > 0 && StringHelper.isNotEmpty( joinColumns[0].getMappedBy() ) ) {
  967   			String entityName = joinColumns[0].getManyToManyOwnerSideEntityName() != null ?
  968   					"inverse__" + joinColumns[0].getManyToManyOwnerSideEntityName() :
  969   					joinColumns[0].getPropertyHolder().getEntityName();
  970   			String propRef = mappings.getPropertyReferencedAssociation(
  971   					entityName,
  972   					joinColumns[0].getMappedBy()
  973   			);
  974   			if ( propRef != null ) {
  975   				collValue.setReferencedPropertyName( propRef );
  976   				mappings.addPropertyReference( collValue.getOwnerEntityName(), propRef );
  977   			}
  978   		}
  979   		String propRef = collValue.getReferencedPropertyName();
  980   		if ( propRef == null ) {
  981   			keyVal = collValue.getOwner().getIdentifier();
  982   		}
  983   		else {
  984   			keyVal = (KeyValue) collValue.getOwner()
  985   					.getRecursiveProperty( propRef )
  986   					.getValue();
  987   		}
  988   		DependantValue key = new DependantValue( collValue.getCollectionTable(), keyVal );
  989   		key.setTypeName( null );
  990   		Ejb3Column.checkPropertyConsistency( joinColumns, collValue.getOwnerEntityName() );
  991   		key.setNullable( joinColumns.length == 0 || joinColumns[0].isNullable() );
  992   		key.setUpdateable( joinColumns.length == 0 || joinColumns[0].isUpdatable() );
  993   		key.setCascadeDeleteEnabled( cascadeDeleteEnabled );
  994   		collValue.setKey( key );
  995   		ForeignKey fk = property != null ? property.getAnnotation( ForeignKey.class ) : null;
  996   		String fkName = fk != null ? fk.name() : "";
  997   		if ( ! BinderHelper.isDefault( fkName ) ) key.setForeignKeyName( fkName );
  998   		return key;
  999   	}
 1000   
 1001   	protected void bindManyToManySecondPass(
 1002   			Collection collValue,
 1003   			Map persistentClasses,
 1004   			Ejb3JoinColumn[] joinColumns,
 1005   			Ejb3JoinColumn[] inverseJoinColumns,
 1006   			Ejb3Column[] elementColumns,
 1007   			boolean isEmbedded,
 1008               XClass collType,
 1009   			boolean ignoreNotFound, boolean unique,
 1010   			boolean cascadeDeleteEnabled,
 1011   			TableBinder associationTableBinder, XProperty property, PropertyHolder parentPropertyHolder,
 1012   			String hqlOrderBy, ExtendedMappings mappings
 1013   	) throws MappingException {
 1014   
 1015   		PersistentClass collectionEntity = (PersistentClass) persistentClasses.get( collType.getName() );
 1016   		boolean isCollectionOfEntities = collectionEntity != null;
 1017   		if ( log.isDebugEnabled() ) {
 1018   			String path = collValue.getOwnerEntityName() + "." + joinColumns[0].getPropertyName();
 1019   			if ( isCollectionOfEntities && unique ) {
 1020   				log.debug( "Binding a OneToMany: " + path + " through an association table" );
 1021   			}
 1022   			else if ( isCollectionOfEntities ) {
 1023   				log.debug( "Binding as ManyToMany: " + path );
 1024   			}
 1025   			else {
 1026   				log.debug( "Binding a collection of element: " + path );
 1027   			}
 1028   		}
 1029   		//check for user error
 1030   		if ( ! isCollectionOfEntities ) {
 1031   			if ( property.isAnnotationPresent( ManyToMany.class ) || property.isAnnotationPresent( OneToMany.class ) ) {
 1032   				String path = collValue.getOwnerEntityName() + "." + joinColumns[0].getPropertyName();
 1033   				throw new AnnotationException(
 1034   						"Use of @OneToMany or @ManyToMany targeting an unmapped class: " + path + "[" + collType + "]"
 1035   				);
 1036   			}
 1037   			else {
 1038   				JoinTable joinTableAnn = property.getAnnotation( JoinTable.class );
 1039   				if ( joinTableAnn != null && joinTableAnn.inverseJoinColumns().length > 0 ) {
 1040   					String path = collValue.getOwnerEntityName() + "." + joinColumns[0].getPropertyName();
 1041   					throw new AnnotationException(
 1042   							"Use of @JoinTable.inverseJoinColumns targeting an unmapped class: " + path + "[" + collType + "]"
 1043   					);
 1044   				}
 1045   			}
 1046   		}
 1047   
 1048   		boolean mappedBy = ! BinderHelper.isDefault( joinColumns[0].getMappedBy() );
 1049   		if ( mappedBy ) {
 1050   			if ( ! isCollectionOfEntities ) {
 1051   				StringBuilder error = new StringBuilder( 80 )
 1052   						.append(
 1053   								"Collection of elements must not have mappedBy or association reference an unmapped entity: "
 1054   						)
 1055   						.append( collValue.getOwnerEntityName() )
 1056   						.append( "." )
 1057   						.append( joinColumns[0].getPropertyName() );
 1058   				throw new AnnotationException( error.toString() );
 1059   			}
 1060   			Property otherSideProperty;
 1061   			try {
 1062   				otherSideProperty = collectionEntity.getRecursiveProperty( joinColumns[0].getMappedBy() );
 1063   			}
 1064   			catch (MappingException e) {
 1065   				StringBuilder error = new StringBuilder( 80 );
 1066   				error.append( "mappedBy reference an unknown target entity property: " )
 1067   						.append( collType ).append( "." ).append( joinColumns[0].getMappedBy() )
 1068   						.append( " in " )
 1069   						.append( collValue.getOwnerEntityName() )
 1070   						.append( "." )
 1071   						.append( joinColumns[0].getPropertyName() );
 1072   				throw new AnnotationException( error.toString() );
 1073   			}
 1074   			Table table;
 1075   			if ( otherSideProperty.getValue() instanceof Collection ) {
 1076   				//this is a collection on the other side
 1077   				table = ( (Collection) otherSideProperty.getValue() ).getCollectionTable();
 1078   			}
 1079   			else {
 1080   				//This is a ToOne with a @JoinTable or a regular property
 1081   				table = otherSideProperty.getValue().getTable();
 1082   			}
 1083   			collValue.setCollectionTable( table );
 1084   			String entityName = collectionEntity.getEntityName();
 1085   			for ( Ejb3JoinColumn column : joinColumns ) {
 1086   				//column.setDefaultColumnHeader( joinColumns[0].getMappedBy() ); //seems not to be used, make sense
 1087   				column.setManyToManyOwnerSideEntityName( entityName );
 1088   			}
 1089   		}
 1090   		else {
 1091   			//TODO: only for implicit columns?
 1092   			//FIXME NamingStrategy
 1093   			for ( Ejb3JoinColumn column : joinColumns ) {
 1094   				String mappedByProperty = mappings.getFromMappedBy(
 1095   						collValue.getOwnerEntityName(), column.getPropertyName()
 1096   				);
 1097   				Table ownerTable = collValue.getOwner().getTable();
 1098   				column.setMappedBy(
 1099   						collValue.getOwner().getEntityName(), mappings.getLogicalTableName( ownerTable ),
 1100   						mappedByProperty
 1101   				);
 1102   //				String header = ( mappedByProperty == null ) ? mappings.getLogicalTableName( ownerTable ) : mappedByProperty;
 1103   //				column.setDefaultColumnHeader( header );
 1104   			}
 1105   			if ( StringHelper.isEmpty( associationTableBinder.getName() ) ) {
 1106   				//default value
 1107   				associationTableBinder.setDefaultName(
 1108   						collValue.getOwner().getEntityName(),
 1109   						mappings.getLogicalTableName( collValue.getOwner().getTable() ),
 1110   						collectionEntity != null ? collectionEntity.getEntityName() : null,
 1111   						collectionEntity != null ? mappings.getLogicalTableName( collectionEntity.getTable() ) : null,
 1112   						joinColumns[0].getPropertyName()
 1113   				);
 1114   			}
 1115   			collValue.setCollectionTable( associationTableBinder.bind() );
 1116   		}
 1117   		bindFilters( isCollectionOfEntities );
 1118   		bindCollectionSecondPass( collValue, collectionEntity, joinColumns, cascadeDeleteEnabled, property, mappings );
 1119   
 1120   		ManyToOne element = null;
 1121   		if ( isCollectionOfEntities ) {
 1122   			element =
 1123   					new ManyToOne( collValue.getCollectionTable() );
 1124   			collValue.setElement( element );
 1125   			element.setReferencedEntityName( collType.getName() );
 1126   			//element.setFetchMode( fetchMode );
 1127   			//element.setLazy( fetchMode != FetchMode.JOIN );
 1128   			//make the second join non lazy
 1129   			element.setFetchMode( FetchMode.JOIN );
 1130   			element.setLazy( false );
 1131   			element.setIgnoreNotFound( ignoreNotFound );
 1132   			if ( StringHelper.isNotEmpty( hqlOrderBy ) ) {
 1133   				collValue.setManyToManyOrdering(
 1134   						buildOrderByClauseFromHql( hqlOrderBy, collectionEntity, collValue.getRole() )
 1135   				);
 1136   			}
 1137   			ForeignKey fk = property != null ? property.getAnnotation( ForeignKey.class ) : null;
 1138   			String fkName = fk != null ? fk.inverseName() : "";
 1139   			if ( ! BinderHelper.isDefault( fkName ) ) element.setForeignKeyName( fkName );
 1140   		}
 1141   		else {
 1142   			XClass elementClass;
 1143   			AnnotatedClassType classType;
 1144   //			Map<String, javax.persistence.Column[]> columnOverrides = PropertyHolderBuilder.buildColumnOverride(
 1145   //					property, StringHelper.qualify( collValue.getRole(), "element" )
 1146   //			);
 1147   			//FIXME the "element" is lost
 1148   			PropertyHolder holder = null;
 1149   			if ( BinderHelper.PRIMITIVE_NAMES.contains( collType.getName() ) ) {
 1150   				classType = AnnotatedClassType.NONE;
 1151   				elementClass = null;
 1152   			}
 1153   			else {
 1154   				elementClass = collType;
 1155   				classType = mappings.getClassType( elementClass );
 1156   
 1157   				holder = PropertyHolderBuilder.buildPropertyHolder(
 1158   						collValue,
 1159   						collValue.getRole(), // + ".element",
 1160   						elementClass,
 1161   						property, parentPropertyHolder, mappings
 1162   				);
 1163   				//force in case of attribute override
 1164   				boolean attributeOverride = property.isAnnotationPresent( AttributeOverride.class )
 1165   						|| property.isAnnotationPresent( AttributeOverrides.class );
 1166   				if ( isEmbedded || attributeOverride ) {
 1167   					classType = AnnotatedClassType.EMBEDDABLE;
 1168   				}
 1169   			}
 1170   
 1171   			if ( AnnotatedClassType.EMBEDDABLE.equals( classType ) ) {
 1172   				EntityBinder entityBinder = new EntityBinder();
 1173   				PersistentClass owner = collValue.getOwner();
 1174   				boolean isPropertyAnnotated;
 1175   				//FIXME support @Access for collection of elements
 1176   				//String accessType = access != null ? access.value() : null;
 1177   				if ( owner.getIdentifierProperty() != null ) {
 1178   					isPropertyAnnotated = owner.getIdentifierProperty().getPropertyAccessorName().equals( "property" );
 1179   				}
 1180   				else if ( owner.getIdentifierMapper() != null && owner.getIdentifierMapper().getPropertySpan() > 0 ) {
 1181   					Property prop = (Property) owner.getIdentifierMapper().getPropertyIterator().next();
 1182   					isPropertyAnnotated = prop.getPropertyAccessorName().equals( "property" );
 1183   				}
 1184   				else {
 1185   					throw new AssertionFailure( "Unable to guess collection property accessor name" );
 1186   				}
 1187   
 1188   				//boolean propertyAccess = embeddable == null || AccessType.PROPERTY.equals( embeddable.access() );
 1189   				PropertyData inferredData = new PropertyPreloadedData( "property", "element", elementClass );
 1190   				//TODO be smart with isNullable
 1191   				Component component = AnnotationBinder.fillComponent(
 1192   						holder, inferredData, isPropertyAnnotated, isPropertyAnnotated ? "property" : "field", true,
 1193   						entityBinder, false, false,
 1194   						true, mappings
 1195   				);
 1196   
 1197   				collValue.setElement( component );
 1198   
 1199   				if ( StringHelper.isNotEmpty( hqlOrderBy ) ) {
 1200   					String path = collValue.getOwnerEntityName() + "." + joinColumns[0].getPropertyName();
 1201   					String orderBy = buildOrderByClauseFromHql( hqlOrderBy, component, path );
 1202   					if ( orderBy != null ) {
 1203   						collValue.setOrderBy( orderBy );
 1204   					}
 1205   				}
 1206   			}
 1207   			else {
 1208   				SimpleValueBinder elementBinder = new SimpleValueBinder();
 1209   				elementBinder.setMappings( mappings );
 1210   				elementBinder.setReturnedClassName( collType.getName() );
 1211   				if ( elementColumns == null || elementColumns.length == 0 ) {
 1212   					elementColumns = new Ejb3Column[1];
 1213   					Ejb3Column column = new Ejb3Column();
 1214   					column.setImplicit( false );
 1215   					//not following the spec but more clean
 1216   					column.setNullable( true );
 1217   					column.setLength( Ejb3Column.DEFAULT_COLUMN_LENGTH );
 1218   					column.setLogicalColumnName( Collection.DEFAULT_ELEMENT_COLUMN_NAME );
 1219   					//TODO create an EMPTY_JOINS collection
 1220   					column.setJoins( new HashMap<String, Join>() );
 1221   					column.setMappings( mappings );
 1222   					column.bind();
 1223   					elementColumns[0] = column;
 1224   				}
 1225   				//override the table
 1226   				for ( Ejb3Column column : elementColumns ) {
 1227   					column.setTable( collValue.getCollectionTable() );
 1228   				}
 1229   				elementBinder.setColumns( elementColumns );
 1230   				elementBinder.setType( property, elementClass );
 1231   				collValue.setElement( elementBinder.make() );
 1232   			}
 1233   		}
 1234   
 1235   		checkFilterConditions( collValue );
 1236   
 1237   		//FIXME: do optional = false
 1238   		if ( isCollectionOfEntities ) {
 1239   			bindManytoManyInverseFk( collectionEntity, inverseJoinColumns, element, unique, mappings );
 1240   		}
 1241   
 1242   	}
 1243   
 1244   	private static void checkFilterConditions(Collection collValue) {
 1245   		//for now it can't happen, but sometime soon...
 1246   		if ( ( collValue.getFilterMap().size() != 0 || StringHelper.isNotEmpty( collValue.getWhere() ) ) &&
 1247   				collValue.getFetchMode() == FetchMode.JOIN &&
 1248   				collValue.getElement().getFetchMode() != FetchMode.JOIN ) {
 1249   			throw new MappingException(
 1250   					"@ManyToMany or @CollectionOfElements defining filter or where without join fetching "
 1251   							+ "not valid within collection using join fetching[" + collValue.getRole() + "]"
 1252   			);
 1253   		}
 1254   	}
 1255   
 1256   	private static void bindCollectionSecondPass(
 1257   			Collection collValue, PersistentClass collectionEntity, Ejb3JoinColumn[] joinColumns,
 1258   			boolean cascadeDeleteEnabled, XProperty property,
 1259   			ExtendedMappings mappings
 1260   	) {
 1261   		BinderHelper.createSyntheticPropertyReference(
 1262   				joinColumns, collValue.getOwner(), collectionEntity, collValue, false, mappings
 1263   		);
 1264   		SimpleValue key = buildCollectionKey( collValue, joinColumns, cascadeDeleteEnabled, property, mappings );
 1265   		TableBinder.bindFk( collValue.getOwner(), collectionEntity, joinColumns, key, false, mappings );
 1266   	}
 1267   
 1268   	public void setCascadeDeleteEnabled(boolean onDeleteCascade) {
 1269   		this.cascadeDeleteEnabled = onDeleteCascade;
 1270   	}
 1271   
 1272   	private String safeCollectionRole() {
 1273   		if ( propertyHolder != null ) {
 1274   			return propertyHolder.getEntityName() + "." + propertyName;
 1275   		}
 1276   		else {
 1277   			return "";
 1278   		}
 1279   	}
 1280   
 1281   
 1282   	/**
 1283   	 * bind the inverse FK of a ManyToMany
 1284   	 * If we are in a mappedBy case, read the columns from the associated
 1285   	 * colletion element
 1286   	 * Otherwise delegates to the usual algorithm
 1287   	 */
 1288   	public static void bindManytoManyInverseFk(
 1289   			PersistentClass referencedEntity, Ejb3JoinColumn[] columns, SimpleValue value, boolean unique,
 1290   			ExtendedMappings mappings
 1291   	) {
 1292   		final String mappedBy = columns[0].getMappedBy();
 1293   		if ( StringHelper.isNotEmpty( mappedBy ) ) {
 1294   			final Property property = referencedEntity.getRecursiveProperty( mappedBy );
 1295   			Iterator mappedByColumns;
 1296   			if ( property.getValue() instanceof Collection ) {
 1297   				mappedByColumns = ( (Collection) property.getValue() ).getKey().getColumnIterator();
 1298   			}
 1299   			else {
 1300   				//find the appropriate reference key, can be in a join
 1301   				Iterator joinsIt = referencedEntity.getJoinIterator();
 1302   				KeyValue key = null;
 1303   				while ( joinsIt.hasNext() ) {
 1304   					Join join = (Join) joinsIt.next();
 1305   					if ( join.containsProperty( property ) ) {
 1306   						key = join.getKey();
 1307   						break;
 1308   					}
 1309   				}
 1310   				if ( key == null ) key = property.getPersistentClass().getIdentifier();
 1311   				mappedByColumns = key.getColumnIterator();
 1312   			}
 1313   			while ( mappedByColumns.hasNext() ) {
 1314   				Column column = (Column) mappedByColumns.next();
 1315   				columns[0].linkValueUsingAColumnCopy( column, value );
 1316   			}
 1317   			String referencedPropertyName =
 1318   					mappings.getPropertyReferencedAssociation(
 1319   							"inverse__" + referencedEntity.getEntityName(), mappedBy
 1320   					);
 1321   			if ( referencedPropertyName != null ) {
 1322   				//TODO always a many to one?
 1323   				( (ManyToOne) value ).setReferencedPropertyName( referencedPropertyName );
 1324   				mappings.addUniquePropertyReference( referencedEntity.getEntityName(), referencedPropertyName );
 1325   			}
 1326   			value.createForeignKey();
 1327   		}
 1328   		else {
 1329   			BinderHelper.createSyntheticPropertyReference( columns, referencedEntity, null, value, true, mappings );
 1330   			TableBinder.bindFk( referencedEntity, null, columns, value, unique, mappings );
 1331   		}
 1332   	}
 1333   
 1334   	public void setFkJoinColumns(Ejb3JoinColumn[] ejb3JoinColumns) {
 1335   		this.fkJoinColumns = ejb3JoinColumns;
 1336   	}
 1337   
 1338   	public void setExplicitAssociationTable(boolean explicitAssocTable) {
 1339   		this.isExplicitAssociationTable = explicitAssocTable;
 1340   	}
 1341   
 1342   	public void setElementColumns(Ejb3Column[] elementColumns) {
 1343   		this.elementColumns = elementColumns;
 1344   	}
 1345   
 1346   	public void setEmbedded(boolean annotationPresent) {
 1347   		this.isEmbedded = annotationPresent;
 1348   	}
 1349   
 1350   	public void setProperty(XProperty property) {
 1351   		this.property = property;
 1352   	}
 1353   
 1354   	public void setIgnoreNotFound(boolean ignoreNotFound) {
 1355   		this.ignoreNotFound = ignoreNotFound;
 1356   	}
 1357   
 1358   	public void setMapKeyColumns(Ejb3Column[] mapKeyColumns) {
 1359   		this.mapKeyColumns = mapKeyColumns;
 1360   	}
 1361   
 1362   	public void setMapKeyManyToManyColumns(Ejb3JoinColumn[] mapJoinColumns) {
 1363   		this.mapKeyManyToManyColumns = mapJoinColumns;
 1364   	}
 1365   
 1366   	public void setLocalGenerators(HashMap<String, IdGenerator> localGenerators) {
 1367   		this.localGenerators = localGenerators;
 1368   	}
 1369   }

Save This Page
Home » hibernate-distribution-3.3.1.GA-dist » org.hibernate » cfg » annotations » [javadoc | source]