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 }