1 //$Id: AnnotationBinder.java 11264 2007-03-09 04:24:33Z epbernard $
2 package org.hibernate.cfg;
3
4 import java.util.ArrayList;
5 import java.util.EnumSet;
6 import java.util.HashMap;
7 import java.util.HashSet;
8 import java.util.Iterator;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.Properties;
12 import java.util.Set;
13 import java.util.Collections;
14 import java.util.Comparator;
15 import javax.persistence.Basic;
16 import javax.persistence.Column;
17 import javax.persistence.DiscriminatorType;
18 import javax.persistence.DiscriminatorValue;
19 import javax.persistence.Embeddable;
20 import javax.persistence.Embedded;
21 import javax.persistence.EmbeddedId;
22 import javax.persistence.Entity;
23 import javax.persistence.FetchType;
24 import javax.persistence.GeneratedValue;
25 import javax.persistence.GenerationType;
26 import javax.persistence.Id;
27 import javax.persistence.IdClass;
28 import javax.persistence.InheritanceType;
29 import javax.persistence.JoinColumn;
30 import javax.persistence.JoinColumns;
31 import javax.persistence.JoinTable;
32 import javax.persistence.ManyToMany;
33 import javax.persistence.ManyToOne;
34 import javax.persistence.MapKey;
35 import javax.persistence.MappedSuperclass;
36 import javax.persistence.NamedNativeQueries;
37 import javax.persistence.NamedNativeQuery;
38 import javax.persistence.NamedQueries;
39 import javax.persistence.NamedQuery;
40 import javax.persistence.OneToMany;
41 import javax.persistence.OneToOne;
42 import javax.persistence.PrimaryKeyJoinColumn;
43 import javax.persistence.PrimaryKeyJoinColumns;
44 import javax.persistence.SequenceGenerator;
45 import javax.persistence.SqlResultSetMapping;
46 import javax.persistence.SqlResultSetMappings;
47 import javax.persistence.Table;
48 import javax.persistence.TableGenerator;
49 import javax.persistence.Transient;
50 import javax.persistence.Version;
51
52 import org.apache.commons.logging.Log;
53 import org.apache.commons.logging.LogFactory;
54 import org.hibernate.AnnotationException;
55 import org.hibernate.AssertionFailure;
56 import org.hibernate.FetchMode;
57 import org.hibernate.MappingException;
58 import org.hibernate.EntityMode;
59 import org.hibernate.annotations.AccessType;
60 import org.hibernate.annotations.BatchSize;
61 import org.hibernate.annotations.Cache;
62 import org.hibernate.annotations.Cascade;
63 import org.hibernate.annotations.CascadeType;
64 import org.hibernate.annotations.Check;
65 import org.hibernate.annotations.CollectionId;
66 import org.hibernate.annotations.CollectionOfElements;
67 import org.hibernate.annotations.Columns;
68 import org.hibernate.annotations.Fetch;
69 import org.hibernate.annotations.Filter;
70 import org.hibernate.annotations.FilterDef;
71 import org.hibernate.annotations.FilterDefs;
72 import org.hibernate.annotations.Filters;
73 import org.hibernate.annotations.ForeignKey;
74 import org.hibernate.annotations.Formula;
75 import org.hibernate.annotations.GenericGenerator;
76 import org.hibernate.annotations.LazyToOne;
77 import org.hibernate.annotations.LazyToOneOption;
78 import org.hibernate.annotations.MapKeyManyToMany;
79 import org.hibernate.annotations.NotFound;
80 import org.hibernate.annotations.NotFoundAction;
81 import org.hibernate.annotations.OnDelete;
82 import org.hibernate.annotations.OnDeleteAction;
83 import org.hibernate.annotations.OrderBy;
84 import org.hibernate.annotations.ParamDef;
85 import org.hibernate.annotations.Parameter;
86 import org.hibernate.annotations.Parent;
87 import org.hibernate.annotations.Proxy;
88 import org.hibernate.annotations.Sort;
89 import org.hibernate.annotations.Type;
90 import org.hibernate.annotations.TypeDef;
91 import org.hibernate.annotations.TypeDefs;
92 import org.hibernate.annotations.Where;
93 import org.hibernate.annotations.Index;
94 import org.hibernate.annotations.Target;
95 import org.hibernate.annotations.Tuplizers;
96 import org.hibernate.annotations.Tuplizer;
97 import org.hibernate.cfg.annotations.CollectionBinder;
98 import org.hibernate.cfg.annotations.EntityBinder;
99 import org.hibernate.cfg.annotations.Nullability;
100 import org.hibernate.cfg.annotations.PropertyBinder;
101 import org.hibernate.cfg.annotations.QueryBinder;
102 import org.hibernate.cfg.annotations.SimpleValueBinder;
103 import org.hibernate.cfg.annotations.TableBinder;
104 import org.hibernate.engine.FilterDefinition;
105 import org.hibernate.engine.Versioning;
106 import org.hibernate.id.MultipleHiLoPerTableGenerator;
107 import org.hibernate.id.PersistentIdentifierGenerator;
108 import org.hibernate.id.SequenceHiLoGenerator;
109 import org.hibernate.id.TableHiLoGenerator;
110 import org.hibernate.mapping.Component;
111 import org.hibernate.mapping.DependantValue;
112 import org.hibernate.mapping.IdGenerator;
113 import org.hibernate.mapping.Join;
114 import org.hibernate.mapping.JoinedSubclass;
115 import org.hibernate.mapping.PersistentClass;
116 import org.hibernate.mapping.Property;
117 import org.hibernate.mapping.RootClass;
118 import org.hibernate.mapping.SimpleValue;
119 import org.hibernate.mapping.SingleTableSubclass;
120 import org.hibernate.mapping.Subclass;
121 import org.hibernate.mapping.ToOne;
122 import org.hibernate.mapping.UnionSubclass;
123 import org.hibernate.persister.entity.JoinedSubclassEntityPersister;
124 import org.hibernate.persister.entity.SingleTableEntityPersister;
125 import org.hibernate.persister.entity.UnionSubclassEntityPersister;
126 import org.hibernate.annotations.common.reflection.ReflectionManager;
127 import org.hibernate.annotations.common.reflection.XAnnotatedElement;
128 import org.hibernate.annotations.common.reflection.XClass;
129 import org.hibernate.annotations.common.reflection.XPackage;
130 import org.hibernate.annotations.common.reflection.XProperty;
131 import org.hibernate.type.TypeFactory;
132 import org.hibernate.util.StringHelper;
133
134 /**
135 * JSR 175 annotation binder
136 * Will read the annotation from classes, apply the
137 * principles of the EJB3 spec and produces the Hibernate
138 * configuration-time metamodel (the classes in the <tt>mapping</tt>
139 * package)
140 *
141 * @author Emmanuel Bernard
142 */
143 public final class AnnotationBinder {
144
145 /*
146 * Some design description
147 * I tried to remove any link to annotation except from the 2 first level of
148 * method call.
149 * It'll enable to:
150 * - facilitate annotation overriding
151 * - mutualize one day xml and annotation binder (probably a dream though)
152 * - split this huge class in smaller mapping oriented classes
153 *
154 * bindSomething usually create the mapping container and is accessed by one of the 2 first level method
155 * makeSomething usually create the mapping container and is accessed by bindSomething[else]
156 * fillSomething take the container into parameter and fill it.
157 *
158 *
159 */
160 private AnnotationBinder() {
161 }
162
163 private static final Log log = LogFactory.getLog( AnnotationBinder.class );
164
165 public static void bindDefaults(ExtendedMappings mappings) {
166 Map defaults = mappings.getReflectionManager().getDefaults();
167 {
168 List<SequenceGenerator> anns = (List<SequenceGenerator>) defaults.get( SequenceGenerator.class );
169 if ( anns != null ) {
170 for ( SequenceGenerator ann : anns ) {
171 IdGenerator idGen = buildIdGenerator( ann, mappings );
172 if ( idGen != null ) mappings.addDefaultGenerator( idGen );
173 }
174 }
175 }
176 {
177 List<TableGenerator> anns = (List<TableGenerator>) defaults.get( TableGenerator.class );
178 if ( anns != null ) {
179 for ( TableGenerator ann : anns ) {
180 IdGenerator idGen = buildIdGenerator( ann, mappings );
181 if ( idGen != null ) mappings.addDefaultGenerator( idGen );
182 }
183 }
184 }
185 {
186 List<NamedQuery> anns = (List<NamedQuery>) defaults.get( NamedQuery.class );
187 if ( anns != null ) {
188 for ( NamedQuery ann : anns ) {
189 QueryBinder.bindQuery( ann, mappings, true );
190 }
191 }
192 }
193 {
194 List<NamedNativeQuery> anns = (List<NamedNativeQuery>) defaults.get( NamedNativeQuery.class );
195 if ( anns != null ) {
196 for ( NamedNativeQuery ann : anns ) {
197 QueryBinder.bindNativeQuery( ann, mappings, true );
198 }
199 }
200 }
201 {
202 List<SqlResultSetMapping> anns = (List<SqlResultSetMapping>) defaults.get( SqlResultSetMapping.class );
203 if ( anns != null ) {
204 for ( SqlResultSetMapping ann : anns ) {
205 QueryBinder.bindSqlResultsetMapping( ann, mappings, true );
206 }
207 }
208 }
209 }
210
211 public static void bindPackage(String packageName, ExtendedMappings mappings) {
212 XPackage pckg = null;
213 try {
214 pckg = mappings.getReflectionManager().packageForName( packageName );
215 }
216 catch (ClassNotFoundException cnf) {
217 log.warn( "Package not found or wo package-info.java: " + packageName );
218 return;
219 }
220 if ( pckg.isAnnotationPresent( SequenceGenerator.class ) ) {
221 SequenceGenerator ann = pckg.getAnnotation( SequenceGenerator.class );
222 IdGenerator idGen = buildIdGenerator( ann, mappings );
223 mappings.addGenerator( idGen );
224 log.debug( "Add sequence generator with name: " + idGen.getName() );
225 }
226 if ( pckg.isAnnotationPresent( TableGenerator.class ) ) {
227 TableGenerator ann = pckg.getAnnotation( TableGenerator.class );
228 IdGenerator idGen = buildIdGenerator( ann, mappings );
229 mappings.addGenerator( idGen );
230
231 }
232 if ( pckg.isAnnotationPresent( GenericGenerator.class ) ) {
233 GenericGenerator ann = pckg.getAnnotation( GenericGenerator.class );
234 IdGenerator idGen = buildIdGenerator( ann, mappings );
235 mappings.addGenerator( idGen );
236 }
237 bindQueries( pckg, mappings );
238 bindFilterDefs( pckg, mappings );
239 bindTypeDefs( pckg, mappings );
240 }
241
242 private static void bindQueries(XAnnotatedElement annotatedElement, ExtendedMappings mappings) {
243 {
244 SqlResultSetMapping ann = annotatedElement.getAnnotation( SqlResultSetMapping.class );
245 QueryBinder.bindSqlResultsetMapping( ann, mappings, false );
246 }
247 {
248 SqlResultSetMappings ann = annotatedElement.getAnnotation( SqlResultSetMappings.class );
249 if ( ann != null ) {
250 for ( SqlResultSetMapping current : ann.value() ) {
251 QueryBinder.bindSqlResultsetMapping( current, mappings, false );
252 }
253 }
254 }
255 {
256 NamedQuery ann = annotatedElement.getAnnotation( NamedQuery.class );
257 QueryBinder.bindQuery( ann, mappings, false );
258 }
259 {
260 org.hibernate.annotations.NamedQuery ann = annotatedElement.getAnnotation(
261 org.hibernate.annotations.NamedQuery.class
262 );
263 QueryBinder.bindQuery( ann, mappings );
264 }
265 {
266 NamedQueries ann = annotatedElement.getAnnotation( NamedQueries.class );
267 QueryBinder.bindQueries( ann, mappings, false );
268 }
269 {
270 org.hibernate.annotations.NamedQueries ann = annotatedElement.getAnnotation(
271 org.hibernate.annotations.NamedQueries.class
272 );
273 QueryBinder.bindQueries( ann, mappings );
274 }
275 {
276 NamedNativeQuery ann = annotatedElement.getAnnotation( NamedNativeQuery.class );
277 QueryBinder.bindNativeQuery( ann, mappings, false );
278 }
279 {
280 org.hibernate.annotations.NamedNativeQuery ann = annotatedElement.getAnnotation(
281 org.hibernate.annotations.NamedNativeQuery.class
282 );
283 QueryBinder.bindNativeQuery( ann, mappings );
284 }
285 {
286 NamedNativeQueries ann = annotatedElement.getAnnotation( NamedNativeQueries.class );
287 QueryBinder.bindNativeQueries( ann, mappings, false );
288 }
289 {
290 org.hibernate.annotations.NamedNativeQueries ann = annotatedElement.getAnnotation(
291 org.hibernate.annotations.NamedNativeQueries.class
292 );
293 QueryBinder.bindNativeQueries( ann, mappings );
294 }
295 }
296
297 private static IdGenerator buildIdGenerator(java.lang.annotation.Annotation ann, Mappings mappings) {
298 IdGenerator idGen = new IdGenerator();
299 if ( mappings.getSchemaName() != null ) {
300 idGen.addParam( PersistentIdentifierGenerator.SCHEMA, mappings.getSchemaName() );
301 }
302 if ( mappings.getCatalogName() != null ) {
303 idGen.addParam( PersistentIdentifierGenerator.CATALOG, mappings.getCatalogName() );
304 }
305 if ( ann == null ) {
306 idGen = null;
307 }
308 else if ( ann instanceof TableGenerator ) {
309 TableGenerator tabGen = (TableGenerator) ann;
310 idGen.setName( tabGen.name() );
311 idGen.setIdentifierGeneratorStrategy( MultipleHiLoPerTableGenerator.class.getName() );
312
313 if ( !BinderHelper.isDefault( tabGen.table() ) ) {
314 idGen.addParam( MultipleHiLoPerTableGenerator.ID_TABLE, tabGen.table() );
315 }
316 if ( !BinderHelper.isDefault( tabGen.catalog() ) ) {
317 idGen.addParam( MultipleHiLoPerTableGenerator.CATALOG, tabGen.catalog() );
318 }
319 if ( !BinderHelper.isDefault( tabGen.schema() ) ) {
320 idGen.addParam( MultipleHiLoPerTableGenerator.SCHEMA, tabGen.schema() );
321 }
322 //FIXME implements uniqueconstrains
323
324 if ( !BinderHelper.isDefault( tabGen.pkColumnName() ) ) {
325 idGen.addParam( MultipleHiLoPerTableGenerator.PK_COLUMN_NAME, tabGen.pkColumnName() );
326 }
327 if ( !BinderHelper.isDefault( tabGen.valueColumnName() ) ) {
328 idGen.addParam( MultipleHiLoPerTableGenerator.VALUE_COLUMN_NAME, tabGen.valueColumnName() );
329 }
330 if ( !BinderHelper.isDefault( tabGen.pkColumnValue() ) ) {
331 idGen.addParam( MultipleHiLoPerTableGenerator.PK_VALUE_NAME, tabGen.pkColumnValue() );
332 }
333 idGen.addParam( TableHiLoGenerator.MAX_LO, String.valueOf( tabGen.allocationSize() - 1 ) );
334 log.debug( "Add table generator with name: " + idGen.getName() );
335 }
336 else if ( ann instanceof SequenceGenerator ) {
337 SequenceGenerator seqGen = (SequenceGenerator) ann;
338 idGen.setName( seqGen.name() );
339 idGen.setIdentifierGeneratorStrategy( "seqhilo" );
340
341 if ( !BinderHelper.isDefault( seqGen.sequenceName() ) ) {
342 idGen.addParam( org.hibernate.id.SequenceGenerator.SEQUENCE, seqGen.sequenceName() );
343 }
344 //FIXME: work on initialValue() through SequenceGenerator.PARAMETERS
345 if ( seqGen.initialValue() != 1 ) {
346 log.warn(
347 "Hibernate does not support SequenceGenerator.initialValue()"
348 );
349 }
350 idGen.addParam( SequenceHiLoGenerator.MAX_LO, String.valueOf( seqGen.allocationSize() - 1 ) );
351 log.debug( "Add sequence generator with name: " + idGen.getName() );
352 }
353 else if ( ann instanceof GenericGenerator ) {
354 GenericGenerator genGen = (GenericGenerator) ann;
355 idGen.setName( genGen.name() );
356 idGen.setIdentifierGeneratorStrategy( genGen.strategy() );
357 Parameter[] params = genGen.parameters();
358 for ( Parameter parameter : params ) {
359 idGen.addParam( parameter.name(), parameter.value() );
360 }
361 log.debug( "Add generic generator with name: " + idGen.getName() );
362 }
363 else {
364 throw new AssertionFailure( "Unknown Generator annotation: " + ann );
365 }
366 return idGen;
367 }
368
369 /**
370 * Bind a class having JSR175 annotations
371 * The subclasses <b>have to</b> be binded after its mother class
372 */
373 public static void bindClass(
374 XClass clazzToProcess, Map<XClass, InheritanceState> inheritanceStatePerClass, ExtendedMappings mappings
375 ) throws MappingException {
376 //TODO: be more strict with secondarytable allowance (not for ids, not for secondary table join columns etc)
377 InheritanceState inheritanceState = inheritanceStatePerClass.get( clazzToProcess );
378 AnnotatedClassType classType = mappings.getClassType( clazzToProcess );
379 if ( AnnotatedClassType.EMBEDDABLE_SUPERCLASS.equals( classType ) //will be processed by their subentities
380 || AnnotatedClassType.NONE.equals( classType ) //to be ignored
381 || AnnotatedClassType.EMBEDDABLE.equals( classType ) //allow embeddable element declaration
382 ) {
383 if ( AnnotatedClassType.NONE.equals( classType )
384 && clazzToProcess.isAnnotationPresent( org.hibernate.annotations.Entity.class ) ) {
385 log.warn("Class annotated @org.hibernate.annotations.Entity but not javax.persistence.Entity "
386 + "(most likely a user error): " + clazzToProcess.getName() );
387 }
388 return;
389 }
390 if ( !classType.equals( AnnotatedClassType.ENTITY ) ) {
391 //TODO make this test accurate by removing the none elements artifically added
392 throw new AnnotationException(
393 "Annotated class should have a @javax.persistence.Entity, @javax.persistence.Embeddable or @javax.persistence.EmbeddedSuperclass annotation: " + clazzToProcess
394 .getName()
395 );
396 }
397 XAnnotatedElement annotatedClass = clazzToProcess;
398 if ( log.isInfoEnabled() ) log.info( "Binding entity from annotated class: " + clazzToProcess.getName() );
399 InheritanceState superEntityState =
400 InheritanceState.getSuperEntityInheritanceState(
401 clazzToProcess, inheritanceStatePerClass, mappings.getReflectionManager()
402 );
403 PersistentClass superEntity = superEntityState != null ?
404 mappings.getClass(
405 superEntityState.clazz.getName()
406 ) :
407 null;
408 if ( superEntity == null ) {
409 //check if superclass is not a potential persistent class
410 if ( inheritanceState.hasParents ) {
411 throw new AssertionFailure(
412 "Subclass has to be binded after it's mother class: "
413 + superEntityState.clazz.getName()
414 );
415 }
416 }
417 bindQueries( annotatedClass, mappings );
418 bindFilterDefs( annotatedClass, mappings );
419 bindTypeDefs( annotatedClass, mappings );
420
421 String schema = "";
422 String table = ""; //might be no @Table annotation on the annotated class
423 String catalog = "";
424 String discrimValue = null;
425 List<String[]> uniqueConstraints = new ArrayList<String[]>();
426 Ejb3DiscriminatorColumn discriminatorColumn = null;
427 Ejb3JoinColumn[] inheritanceJoinedColumns = null;
428
429 if ( annotatedClass.isAnnotationPresent( javax.persistence.Table.class ) ) {
430 javax.persistence.Table tabAnn = annotatedClass.getAnnotation( javax.persistence.Table.class );
431 table = tabAnn.name();
432 schema = tabAnn.schema();
433 catalog = tabAnn.catalog();
434 uniqueConstraints = TableBinder.buildUniqueConstraints( tabAnn.uniqueConstraints() );
435 }
436 final boolean hasJoinedColumns = inheritanceState.hasParents
437 && InheritanceType.JOINED.equals( inheritanceState.type );
438 if ( hasJoinedColumns ) {
439 //@Inheritance(JOINED) subclass need to link back to the super entity
440 PrimaryKeyJoinColumns jcsAnn = annotatedClass.getAnnotation( PrimaryKeyJoinColumns.class );
441 boolean explicitInheritanceJoinedColumns = jcsAnn != null && jcsAnn.value().length != 0;
442 if ( explicitInheritanceJoinedColumns ) {
443 int nbrOfInhJoinedColumns = jcsAnn.value().length;
444 PrimaryKeyJoinColumn jcAnn;
445 inheritanceJoinedColumns = new Ejb3JoinColumn[nbrOfInhJoinedColumns];
446 for ( int colIndex = 0; colIndex < nbrOfInhJoinedColumns; colIndex++ ) {
447 jcAnn = jcsAnn.value()[colIndex];
448 inheritanceJoinedColumns[colIndex] = Ejb3JoinColumn.buildJoinColumn(
449 jcAnn, null, superEntity.getIdentifier(),
450 (Map<String, Join>) null, (PropertyHolder) null, mappings
451 );
452 }
453 }
454 else {
455 PrimaryKeyJoinColumn jcAnn = annotatedClass.getAnnotation( PrimaryKeyJoinColumn.class );
456 inheritanceJoinedColumns = new Ejb3JoinColumn[1];
457 inheritanceJoinedColumns[0] = Ejb3JoinColumn.buildJoinColumn(
458 jcAnn, null, superEntity.getIdentifier(),
459 (Map<String, Join>) null, (PropertyHolder) null, mappings
460 );
461 }
462 log.debug( "Subclass joined column(s) created" );
463 }
464 else {
465 if ( annotatedClass.isAnnotationPresent( javax.persistence.PrimaryKeyJoinColumns.class )
466 || annotatedClass.isAnnotationPresent( javax.persistence.PrimaryKeyJoinColumn.class ) ) {
467 log.warn( "Root entity should not hold an PrimaryKeyJoinColum(s), will be ignored" );
468 }
469 }
470
471 if ( InheritanceType.SINGLE_TABLE.equals( inheritanceState.type ) ) {
472 javax.persistence.Inheritance inhAnn = annotatedClass.getAnnotation( javax.persistence.Inheritance.class );
473 javax.persistence.DiscriminatorColumn discAnn = annotatedClass.getAnnotation(
474 javax.persistence.DiscriminatorColumn.class
475 );
476 DiscriminatorType discriminatorType = discAnn != null ?
477 discAnn.discriminatorType() :
478 DiscriminatorType.STRING;
479
480 org.hibernate.annotations.DiscriminatorFormula discFormulaAnn = annotatedClass.getAnnotation(
481 org.hibernate.annotations.DiscriminatorFormula.class
482 );
483 if ( !inheritanceState.hasParents ) {
484 discriminatorColumn = Ejb3DiscriminatorColumn.buildDiscriminatorColumn(
485 discriminatorType, discAnn, discFormulaAnn, mappings
486 );
487 }
488 if ( discAnn != null && inheritanceState.hasParents ) {
489 log.warn(
490 "Discriminator column has to be defined in the root entity, it will be ignored in subclass: "
491 + clazzToProcess.getName()
492 );
493 }
494 discrimValue = annotatedClass.isAnnotationPresent( DiscriminatorValue.class ) ?
495 annotatedClass.getAnnotation( DiscriminatorValue.class ).value() :
496 null;
497 }
498
499 //we now know what kind of persistent entity it is
500 PersistentClass persistentClass;
501 //create persistent class
502 if ( !inheritanceState.hasParents ) {
503 persistentClass = new RootClass();
504 }
505 else if ( InheritanceType.SINGLE_TABLE.equals( inheritanceState.type ) ) {
506 persistentClass = new SingleTableSubclass( superEntity );
507 }
508 else if ( InheritanceType.JOINED.equals( inheritanceState.type ) ) {
509 persistentClass = new JoinedSubclass( superEntity );
510 }
511 else if ( InheritanceType.TABLE_PER_CLASS.equals( inheritanceState.type ) ) {
512 persistentClass = new UnionSubclass( superEntity );
513 }
514 else {
515 throw new AssertionFailure( "Unknown inheritance type: " + inheritanceState.type );
516 }
517 Proxy proxyAnn = annotatedClass.getAnnotation( Proxy.class );
518 BatchSize sizeAnn = annotatedClass.getAnnotation( BatchSize.class );
519 Where whereAnn = annotatedClass.getAnnotation( Where.class );
520 Entity entityAnn = annotatedClass.getAnnotation( Entity.class );
521 org.hibernate.annotations.Entity hibEntityAnn = annotatedClass.getAnnotation(
522 org.hibernate.annotations.Entity.class
523 );
524 org.hibernate.annotations.Cache cacheAnn = annotatedClass.getAnnotation(
525 org.hibernate.annotations.Cache.class
526 );
527 EntityBinder entityBinder = new EntityBinder(
528 entityAnn, hibEntityAnn, clazzToProcess, persistentClass, mappings
529 );
530 entityBinder.setDiscriminatorValue( discrimValue );
531 entityBinder.setBatchSize( sizeAnn );
532 entityBinder.setProxy( proxyAnn );
533 entityBinder.setWhere( whereAnn );
534 entityBinder.setCache( cacheAnn );
535 entityBinder.setInheritanceState( inheritanceState );
536 Filter filterAnn = annotatedClass.getAnnotation( Filter.class );
537 if ( filterAnn != null ) {
538 entityBinder.addFilter( filterAnn.name(), filterAnn.condition() );
539 }
540 Filters filtersAnn = annotatedClass.getAnnotation( Filters.class );
541 if ( filtersAnn != null ) {
542 for ( Filter filter : filtersAnn.value() ) {
543 entityBinder.addFilter( filter.name(), filter.condition() );
544 }
545 }
546 entityBinder.bindEntity();
547
548 if ( inheritanceState.hasTable() ) {
549 Check checkAnn = annotatedClass.getAnnotation( Check.class );
550 String constraints = checkAnn == null ?
551 null :
552 checkAnn.constraints();
553 entityBinder.bindTable(
554 schema, catalog, table, uniqueConstraints,
555 constraints, inheritanceState.hasDenormalizedTable() ?
556 superEntity.getTable() :
557 null
558 );
559 }
560 else {
561 if ( annotatedClass.isAnnotationPresent( Table.class ) ) {
562 log.warn( "Illegal use of @Table in a subclass of a SINGLE_TABLE hierarchy: " + clazzToProcess
563 .getName() );
564 }
565 }
566 // Map<String, Column[]> columnOverride = PropertyHolderBuilder.buildHierarchyColumnOverride(
567 // clazzToProcess,
568 // persistentClass.getClassName()
569 // );
570 PropertyHolder propertyHolder = PropertyHolderBuilder.buildPropertyHolder(
571 clazzToProcess,
572 persistentClass,
573 entityBinder, mappings
574 );
575
576 javax.persistence.SecondaryTable secTabAnn = annotatedClass.getAnnotation(
577 javax.persistence.SecondaryTable.class
578 );
579 javax.persistence.SecondaryTables secTabsAnn = annotatedClass.getAnnotation(
580 javax.persistence.SecondaryTables.class
581 );
582 entityBinder.firstLevelSecondaryTablesBinding( secTabAnn, secTabsAnn );
583
584 OnDelete onDeleteAnn = annotatedClass.getAnnotation( OnDelete.class );
585 boolean onDeleteAppropriate = false;
586 if ( InheritanceType.JOINED.equals( inheritanceState.type ) && inheritanceState.hasParents ) {
587 onDeleteAppropriate = true;
588 final JoinedSubclass jsc = (JoinedSubclass) persistentClass;
589 if ( persistentClass.getEntityPersisterClass() == null ) {
590 persistentClass.getRootClass().setEntityPersisterClass( JoinedSubclassEntityPersister.class );
591 }
592 SimpleValue key = new DependantValue( jsc.getTable(), jsc.getIdentifier() );
593 jsc.setKey( key );
594 ForeignKey fk = annotatedClass.getAnnotation( ForeignKey.class );
595 if (fk != null && ! BinderHelper.isDefault( fk.name() ) ) {
596 key.setForeignKeyName( fk.name() );
597 }
598 if ( onDeleteAnn != null ) {
599 key.setCascadeDeleteEnabled( OnDeleteAction.CASCADE.equals( onDeleteAnn.action() ) );
600 }
601 else {
602 key.setCascadeDeleteEnabled( false );
603 }
604 TableBinder.bindFk( jsc.getSuperclass(), jsc, inheritanceJoinedColumns, key, false, mappings );
605 //no need to handle inSecondPass this is an Etntiy related work
606 mappings.addSecondPass( new CreateKeySecondPass( jsc ) );
607
608 }
609 else if ( InheritanceType.SINGLE_TABLE.equals( inheritanceState.type ) ) {
610 if ( inheritanceState.hasParents ) {
611 if ( persistentClass.getEntityPersisterClass() == null ) {
612 persistentClass.getRootClass().setEntityPersisterClass( SingleTableEntityPersister.class );
613 }
614 }
615 else {
616 if ( inheritanceState.hasSons || !discriminatorColumn.isImplicit() ) {
617 //need a discriminator column
618 bindDiscriminatorToPersistentClass(
619 (RootClass) persistentClass,
620 discriminatorColumn,
621 entityBinder.getSecondaryTables(),
622 propertyHolder
623 );
624 entityBinder.bindDiscriminatorValue();//bind it again since the type might have changed
625 }
626 }
627 }
628 else if ( InheritanceType.TABLE_PER_CLASS.equals( inheritanceState.type ) ) {
629 if ( inheritanceState.hasParents ) {
630 if ( persistentClass.getEntityPersisterClass() == null ) {
631 persistentClass.getRootClass().setEntityPersisterClass( UnionSubclassEntityPersister.class );
632 }
633 }
634 }
635 if ( onDeleteAnn != null && !onDeleteAppropriate ) {
636 log.warn(
637 "Inapropriate use of @OnDelete on entity, annotation ignored: " + propertyHolder.getEntityName()
638 );
639 }
640
641 //try to find class level generators
642 HashMap<String, IdGenerator> classGenerators = buildLocalGenerators( annotatedClass, mappings );
643
644 // check properties
645 List<PropertyData> elements =
646 getElementsToProcess(
647 clazzToProcess, inheritanceStatePerClass, propertyHolder, entityBinder, mappings
648 );
649 if ( elements == null ) {
650 throw new AnnotationException( "No identifier specified for entity: " + propertyHolder.getEntityName() );
651 }
652 final boolean subclassAndSingleTableStrategy = inheritanceState.type == InheritanceType.SINGLE_TABLE
653 && inheritanceState.hasParents;
654 //process idclass if any
655 Set<String> idProperties = new HashSet<String>();
656 IdClass idClass = null;
657 if ( !inheritanceState.hasParents ) {
658 //look for idClass
659 XClass current = inheritanceState.clazz;
660 InheritanceState state = inheritanceState;
661 do {
662 current = state.clazz;
663 if ( current.isAnnotationPresent( IdClass.class ) ) {
664 idClass = current.getAnnotation( IdClass.class );
665 break;
666 }
667 state = InheritanceState.getSuperclassInheritanceState(
668 current, inheritanceStatePerClass, mappings.getReflectionManager()
669 );
670 }
671 while ( state != null );
672 }
673 if ( idClass != null ) {
674 XClass compositeClass = mappings.getReflectionManager().toXClass( idClass.value() );
675 boolean isComponent = true;
676 boolean propertyAnnotated = entityBinder.isPropertyAnnotated( compositeClass );
677 String propertyAccessor = entityBinder.getPropertyAccessor( compositeClass );
678 String generatorType = "assigned";
679 String generator = BinderHelper.ANNOTATION_STRING_DEFAULT;
680 PropertyData inferredData = new PropertyPreloadedData(
681 entityBinder.getPropertyAccessor(), "id", compositeClass
682 );
683 HashMap<String, IdGenerator> localGenerators = new HashMap<String, IdGenerator>();
684 boolean ignoreIdAnnotations = entityBinder.isIgnoreIdAnnotations();
685 entityBinder.setIgnoreIdAnnotations( true );
686 bindId(
687 generatorType,
688 generator,
689 inferredData,
690 null,
691 propertyHolder,
692 localGenerators,
693 isComponent,
694 propertyAnnotated,
695 propertyAccessor, entityBinder,
696 null,
697 true,
698 false, mappings
699 );
700 inferredData = new PropertyPreloadedData(
701 propertyAccessor, "_identifierMapper", compositeClass
702 );
703 Component mapper = fillComponent(
704 propertyHolder,
705 inferredData,
706 propertyAnnotated,
707 propertyAccessor, false,
708 entityBinder,
709 true, true,
710 false, mappings
711 );
712 entityBinder.setIgnoreIdAnnotations( ignoreIdAnnotations );
713 persistentClass.setIdentifierMapper( mapper );
714 Property property = new Property();
715 property.setName( "_identifierMapper" );
716 property.setNodeName( "id" );
717 property.setUpdateable( false );
718 property.setInsertable( false );
719 property.setValue( mapper );
720 property.setPropertyAccessorName( "embedded" );
721 persistentClass.addProperty( property );
722 entityBinder.setIgnoreIdAnnotations( true );
723
724 Iterator properties = mapper.getPropertyIterator();
725 while ( properties.hasNext() ) {
726 idProperties.add( ( (Property) properties.next() ).getName() );
727 }
728 }
729 Set<String> missingIdProperties = new HashSet<String>( idProperties );
730 for ( PropertyData propertyAnnotatedElement : elements ) {
731 String propertyName = propertyAnnotatedElement.getPropertyName();
732 if ( !idProperties.contains( propertyName ) ) {
733 processElementAnnotations(
734 propertyHolder,
735 subclassAndSingleTableStrategy ?
736 Nullability.FORCED_NULL :
737 Nullability.NO_CONSTRAINT,
738 propertyAnnotatedElement.getProperty(),
739 propertyAnnotatedElement, classGenerators, entityBinder,
740 false, false, false, mappings
741 );
742 }
743 else {
744 missingIdProperties.remove( propertyName );
745 }
746 }
747
748 if ( missingIdProperties.size() != 0 ) {
749 StringBuilder missings = new StringBuilder();
750 for ( String property : missingIdProperties ) {
751 missings.append( property ).append( ", " );
752 }
753 throw new AnnotationException(
754 "Unable to find properties ("
755 + missings.substring( 0, missings.length() - 2 )
756 + ") in entity annotated with @IdClass:" + persistentClass.getEntityName()
757 );
758 }
759
760 if ( !inheritanceState.hasParents ) {
761 final RootClass rootClass = (RootClass) persistentClass;
762 //no need to handle inSecondPass this is an Entity related work
763 mappings.addSecondPass( new CreateKeySecondPass( rootClass ) );
764 }
765 else {
766 superEntity.addSubclass( (Subclass) persistentClass );
767 }
768
769 mappings.addClass( persistentClass );
770 entityBinder.finalSecondaryTableBinding( propertyHolder );
771
772 //add process complementary Table definition (index & all)
773 entityBinder.processComplementaryTableDefinitions( annotatedClass.getAnnotation( org.hibernate.annotations.Table.class ) );
774 entityBinder.processComplementaryTableDefinitions( annotatedClass.getAnnotation( org.hibernate.annotations.Tables.class ) );
775
776 }
777
778 /**
779 * Get the annotated elements
780 * Guess the annotated element from @Id or @EmbeddedId presence
781 * Change EntityBinder by side effect
782 */
783 private static List<PropertyData> getElementsToProcess(
784 XClass clazzToProcess, Map<XClass, InheritanceState> inheritanceStatePerClass,
785 PropertyHolder propertyHolder, EntityBinder entityBinder, ExtendedMappings mappings
786 ) {
787 InheritanceState inheritanceState = inheritanceStatePerClass.get( clazzToProcess );
788 List<XClass> classesToProcess = orderClassesToBeProcessed(
789 clazzToProcess, inheritanceStatePerClass, inheritanceState, mappings
790 );
791 List<PropertyData> elements = new ArrayList<PropertyData>();
792 int deep = classesToProcess.size();
793 boolean hasIdentifier = false;
794
795 assert !inheritanceState.isEmbeddableSuperclass;
796 Boolean isExplicitPropertyAnnotated = null;
797 String explicitAccessType = null;
798 if ( inheritanceState.hasParents ) {
799 InheritanceState superEntityState =
800 InheritanceState.getSuperEntityInheritanceState(
801 clazzToProcess, inheritanceStatePerClass, mappings.getReflectionManager()
802 );
803 isExplicitPropertyAnnotated = superEntityState != null ?
804 superEntityState.isPropertyAnnotated :
805 null;
806 explicitAccessType = superEntityState != null ?
807 superEntityState.accessType :
808 null;
809 }
810 else {
811 AccessType access = clazzToProcess.getAnnotation( AccessType.class );
812 explicitAccessType = access != null ?
813 access.value() :
814 null;
815 if ( "property".equals( explicitAccessType ) ) {
816 isExplicitPropertyAnnotated = Boolean.TRUE;
817 }
818 else if ( "field".equals( explicitAccessType ) ) {
819 isExplicitPropertyAnnotated = Boolean.FALSE;
820 }
821 }
822 Boolean isPropertyAnnotated = isExplicitPropertyAnnotated == null ?
823 Boolean.TRUE :
824 //default to property and fallback if needed
825 isExplicitPropertyAnnotated;
826 String accessType = explicitAccessType != null ?
827 explicitAccessType :
828 "property";
829
830 for ( int index = 0; index < deep; index++ ) {
831 XClass clazz = classesToProcess.get( index );
832
833 boolean currentHasIdentifier = addElementsOfAClass(
834 elements, propertyHolder, isPropertyAnnotated,
835 accessType, clazz, mappings
836 );
837 hasIdentifier = hasIdentifier || currentHasIdentifier;
838 }
839
840 if ( !hasIdentifier && !inheritanceState.hasParents ) {
841 if ( isExplicitPropertyAnnotated != null ) return null; //explicit but no @Id
842 isPropertyAnnotated = !isPropertyAnnotated;
843 accessType = "field";
844 elements.clear();
845 for ( int index = 0; index < deep; index++ ) {
846 XClass clazz = classesToProcess.get( index );
847 boolean currentHasIdentifier = addElementsOfAClass(
848 elements, propertyHolder, isPropertyAnnotated,
849 accessType, clazz, mappings
850 );
851 hasIdentifier = hasIdentifier || currentHasIdentifier;
852 }
853 }
854 //TODO set the access type here?
855 entityBinder.setPropertyAnnotated( isPropertyAnnotated );
856 entityBinder.setPropertyAccessor( accessType );
857 inheritanceState.isPropertyAnnotated = isPropertyAnnotated;
858 inheritanceState.accessType = accessType;
859 return hasIdentifier || inheritanceState.hasParents ?
860 elements :
861 null;
862 }
863
864 private static List<XClass> orderClassesToBeProcessed(
865 XClass annotatedClass, Map<XClass, InheritanceState> inheritanceStatePerClass,
866 InheritanceState inheritanceState, ExtendedMappings mappings
867 ) {
868 //ordered to allow proper messages on properties subclassing
869 List<XClass> classesToProcess = new ArrayList<XClass>();
870 XClass currentClassInHierarchy = annotatedClass;
871 InheritanceState superclassState;
872 do {
873 classesToProcess.add( 0, currentClassInHierarchy );
874 XClass superClass = currentClassInHierarchy;
875 do {
876 superClass = superClass.getSuperclass();
877 superclassState = inheritanceStatePerClass.get( superClass );
878 }
879 while ( superClass != null && !mappings.getReflectionManager()
880 .equals( superClass, Object.class ) && superclassState == null );
881
882 currentClassInHierarchy = superClass;
883 }
884 while ( superclassState != null && superclassState.isEmbeddableSuperclass );
885
886 return classesToProcess;
887 }
888
889 private static void bindFilterDefs(XAnnotatedElement annotatedElement, ExtendedMappings mappings) {
890 FilterDef defAnn = annotatedElement.getAnnotation( FilterDef.class );
891 FilterDefs defsAnn = annotatedElement.getAnnotation( FilterDefs.class );
892 if ( defAnn != null ) {
893 bindFilterDef( defAnn, mappings );
894 }
895 if ( defsAnn != null ) {
896 for ( FilterDef def : defsAnn.value() ) {
897 bindFilterDef( def, mappings );
898 }
899 }
900 }
901
902 private static void bindFilterDef(FilterDef defAnn, ExtendedMappings mappings) {
903 Map<String, org.hibernate.type.Type> params = new HashMap<String, org.hibernate.type.Type>();
904 for ( ParamDef param : defAnn.parameters() ) {
905 params.put( param.name(), TypeFactory.heuristicType( param.type() ) );
906 }
907 FilterDefinition def = new FilterDefinition( defAnn.name(), defAnn.defaultCondition(), params );
908 if ( log.isInfoEnabled() ) log.info( "Binding filter definition: " + def.getFilterName() );
909 mappings.addFilterDefinition( def );
910 }
911
912 private static void bindTypeDefs(XAnnotatedElement annotatedElement, ExtendedMappings mappings) {
913 TypeDef defAnn = annotatedElement.getAnnotation( TypeDef.class );
914 TypeDefs defsAnn = annotatedElement.getAnnotation( TypeDefs.class );
915 if ( defAnn != null ) {
916 bindTypeDef( defAnn, mappings );
917 }
918 if ( defsAnn != null ) {
919 for ( TypeDef def : defsAnn.value() ) {
920 bindTypeDef( def, mappings );
921 }
922 }
923 }
924
925 private static void bindTypeDef(TypeDef defAnn, ExtendedMappings mappings) {
926 Properties params = new Properties();
927 for ( Parameter param : defAnn.parameters() ) {
928 params.setProperty( param.name(), param.value() );
929 }
930 if ( log.isInfoEnabled() ) log.info( "Binding type definition: " + defAnn.name() );
931 mappings.addTypeDef( defAnn.name(), defAnn.typeClass().getName(), params );
932 }
933
934 private static void bindDiscriminatorToPersistentClass(
935 RootClass rootClass,
936 Ejb3DiscriminatorColumn discriminatorColumn, Map<String, Join> secondaryTables,
937 PropertyHolder propertyHolder
938 ) {
939 if ( rootClass.getDiscriminator() == null ) {
940 if ( discriminatorColumn == null ) {
941 throw new AssertionFailure( "discriminator column should have been built" );
942 }
943 discriminatorColumn.setJoins( secondaryTables );
944 discriminatorColumn.setPropertyHolder( propertyHolder );
945 SimpleValue discrim = new SimpleValue( rootClass.getTable() );
946 rootClass.setDiscriminator( discrim );
947 discriminatorColumn.linkWithValue( discrim );
948 discrim.setTypeName( discriminatorColumn.getDiscriminatorTypeName() );
949 rootClass.setPolymorphic( true );
950 log.debug( "Setting discriminator for entity " + rootClass.getEntityName() );
951 }
952 }
953
954 /**
955 * Add elements of a class
956 */
957 private static boolean addElementsOfAClass(
958 List<PropertyData> elements, PropertyHolder propertyHolder, boolean isPropertyAnnotated,
959 String propertyAccessor, final XClass annotatedClass, ExtendedMappings mappings
960 ) {
961 boolean hasIdentifier = false;
962 AccessType access = annotatedClass.getAnnotation( AccessType.class );
963 String localPropertyAccessor = access != null ?
964 access.value() :
965 null;
966 String accessType = null;
967 if ( "property".equals( localPropertyAccessor ) || "field".equals( localPropertyAccessor ) ) {
968 accessType = localPropertyAccessor;
969 }
970 else {
971 if ( localPropertyAccessor == null ) {
972 localPropertyAccessor = propertyAccessor;
973 }
974
975 if ( isPropertyAnnotated ) {
976 accessType = "property";
977 }
978 else {
979 accessType = "field";
980 }
981 }
982
983 log.debug( "Processing " + propertyHolder.getEntityName() + " " + accessType + " annotation" );
984 List<XProperty> properties = annotatedClass.getDeclaredProperties( accessType );
985 //order so that property are used int he same order when binding native query
986 Collections.sort( properties, new Comparator<XProperty>() {
987 public int compare(XProperty property1, XProperty property2) {
988 return property1.getName().compareTo( property2.getName() );
989 }
990 } );
991 for ( XProperty p : properties ) {
992 if ( !p.isTypeResolved() && !discoverTypeWithoutReflection( p ) && !mustBeSkipped( p, mappings ) ) {
993 throw new AnnotationException(
994 "Property " + StringHelper.qualify( propertyHolder.getEntityName(), p.getName() ) +
995 " has an unbound type and no explicit target entity. Resolve this Generic usage issue" +
996 " or set an explicit target attribute (eg @OneToMany(target=) or use an explicit @Type"
997 );
998 }
999 final boolean currentHasIdentifier = addProperty( p, elements, localPropertyAccessor, mappings );
1000 hasIdentifier = hasIdentifier || currentHasIdentifier;
1001 }
1002 return hasIdentifier;
1003 }
1004
1005 private static boolean discoverTypeWithoutReflection(XProperty p) {
1006 if ( p.isAnnotationPresent( OneToOne.class ) && !p.getAnnotation( OneToOne.class )
1007 .targetEntity()
1008 .equals( void.class ) ) {
1009 return true;
1010 }
1011 else if ( p.isAnnotationPresent( OneToMany.class ) && !p.getAnnotation( OneToMany.class )
1012 .targetEntity()
1013 .equals( void.class ) ) {
1014 return true;
1015 }
1016 else if ( p.isAnnotationPresent( ManyToOne.class ) && !p.getAnnotation( ManyToOne.class )
1017 .targetEntity()
1018 .equals( void.class ) ) {
1019 return true;
1020 }
1021 else if ( p.isAnnotationPresent( ManyToMany.class ) && !p.getAnnotation( ManyToMany.class )
1022 .targetEntity()
1023 .equals( void.class ) ) {
1024 return true;
1025 }
1026 else if ( p.isAnnotationPresent( Type.class ) ) {
1027 return true;
1028 }
1029 else if ( p.isAnnotationPresent( Target.class ) ) {
1030 return true;
1031 }
1032 return false;
1033 }
1034
1035 private static boolean addProperty(
1036 XProperty property, List<PropertyData> annElts,
1037 String propertyAccessor, ExtendedMappings mappings
1038 ) {
1039 boolean hasIdentifier = false;
1040 PropertyData propertyAnnotatedElement = new PropertyInferredData(
1041 property, propertyAccessor,
1042 mappings.getReflectionManager() );
1043 if ( !mustBeSkipped( propertyAnnotatedElement.getProperty(), mappings ) ) {
1044 /*
1045 * put element annotated by @Id in front
1046 * since it has to be parsed before any assoctation by Hibernate
1047 */
1048 final XAnnotatedElement element = propertyAnnotatedElement.getProperty();
1049 if ( element.isAnnotationPresent( Id.class ) || element.isAnnotationPresent( EmbeddedId.class ) ) {
1050 annElts.add( 0, propertyAnnotatedElement );
1051 hasIdentifier = true;
1052 }
1053 else {
1054 annElts.add( propertyAnnotatedElement );
1055 hasIdentifier = false;
1056 }
1057 }
1058 return hasIdentifier;
1059 }
1060
1061 private static boolean mustBeSkipped(XProperty property, ExtendedMappings mappings) {
1062 //TODO make those hardcoded tests more portable (through the bytecode provider?)
1063 return property.isAnnotationPresent( Transient.class )
1064 || "net.sf.cglib.transform.impl.InterceptFieldCallback".equals( property.getType().getName() )
1065 || "org.hibernate.tool.instrument.javassist.FieldHandler".equals( property.getType().getName() );
1066 }
1067
1068 /**
1069 * Process annotation of a particular property
1070 */
1071 private static void processElementAnnotations(
1072 PropertyHolder propertyHolder, Nullability nullability, XProperty property,
1073 PropertyData inferredData, HashMap<String, IdGenerator> classGenerators,
1074 EntityBinder entityBinder, boolean isIdentifierMapper,
1075 boolean isComponentEmbedded, boolean inSecondPass, ExtendedMappings mappings
1076 )
1077 throws MappingException {
1078 /**
1079 * inSecondPass can only be used to apply right away the second pass of a composite-element
1080 * Because it's a value type, there is no bidirectional association, hence second pass
1081 * ordering does not matter
1082 */
1083 Ejb3Column[] columns = null;
1084 Ejb3JoinColumn[] joinColumns = null;
1085 if ( log.isDebugEnabled() ) {
1086 log.debug(
1087 "Processing annotations of " + propertyHolder.getEntityName() + "." + inferredData.getPropertyName()
1088 );
1089 }
1090
1091 if ( property.isAnnotationPresent( Parent.class ) ) {
1092 if ( propertyHolder.isComponent() ) {
1093 propertyHolder.setParentProperty( property.getName() );
1094 }
1095 else {
1096 throw new AnnotationException(
1097 "@Parent cannot be applied outside an embeddable object: "
1098 + StringHelper.qualify( propertyHolder.getPath(), property.getName() )
1099 );
1100 }
1101 return;
1102 }
1103
1104 //process @JoinColumn(s) before @Column(s) to handle collection of elements properly
1105 {
1106 JoinColumn[] anns = null;
1107 if ( property.isAnnotationPresent( JoinColumn.class ) ) {
1108 anns = new JoinColumn[]{property.getAnnotation( JoinColumn.class )};
1109 }
1110 else if ( property.isAnnotationPresent( JoinColumns.class ) ) {
1111 JoinColumns ann = property.getAnnotation( JoinColumns.class );
1112 anns = ann.value();
1113 int length = anns.length;
1114 if ( length == 0 ) {
1115 throw new AnnotationException( "Cannot bind an empty @JoinColumns" );
1116 }
1117 }
1118 if ( anns != null ) {
1119 joinColumns = Ejb3JoinColumn.buildJoinColumns(
1120 anns, null, entityBinder.getSecondaryTables(),
1121 propertyHolder, inferredData.getPropertyName(), mappings
1122 );
1123 }
1124 }
1125 if ( property.isAnnotationPresent( Column.class ) || property.isAnnotationPresent( Formula.class ) ) {
1126 Column ann = property.getAnnotation( Column.class );
1127 Formula formulaAnn = property.getAnnotation( Formula.class );
1128 columns = Ejb3Column.buildColumnFromAnnotation(
1129 new Column[]{ann}, formulaAnn, nullability, propertyHolder, inferredData,
1130 entityBinder.getSecondaryTables(), mappings
1131 );
1132 }
1133 else if ( property.isAnnotationPresent( Columns.class ) ) {
1134 Columns anns = property.getAnnotation( Columns.class );
1135 columns = Ejb3Column.buildColumnFromAnnotation(
1136 anns.columns(), null, nullability, propertyHolder, inferredData, entityBinder.getSecondaryTables(),
1137 mappings
1138 );
1139 }
1140
1141 //set default values if needed
1142 if ( joinColumns == null &&
1143 ( property.isAnnotationPresent( ManyToOne.class )
1144 || property.isAnnotationPresent( OneToOne.class ) )
1145 ) {
1146 if ( property.isAnnotationPresent( JoinTable.class ) ) {
1147 JoinTable joinTableAnn = property.getAnnotation( JoinTable.class );
1148 joinColumns = Ejb3JoinColumn.buildJoinColumns(
1149 joinTableAnn.inverseJoinColumns(), null, entityBinder.getSecondaryTables(),
1150 propertyHolder, inferredData.getPropertyName(), mappings
1151 );
1152 if ( StringHelper.isEmpty( joinTableAnn.name() ) ) {
1153 throw new AnnotationException(
1154 "JoinTable.name() on a @ToOne association has to be explicit: "
1155 + StringHelper.qualify( propertyHolder.getPath(), inferredData.getPropertyName() )
1156 );
1157 }
1158 }
1159 else {
1160 OneToOne oneToOneAnn = property.getAnnotation( OneToOne.class );
1161 String mappedBy = oneToOneAnn != null ?
1162 oneToOneAnn.mappedBy() :
1163 null;
1164 joinColumns = Ejb3JoinColumn.buildJoinColumns(
1165 (JoinColumn[]) null,
1166 mappedBy, entityBinder.getSecondaryTables(),
1167 propertyHolder, inferredData.getPropertyName(), mappings
1168 );
1169 }
1170 }
1171 else if ( joinColumns == null &&
1172 ( property.isAnnotationPresent( OneToMany.class )
1173 || property.isAnnotationPresent( CollectionOfElements.class ) ) ) {
1174 OneToMany oneToMany = property.getAnnotation( OneToMany.class );
1175 String mappedBy = oneToMany != null ?
1176 oneToMany.mappedBy() :
1177 "";
1178 joinColumns = Ejb3JoinColumn.buildJoinColumns(
1179 (JoinColumn[]) null,
1180 mappedBy, entityBinder.getSecondaryTables(),
1181 propertyHolder, inferredData.getPropertyName(), mappings
1182 );
1183 }
1184 if ( columns == null && !property.isAnnotationPresent( ManyToMany.class ) ) {
1185 //useful for collection of embedded elements
1186 columns = Ejb3Column.buildColumnFromAnnotation(
1187 null, null, nullability, propertyHolder, inferredData, entityBinder.getSecondaryTables(), mappings
1188 );
1189 }
1190
1191 if ( nullability == Nullability.FORCED_NOT_NULL ) {
1192 //force columns to not null
1193 for ( Ejb3Column col : columns ) {
1194 col.forceNotNull();
1195 }
1196 }
1197
1198 final XClass returnedClass = inferredData.getClassOrElement();
1199 if ( !entityBinder.isIgnoreIdAnnotations() &&
1200 ( property.isAnnotationPresent( Id.class )
1201 || property.isAnnotationPresent( EmbeddedId.class ) ) ) {
1202 if ( isIdentifierMapper ) {
1203 throw new AnnotationException(
1204 "@IdClass class should not have @Id nor @EmbeddedId properties"
1205 );
1206 }
1207 log.debug( inferredData.getPropertyName() + " is an id" );
1208 //clone classGenerator and override with local values
1209 HashMap<String, IdGenerator> localGenerators = (HashMap<String, IdGenerator>) classGenerators.clone();
1210 localGenerators.putAll( buildLocalGenerators( property, mappings ) );
1211
1212 //manage composite related metadata
1213 //guess if its a component and find id data access (property, field etc)
1214 final boolean isComponent = returnedClass.isAnnotationPresent( Embeddable.class )
1215 || property.isAnnotationPresent( EmbeddedId.class );
1216 boolean propertyAnnotated = entityBinder.isPropertyAnnotated( returnedClass );
1217 String propertyAccessor = entityBinder.getPropertyAccessor( returnedClass );
1218 //if ( isComponent && embeddableAnn != null && embeddableAnn.access() == AccessType.FIELD ) propertyAccess = false;
1219
1220 GeneratedValue generatedValue = property.getAnnotation( GeneratedValue.class );
1221 String generatorType = generatedValue != null ?
1222 generatorType( generatedValue.strategy() ) :
1223 "assigned";
1224 String generator = generatedValue != null ?
1225 generatedValue.generator() :
1226 BinderHelper.ANNOTATION_STRING_DEFAULT;
1227 if ( isComponent ) generatorType = "assigned"; //a component must not have any generator
1228 Type typeAnn = property.getAnnotation( Type.class );
1229 bindId(
1230 generatorType,
1231 generator,
1232 inferredData,
1233 columns,
1234 propertyHolder,
1235 localGenerators,
1236 isComponent,
1237 propertyAnnotated,
1238 propertyAccessor, entityBinder,
1239 typeAnn,
1240 false,
1241 isIdentifierMapper, mappings
1242 );
1243 if ( log.isDebugEnabled() ) {
1244 log.debug(
1245 "Bind " + ( isComponent ?
1246 "@EmbeddedId" :
1247 "@Id" ) + " on " + inferredData.getPropertyName()
1248 );
1249 }
1250 }
1251 else if ( property.isAnnotationPresent( Version.class ) ) {
1252 if ( isIdentifierMapper ) {
1253 throw new AnnotationException(
1254 "@IdClass class should not have @Version property"
1255 );
1256 }
1257 if ( !( propertyHolder.getPersistentClass() instanceof RootClass ) ) {
1258 throw new AnnotationException(
1259 "Unable to define/override @Version on a subclass: "
1260 + propertyHolder.getEntityName()
1261 );
1262 }
1263 log.debug( inferredData.getPropertyName() + " is a version property" );
1264 RootClass rootClass = (RootClass) propertyHolder.getPersistentClass();
1265 boolean lazy = false;
1266 PropertyBinder propBinder = new PropertyBinder();
1267 propBinder.setName( inferredData.getPropertyName() );
1268 propBinder.setReturnedClassName( inferredData.getTypeName() );
1269 propBinder.setLazy( lazy );
1270 propBinder.setPropertyAccessorName( inferredData.getDefaultAccess() );
1271 propBinder.setColumns( columns );
1272 propBinder.setHolder( propertyHolder ); //PropertyHolderBuilder.buildPropertyHolder(rootClass)
1273 propBinder.setProperty( property );
1274 propBinder.setReturnedClass( inferredData.getPropertyClass() );
1275
1276 propBinder.setMappings( mappings );
1277 Property prop = propBinder.bind();
1278 rootClass.setVersion( prop );
1279 SimpleValue simpleValue = (SimpleValue) prop.getValue();
1280 if ( !simpleValue.isTypeSpecified() ) simpleValue.setTypeName( "integer" );
1281 simpleValue.setNullValue( "undefined" );
1282 rootClass.setOptimisticLockMode( Versioning.OPTIMISTIC_LOCK_VERSION );
1283 log.debug(
1284 "Version name: " + rootClass.getVersion().getName() + ", unsavedValue: " + ( (SimpleValue) rootClass
1285 .getVersion()
1286 .getValue() ).getNullValue()
1287 );
1288 }
1289 else if ( property.isAnnotationPresent( ManyToOne.class ) ) {
1290 ManyToOne ann = property.getAnnotation( ManyToOne.class );
1291
1292 //check validity
1293 if ( property.isAnnotationPresent( Column.class )
1294 || property.isAnnotationPresent( Columns.class ) ) {
1295 throw new AnnotationException( "@Column(s) not allowed on a @ManyToOne property: "
1296 + StringHelper.qualify( propertyHolder.getPath(), inferredData.getPropertyName() ) );
1297 }
1298
1299 Cascade hibernateCascade = property.getAnnotation( Cascade.class );
1300 NotFound notFound = property.getAnnotation( NotFound.class );
1301 boolean ignoreNotFound = notFound != null && notFound.action().equals( NotFoundAction.IGNORE );
1302 OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class );
1303 boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() );
1304 JoinTable assocTable = property.getAnnotation( JoinTable.class );
1305 if ( assocTable != null ) {
1306 Join join = propertyHolder.addJoin( assocTable, false );
1307 for ( Ejb3JoinColumn joinColumn : joinColumns ) {
1308 joinColumn.setSecondaryTableName( join.getTable().getName() );
1309 }
1310 }
1311 bindManyToOne(
1312 getCascadeStrategy( ann.cascade(), hibernateCascade ),
1313 joinColumns,
1314 ann.optional(),
1315 ignoreNotFound, onDeleteCascade,
1316 mappings.getReflectionManager().toXClass( ann.targetEntity() ),
1317 propertyHolder,
1318 inferredData, false, isIdentifierMapper, inSecondPass, mappings
1319 );
1320 }
1321 else if ( property.isAnnotationPresent( OneToOne.class ) ) {
1322 OneToOne ann = property.getAnnotation( OneToOne.class );
1323
1324 //check validity
1325 if ( property.isAnnotationPresent( Column.class )
1326 || property.isAnnotationPresent( Columns.class ) ) {
1327 throw new AnnotationException( "@Column(s) not allowed on a @OneToOne property: "
1328 + StringHelper.qualify( propertyHolder.getPath(), inferredData.getPropertyName() ) );
1329 }
1330
1331 //FIXME support a proper PKJCs
1332 boolean trueOneToOne = property.isAnnotationPresent( PrimaryKeyJoinColumn.class )
1333 || property.isAnnotationPresent( PrimaryKeyJoinColumns.class );
1334 Cascade hibernateCascade = property.getAnnotation( Cascade.class );
1335 NotFound notFound = property.getAnnotation( NotFound.class );
1336 boolean ignoreNotFound = notFound != null && notFound.action().equals( NotFoundAction.IGNORE );
1337 OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class );
1338 boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() );
1339 JoinTable assocTable = property.getAnnotation( JoinTable.class );
1340 if ( assocTable != null ) {
1341 Join join = propertyHolder.addJoin( assocTable, false );
1342 for ( Ejb3JoinColumn joinColumn : joinColumns ) {
1343 joinColumn.setSecondaryTableName( join.getTable().getName() );
1344 }
1345 }
1346 bindOneToOne(
1347 getCascadeStrategy( ann.cascade(), hibernateCascade ),
1348 joinColumns,
1349 ann.optional(),
1350 getFetchMode( ann.fetch() ),
1351 ignoreNotFound, onDeleteCascade,
1352 mappings.getReflectionManager().toXClass( ann.targetEntity() ),
1353 propertyHolder,
1354 inferredData, ann.mappedBy(), trueOneToOne, isIdentifierMapper, inSecondPass, mappings
1355 );
1356 }
1357 else if ( property.isAnnotationPresent( OneToMany.class )
1358 || property.isAnnotationPresent( ManyToMany.class )
1359 || property.isAnnotationPresent( CollectionOfElements.class ) ) {
1360 OneToMany oneToManyAnn = property.getAnnotation( OneToMany.class );
1361 ManyToMany manyToManyAnn = property.getAnnotation( ManyToMany.class );
1362 CollectionOfElements collectionOfElementsAnn = property.getAnnotation( CollectionOfElements.class );
1363 org.hibernate.annotations.IndexColumn indexAnn = property.getAnnotation(
1364 org.hibernate.annotations.IndexColumn.class
1365 );
1366 JoinTable assocTable = property.getAnnotation( JoinTable.class );
1367
1368 IndexColumn indexColumn = IndexColumn.buildColumnFromAnnotation(
1369 indexAnn, propertyHolder, inferredData, mappings
1370 );
1371 CollectionBinder collectionBinder = CollectionBinder.getCollectionBinder(
1372 propertyHolder.getEntityName(),
1373 property,
1374 !indexColumn.isImplicit()
1375 );
1376 collectionBinder.setIndexColumn( indexColumn );
1377 MapKey mapKeyAnn = property.getAnnotation( MapKey.class );
1378 collectionBinder.setMapKey( mapKeyAnn );
1379 collectionBinder.setPropertyName( inferredData.getPropertyName() );
1380 BatchSize batchAnn = property.getAnnotation( BatchSize.class );
1381 collectionBinder.setBatchSize( batchAnn );
1382 javax.persistence.OrderBy ejb3OrderByAnn = property.getAnnotation( javax.persistence.OrderBy.class );
1383 OrderBy orderByAnn = property.getAnnotation( OrderBy.class );
1384 collectionBinder.setEjb3OrderBy( ejb3OrderByAnn );
1385 collectionBinder.setSqlOrderBy( orderByAnn );
1386 Sort sortAnn = property.getAnnotation( Sort.class );
1387 collectionBinder.setSort( sortAnn );
1388 Cache cachAnn = property.getAnnotation( Cache.class );
1389 collectionBinder.setCache( cachAnn );
1390 collectionBinder.setPropertyHolder( propertyHolder );
1391 Cascade hibernateCascade = property.getAnnotation( Cascade.class );
1392 NotFound notFound = property.getAnnotation( NotFound.class );
1393 boolean ignoreNotFound = notFound != null && notFound.action().equals( NotFoundAction.IGNORE );
1394 collectionBinder.setIgnoreNotFound( ignoreNotFound );
1395 collectionBinder.setCollectionType( inferredData.getProperty().getElementClass() );
1396 collectionBinder.setMappings( mappings );
1397 collectionBinder.setPropertyAccessorName( inferredData.getDefaultAccess() );
1398
1399 Ejb3Column[] elementColumns = null;
1400 PropertyData virtualProperty = new WrappedInferredData( inferredData, "element" );
1401 if ( property.isAnnotationPresent( Column.class ) || property.isAnnotationPresent(
1402 Formula.class
1403 ) ) {
1404 Column ann = property.getAnnotation( Column.class );
1405 Formula formulaAnn = property.getAnnotation( Formula.class );
1406 elementColumns = Ejb3Column.buildColumnFromAnnotation(
1407 new Column[]{ann},
1408 formulaAnn,
1409 nullability,
1410 propertyHolder,
1411 virtualProperty,
1412 entityBinder.getSecondaryTables(),
1413 mappings
1414 );
1415 }
1416 else if ( property.isAnnotationPresent( Columns.class ) ) {
1417 Columns anns = property.getAnnotation( Columns.class );
1418 elementColumns = Ejb3Column.buildColumnFromAnnotation(
1419 anns.columns(), null, nullability, propertyHolder, virtualProperty,
1420 entityBinder.getSecondaryTables(), mappings
1421 );
1422 }
1423 else {
1424 elementColumns = Ejb3Column.buildColumnFromAnnotation(
1425 null,
1426 null,
1427 nullability,
1428 propertyHolder,
1429 virtualProperty,
1430 entityBinder.getSecondaryTables(),
1431 mappings
1432 );
1433 }
1434
1435 org.hibernate.annotations.MapKey hibMapKeyAnn = property.getAnnotation(
1436 org.hibernate.annotations.MapKey.class
1437 );
1438 PropertyData mapKeyVirtualProperty = new WrappedInferredData( inferredData, "mapkey" );
1439 Ejb3Column[] mapColumns = Ejb3Column.buildColumnFromAnnotation(
1440 hibMapKeyAnn != null && hibMapKeyAnn.columns().length > 0 ?
1441 hibMapKeyAnn.columns() :
1442 null,
1443 null,
1444 Nullability.FORCED_NOT_NULL,
1445 propertyHolder,
1446 mapKeyVirtualProperty,
1447 entityBinder.getSecondaryTables(),
1448 mappings
1449 );
1450 collectionBinder.setMapKeyColumns( mapColumns );
1451
1452 MapKeyManyToMany mapKeyManyToMany = property.getAnnotation( MapKeyManyToMany.class );
1453 Ejb3JoinColumn[] mapJoinColumns = Ejb3JoinColumn.buildJoinColumns(
1454 mapKeyManyToMany != null ?
1455 mapKeyManyToMany.joinColumns() :
1456 null,
1457 null, entityBinder.getSecondaryTables(),
1458 propertyHolder, mapKeyVirtualProperty.getPropertyName(), mappings
1459 );
1460 collectionBinder.setMapKeyManyToManyColumns( mapJoinColumns );
1461
1462 //potential element
1463 collectionBinder.setEmbedded( property.isAnnotationPresent( Embedded.class ) );
1464 collectionBinder.setElementColumns( elementColumns );
1465 collectionBinder.setProperty( property );
1466
1467 if ( oneToManyAnn != null && manyToManyAnn != null ) {
1468 throw new AnnotationException(
1469 "@OneToMany and @ManyToMany on the same property is not allowed: "
1470 + propertyHolder.getEntityName() + "." + inferredData.getPropertyName()
1471 );
1472 }
1473 String mappedBy = null;
1474 if ( oneToManyAnn != null ) {
1475 for ( Ejb3JoinColumn column : joinColumns ) {
1476 if ( column.isSecondary() ) {
1477 throw new NotYetImplementedException( "Collections having FK in secondary table" );
1478 }
1479 }
1480 collectionBinder.setFkJoinColumns( joinColumns );
1481 mappedBy = oneToManyAnn.mappedBy();
1482 collectionBinder.setTargetEntity(
1483 mappings.getReflectionManager().toXClass( oneToManyAnn.targetEntity() )
1484 );
1485 collectionBinder.setCascadeStrategy( getCascadeStrategy( oneToManyAnn.cascade(), hibernateCascade ) );
1486 collectionBinder.setOneToMany( true );
1487 }
1488 else if ( collectionOfElementsAnn != null ) {
1489 for ( Ejb3JoinColumn column : joinColumns ) {
1490 if ( column.isSecondary() ) {
1491 throw new NotYetImplementedException( "Collections having FK in secondary table" );
1492 }
1493 }
1494 collectionBinder.setFkJoinColumns( joinColumns );
1495 mappedBy = "";
1496 collectionBinder.setTargetEntity(
1497 mappings.getReflectionManager().toXClass( collectionOfElementsAnn.targetElement() )
1498 );
1499 //collectionBinder.setCascadeStrategy( getCascadeStrategy( embeddedCollectionAnn.cascade(), hibernateCascade ) );
1500 collectionBinder.setOneToMany( true );
1501 }
1502 else if ( manyToManyAnn != null ) {
1503 mappedBy = manyToManyAnn.mappedBy();
1504 collectionBinder.setTargetEntity(
1505 mappings.getReflectionManager().toXClass( manyToManyAnn.targetEntity() )
1506 );
1507 collectionBinder.setCascadeStrategy( getCascadeStrategy( manyToManyAnn.cascade(), hibernateCascade ) );
1508 collectionBinder.setOneToMany( false );
1509 }
1510 collectionBinder.setMappedBy( mappedBy );
1511 bindJoinedTableAssociation(
1512 assocTable, mappings, entityBinder, collectionBinder, propertyHolder, inferredData, mappedBy
1513 );
1514
1515 OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class );
1516 boolean onDeleteCascade = onDeleteAnn != null && OnDeleteAction.CASCADE.equals( onDeleteAnn.action() );
1517 collectionBinder.setCascadeDeleteEnabled( onDeleteCascade );
1518 if ( isIdentifierMapper ) {
1519 collectionBinder.setInsertable( false );
1520 collectionBinder.setUpdatable( false );
1521 }
1522 if ( property.isAnnotationPresent( CollectionId.class ) ) { //do not compute the generators unless necessary
1523 HashMap<String, IdGenerator> localGenerators = (HashMap<String, IdGenerator>) classGenerators.clone();
1524 localGenerators.putAll( buildLocalGenerators( property, mappings ) );
1525 collectionBinder.setLocalGenerators( localGenerators );
1526
1527 }
1528 collectionBinder.bind();
1529
1530 }
1531 else {
1532 //define whether the type is a component or not
1533 boolean isComponent = false;
1534 Embeddable embeddableAnn = (Embeddable) returnedClass.getAnnotation( Embeddable.class );
1535 Embedded embeddedAnn = (Embedded) property.getAnnotation( Embedded.class );
1536 isComponent = embeddedAnn != null || embeddableAnn != null;
1537
1538 if ( isComponent ) {
1539 //process component object
1540 //boolean propertyAccess = true;
1541 //if ( embeddableAnn != null && embeddableAnn.access() == AccessType.FIELD ) propertyAccess = false;
1542 boolean propertyAnnotated = entityBinder.isPropertyAnnotated( property );
1543 String propertyAccessor = entityBinder.getPropertyAccessor( property );
1544 bindComponent(
1545 inferredData, propertyHolder, propertyAnnotated, propertyAccessor, entityBinder,
1546 isIdentifierMapper,
1547 mappings, isComponentEmbedded
1548 );
1549 }
1550 else {
1551 //provide the basic property mapping
1552 boolean optional = true;
1553 boolean lazy = false;
1554 if ( property.isAnnotationPresent( Basic.class ) ) {
1555 Basic ann = property.getAnnotation( Basic.class );
1556 optional = ann.optional();
1557 lazy = ann.fetch() == FetchType.LAZY;
1558 }
1559 //implicit type will check basic types and Serializable classes
1560 if ( !optional && nullability != Nullability.FORCED_NULL ) {
1561 //force columns to not null
1562 for ( Ejb3Column col : columns ) {
1563 col.forceNotNull();
1564 }
1565 }
1566
1567 PropertyBinder propBinder = new PropertyBinder();
1568 propBinder.setName( inferredData.getPropertyName() );
1569 propBinder.setReturnedClassName( inferredData.getTypeName() );
1570 propBinder.setLazy( lazy );
1571 propBinder.setPropertyAccessorName( inferredData.getDefaultAccess() );
1572 propBinder.setColumns( columns );
1573 propBinder.setHolder( propertyHolder );
1574 propBinder.setProperty( property );
1575 propBinder.setReturnedClass( inferredData.getPropertyClass() );
1576 propBinder.setMappings( mappings );
1577 if ( isIdentifierMapper ) {
1578 propBinder.setInsertable( false );
1579 propBinder.setUpdatable( false );
1580 }
1581 propBinder.bind();
1582 }
1583 }
1584 //init index
1585 //process indexes after everything: in second pass, many to one has to be done before indexes
1586 Index index = property.getAnnotation( Index.class );
1587 if ( index != null ) {
1588 if ( joinColumns != null ) {
1589 for ( Ejb3Column column : joinColumns ) {
1590 column.addIndex( index, inSecondPass );
1591 }
1592 }
1593 else {
1594 for ( Ejb3Column column : columns ) {
1595 column.addIndex( index, inSecondPass );
1596 }
1597 }
1598 }
1599 }
1600
1601 //TODO move that to collection binder?
1602 private static void bindJoinedTableAssociation(
1603 JoinTable joinTableAnn, ExtendedMappings mappings, EntityBinder entityBinder,
1604 CollectionBinder collectionBinder, PropertyHolder propertyHolder, PropertyData inferredData,
1605 String mappedBy
1606 ) {
1607 TableBinder associationTableBinder = new TableBinder();
1608 JoinColumn[] annJoins;
1609 JoinColumn[] annInverseJoins;
1610 if ( joinTableAnn != null ) {
1611 collectionBinder.setExplicitAssociationTable( true );
1612 if ( !BinderHelper.isDefault( joinTableAnn.schema() ) )
1613 associationTableBinder.setSchema( joinTableAnn.schema() );
1614 if ( !BinderHelper.isDefault( joinTableAnn.catalog() ) )
1615 associationTableBinder.setCatalog( joinTableAnn.catalog() );
1616 if ( !BinderHelper.isDefault( joinTableAnn.name() ) ) associationTableBinder.setName( joinTableAnn.name() );
1617 associationTableBinder.setUniqueConstraints( joinTableAnn.uniqueConstraints() );
1618
1619 //set check constaint in the second pass
1620
1621 JoinColumn[] joins = joinTableAnn.joinColumns();
1622
1623 if ( joins.length == 0 ) {
1624 annJoins = null;
1625 }
1626 else {
1627 annJoins = joins;
1628 }
1629
1630 JoinColumn[] inverseJoins = joinTableAnn.inverseJoinColumns();
1631
1632 if ( inverseJoins.length == 0 ) {
1633 annInverseJoins = null;
1634 }
1635 else {
1636 annInverseJoins = inverseJoins;
1637 }
1638 }
1639 else {
1640 annJoins = null;
1641 annInverseJoins = null;
1642 }
1643 Ejb3JoinColumn[] joinColumns = Ejb3JoinColumn.buildJoinTableJoinColumns(
1644 annJoins, entityBinder.getSecondaryTables(), propertyHolder, inferredData.getPropertyName(), mappedBy,
1645 mappings
1646 );
1647 Ejb3JoinColumn[] inverseJoinColumns = Ejb3JoinColumn.buildJoinTableJoinColumns(
1648 annInverseJoins, entityBinder.getSecondaryTables(), propertyHolder, inferredData.getPropertyName(),
1649 mappedBy, mappings
1650 );
1651 associationTableBinder.setMappings( mappings );
1652 collectionBinder.setTableBinder( associationTableBinder );
1653 collectionBinder.setJoinColumns( joinColumns );
1654 collectionBinder.setInverseJoinColumns( inverseJoinColumns );
1655 }
1656
1657 private static void bindComponent(
1658 PropertyData inferredData,
1659 PropertyHolder propertyHolder,
1660 boolean propertyAnnotated,
1661 String propertyAccessor, EntityBinder entityBinder,
1662 boolean isIdentifierMapper,
1663 ExtendedMappings mappings, boolean isComponentEmbedded
1664 ) {
1665 Component comp = fillComponent(
1666 propertyHolder, inferredData, propertyAnnotated, propertyAccessor, true, entityBinder,
1667 isComponentEmbedded, isIdentifierMapper,
1668 false, mappings
1669 );
1670 XProperty property = inferredData.getProperty();
1671 setupComponentTuplizer( property, comp );
1672
1673 PropertyBinder binder = new PropertyBinder();
1674 binder.setName( inferredData.getPropertyName() );
1675 binder.setValue( comp );
1676 binder.setProperty( inferredData.getProperty() );
1677 binder.setPropertyAccessorName( inferredData.getDefaultAccess() );
1678 Property prop = binder.make();
1679 propertyHolder.addProperty( prop );
1680 }
1681
1682 public static Component fillComponent(
1683 PropertyHolder propertyHolder, PropertyData inferredData,
1684 boolean propertyAnnotated, String propertyAccessor, boolean isNullable,
1685 EntityBinder entityBinder,
1686 boolean isComponentEmbedded, boolean isIdentifierMapper, boolean inSecondPass, ExtendedMappings mappings
1687 ) {
1688 /**
1689 * inSecondPass can only be used to apply right away the second pass of a composite-element
1690 * Because it's a value type, there is no bidirectional association, hence second pass
1691 * ordering does not matter
1692 */
1693 Component comp = new Component( propertyHolder.getPersistentClass() );
1694 comp.setEmbedded( isComponentEmbedded );
1695 //yuk
1696 comp.setTable( propertyHolder.getTable() );
1697 if ( !isIdentifierMapper ) {
1698 comp.setComponentClassName( inferredData.getClassOrElementName() );
1699 }
1700 else {
1701 comp.setComponentClassName( comp.getOwner().getClassName() );
1702 }
1703 comp.setNodeName( inferredData.getPropertyName() );
1704 String subpath = StringHelper.qualify( propertyHolder.getPath(), inferredData.getPropertyName() );
1705 log.debug( "Binding component with path: " + subpath );
1706 PropertyHolder subHolder = PropertyHolderBuilder.buildPropertyHolder(
1707 comp, subpath,
1708 inferredData, propertyHolder, mappings
1709 );
1710 List<PropertyData> classElements = new ArrayList<PropertyData>();
1711 XClass returnedClassOrElement = inferredData.getClassOrElement();
1712 addElementsOfAClass(
1713 classElements,
1714 subHolder,
1715 propertyAnnotated,
1716 propertyAccessor, returnedClassOrElement, mappings
1717 );
1718 //add elements of the embeddable superclass
1719 XClass superClass = inferredData.getPropertyClass().getSuperclass();
1720 while ( superClass != null && superClass.isAnnotationPresent( MappedSuperclass.class ) ) {
1721 //FIXME: proper support of typevariables incl var resolved at upper levels
1722 addElementsOfAClass(
1723 classElements,
1724 subHolder,
1725 entityBinder.isPropertyAnnotated( superClass ),
1726 propertyAccessor, superClass, mappings
1727 );
1728 superClass = superClass.getSuperclass();
1729 }
1730 for ( PropertyData propertyAnnotatedElement : classElements ) {
1731 processElementAnnotations(
1732 subHolder, isNullable ?
1733 Nullability.NO_CONSTRAINT :
1734 Nullability.FORCED_NOT_NULL,
1735 propertyAnnotatedElement.getProperty(), propertyAnnotatedElement,
1736 new HashMap<String, IdGenerator>(), entityBinder, isIdentifierMapper, isComponentEmbedded,
1737 inSecondPass, mappings
1738 );
1739 }
1740 return comp;
1741 }
1742
1743 private static void bindId(
1744 String generatorType, String generatorName,
1745 PropertyData inferredData, Ejb3Column[] columns, PropertyHolder propertyHolder,
1746 Map<String, IdGenerator> localGenerators,
1747 boolean isComposite,
1748 boolean isPropertyAnnotated,
1749 String propertyAccessor, EntityBinder entityBinder, Type typeAnn, boolean isEmbedded,
1750 boolean isIdentifierMapper, ExtendedMappings mappings
1751 ) {
1752 /*
1753 * Fill simple value and property since and Id is a property
1754 */
1755 PersistentClass persistentClass = propertyHolder.getPersistentClass();
1756 if ( !( persistentClass instanceof RootClass ) ) {
1757 throw new AnnotationException(
1758 "Unable to define/override @Id(s) on a subclass: "
1759 + propertyHolder.getEntityName()
1760 );
1761 }
1762 RootClass rootClass = (RootClass) persistentClass;
1763 String persistentClassName = rootClass == null ?
1764 null :
1765 rootClass.getClassName();
1766 SimpleValue id;
1767 if ( isComposite ) {
1768 id = fillComponent(
1769 propertyHolder, inferredData, isPropertyAnnotated, propertyAccessor,
1770 false, entityBinder, isEmbedded, isIdentifierMapper, false, mappings
1771 );
1772 Component componentId = (Component) id;
1773 componentId.setKey( true );
1774 if ( componentId.getPropertySpan() == 0 ) {
1775 throw new AnnotationException( componentId.getComponentClassName() + " has no persistent id property" );
1776 }
1777 //tuplizers
1778 XProperty property = inferredData.getProperty();
1779 setupComponentTuplizer( property, componentId );
1780 }
1781 else {
1782 for ( Ejb3Column column : columns ) {
1783 column.forceNotNull(); //this is an id
1784 }
1785 SimpleValueBinder value = new SimpleValueBinder();
1786 value.setPropertyName( inferredData.getPropertyName() );
1787 value.setReturnedClassName( inferredData.getTypeName() );
1788 value.setColumns( columns );
1789 value.setPersistentClassName( persistentClassName );
1790 value.setMappings( mappings );
1791 value.setExplicitType( typeAnn );
1792 id = value.make();
1793 }
1794 rootClass.setIdentifier( id );
1795 BinderHelper.makeIdGenerator( id, generatorType, generatorName, mappings, localGenerators );
1796 if ( isEmbedded ) {
1797 rootClass.setEmbeddedIdentifier( inferredData.getPropertyClass() == null );
1798 }
1799 else {
1800 PropertyBinder binder = new PropertyBinder();
1801 binder.setName( inferredData.getPropertyName() );
1802 binder.setValue( id );
1803 binder.setPropertyAccessorName( inferredData.getDefaultAccess() );
1804 binder.setProperty( inferredData.getProperty() );
1805 Property prop = binder.make();
1806 rootClass.setIdentifierProperty( prop );
1807 }
1808 }
1809
1810 private static void setupComponentTuplizer(XProperty property, Component component) {
1811 if ( property == null ) return;
1812 if ( property.isAnnotationPresent( Tuplizers.class ) ) {
1813 for ( Tuplizer tuplizer : property.getAnnotation( Tuplizers.class ).value() ) {
1814 EntityMode mode = EntityMode.parse( tuplizer.entityMode() );
1815 component.addTuplizer( mode, tuplizer.impl().getName() );
1816 }
1817 }
1818 if ( property.isAnnotationPresent( Tuplizer.class ) ) {
1819 Tuplizer tuplizer = property.getAnnotation( Tuplizer.class );
1820 EntityMode mode = EntityMode.parse( tuplizer.entityMode() );
1821 component.addTuplizer( mode, tuplizer.impl().getName() );
1822 }
1823 }
1824
1825 private static void bindManyToOne(
1826 String cascadeStrategy, Ejb3JoinColumn[] columns, boolean optional,
1827 boolean ignoreNotFound, boolean cascadeOnDelete,
1828 XClass targetEntity, PropertyHolder propertyHolder,
1829 PropertyData inferredData, boolean unique, boolean isIdentifierMapper, boolean inSecondPass,
1830 ExtendedMappings mappings
1831 ) {
1832 //All FK columns should be in the same table
1833 org.hibernate.mapping.ManyToOne value = new org.hibernate.mapping.ManyToOne( columns[0].getTable() );
1834 if ( isDefault( targetEntity, mappings ) ) {
1835 value.setReferencedEntityName( inferredData.getClassOrElementName() );
1836 }
1837 else {
1838 value.setReferencedEntityName( targetEntity.getName() );
1839 }
1840 defineFetchingStrategy( value, inferredData.getProperty() );
1841 //value.setFetchMode( fetchMode );
1842 value.setIgnoreNotFound( ignoreNotFound );
1843 value.setCascadeDeleteEnabled( cascadeOnDelete );
1844 //value.setLazy( fetchMode != FetchMode.JOIN );
1845 if ( !optional ) {
1846 for ( Ejb3JoinColumn column : columns ) {
1847 column.setNullable( false );
1848 }
1849 }
1850 value.setTypeName( inferredData.getClassOrElementName() );
1851 final String propertyName = inferredData.getPropertyName();
1852 value.setTypeUsingReflection( propertyHolder.getClassName(), propertyName );
1853
1854 ForeignKey fk = inferredData.getProperty().getAnnotation( ForeignKey.class );
1855 String fkName = fk != null ?
1856 fk.name() :
1857 "";
1858 if ( !BinderHelper.isDefault( fkName ) ) value.setForeignKeyName( fkName );
1859
1860 String path = propertyHolder.getPath() + "." + propertyName;
1861 FkSecondPass secondPass = new FkSecondPass(
1862 value, columns,
1863 !optional && unique, //cannot have nullabe and unique on certain DBs like Derby
1864 propertyHolder.getEntityOwnerClassName(),
1865 path, mappings
1866 );
1867 if ( inSecondPass ) {
1868 secondPass.doSecondPass( mappings.getClasses() );
1869 }
1870 else {
1871 mappings.addSecondPass(
1872 secondPass
1873 );
1874 }
1875 Ejb3Column.checkPropertyConsistency( columns, propertyHolder.getEntityName() + propertyName );
1876 PropertyBinder binder = new PropertyBinder();
1877 binder.setName( propertyName );
1878 binder.setValue( value );
1879 //binder.setCascade(cascadeStrategy);
1880 if ( isIdentifierMapper ) {
1881 binder.setInsertable( false );
1882 binder.setInsertable( false );
1883 }
1884 else {
1885 binder.setInsertable( columns[0].isInsertable() );
1886 binder.setUpdatable( columns[0].isUpdatable() );
1887 }
1888 binder.setPropertyAccessorName( inferredData.getDefaultAccess() );
1889 binder.setCascade( cascadeStrategy );
1890 Property prop = binder.make();
1891 //composite FK columns are in the same table so its OK
1892 propertyHolder.addProperty( prop, columns );
1893 }
1894
1895 protected static void defineFetchingStrategy(ToOne toOne, XProperty property) {
1896 LazyToOne lazy = property.getAnnotation( LazyToOne.class );
1897 Fetch fetch = property.getAnnotation( Fetch.class );
1898 ManyToOne manyToOne = property.getAnnotation( ManyToOne.class );
1899 OneToOne oneToOne = property.getAnnotation( OneToOne.class );
1900 FetchType fetchType;
1901 if ( manyToOne != null ) {
1902 fetchType = manyToOne.fetch();
1903 }
1904 else if ( oneToOne != null ) {
1905 fetchType = oneToOne.fetch();
1906 }
1907 else {
1908 throw new AssertionFailure(
1909 "Define fetch strategy on a property not annotated with @OneToMany nor @OneToOne"
1910 );
1911 }
1912 if ( lazy != null ) {
1913 toOne.setLazy( !( lazy.value() == LazyToOneOption.FALSE ) );
1914 toOne.setUnwrapProxy( ( lazy.value() == LazyToOneOption.NO_PROXY ) );
1915 }
1916 else {
1917 toOne.setLazy( fetchType == FetchType.LAZY );
1918 toOne.setUnwrapProxy( false );
1919 }
1920 if ( fetch != null ) {
1921 if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) {
1922 toOne.setFetchMode( FetchMode.JOIN );
1923 toOne.setLazy( false );
1924 toOne.setUnwrapProxy( false );
1925 }
1926 else if ( fetch.value() == org.hibernate.annotations.FetchMode.SELECT ) {
1927 toOne.setFetchMode( FetchMode.SELECT );
1928 }
1929 else if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) {
1930 throw new AnnotationException( "Use of FetchMode.SUBSELECT not allowed on ToOne associations" );
1931 }
1932 else {
1933 throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() );
1934 }
1935 }
1936 else {
1937 toOne.setFetchMode( getFetchMode( fetchType ) );
1938 }
1939 }
1940
1941 private static void bindOneToOne(
1942 String cascadeStrategy,
1943 Ejb3JoinColumn[] joinColumns,
1944 boolean optional,
1945 FetchMode fetchMode,
1946 boolean ignoreNotFound,
1947 boolean cascadeOnDelete,
1948 XClass targetEntity,
1949 PropertyHolder propertyHolder,
1950 PropertyData inferredData, String mappedBy,
1951 boolean trueOneToOne,
1952 boolean isIdentifierMapper, boolean inSecondPass, ExtendedMappings mappings
1953 ) {
1954 //column.getTable() => persistentClass.getTable()
1955 final String propertyName = inferredData.getPropertyName();
1956 log.debug( "Fetching " + propertyName + " with " + fetchMode );
1957 boolean mapToPK = true;
1958 if ( !trueOneToOne ) {
1959 //try to find a hidden true one to one (FK == PK columns)
1960 Iterator idColumns = propertyHolder.getIdentifier().getColumnIterator();
1961 List<String> idColumnNames = new ArrayList<String>();
1962 org.hibernate.mapping.Column currentColumn;
1963 while ( idColumns.hasNext() ) {
1964 currentColumn = (org.hibernate.mapping.Column) idColumns.next();
1965 idColumnNames.add( currentColumn.getName() );
1966 }
1967 for ( Ejb3JoinColumn col : joinColumns ) {
1968 if ( !idColumnNames.contains( col.getMappingColumn().getName() ) ) {
1969 mapToPK = false;
1970 break;
1971 }
1972 }
1973 }
1974 if ( trueOneToOne || mapToPK || !BinderHelper.isDefault( mappedBy ) ) {
1975 //is a true one-to-one
1976 //FIXME referencedColumnName ignored => ordering may fail.
1977 OneToOneSecondPass secondPass = new OneToOneSecondPass(
1978 mappedBy,
1979 propertyHolder.getEntityName(),
1980 propertyName,
1981 propertyHolder, inferredData, targetEntity, ignoreNotFound, cascadeOnDelete,
1982 optional, cascadeStrategy, joinColumns, mappings
1983 );
1984 if ( inSecondPass ) {
1985 secondPass.doSecondPass( mappings.getClasses() );
1986 }
1987 else {
1988 mappings.addSecondPass(
1989 secondPass, BinderHelper.isDefault( mappedBy )
1990 );
1991 }
1992 }
1993 else {
1994 //has a FK on the table
1995 bindManyToOne(
1996 cascadeStrategy, joinColumns, optional, ignoreNotFound, cascadeOnDelete,
1997 targetEntity,
1998 propertyHolder, inferredData, true, isIdentifierMapper, inSecondPass, mappings
1999 );
2000 }
2001 }
2002
2003 private static String generatorType(GenerationType generatorEnum) {
2004 switch (generatorEnum) {
2005 case IDENTITY:
2006 return "identity";
2007 case AUTO:
2008 return "native";
2009 case TABLE:
2010 return MultipleHiLoPerTableGenerator.class.getName();
2011 case SEQUENCE:
2012 return "seqhilo";
2013 }
2014 throw new AssertionFailure( "Unknown GeneratorType: " + generatorEnum );
2015 }
2016
2017 private static EnumSet<CascadeType> convertToHibernateCascadeType(javax.persistence.CascadeType[] ejbCascades) {
2018 EnumSet<CascadeType> hibernateCascadeSet = EnumSet.noneOf( CascadeType.class );
2019 if ( ejbCascades != null && ejbCascades.length > 0 ) {
2020 for ( javax.persistence.CascadeType cascade : ejbCascades ) {
2021 switch (cascade) {
2022 case ALL:
2023 hibernateCascadeSet.add( CascadeType.ALL );
2024 break;
2025 case PERSIST:
2026 hibernateCascadeSet.add( CascadeType.PERSIST );
2027 break;
2028 case MERGE:
2029 hibernateCascadeSet.add( CascadeType.MERGE );
2030 break;
2031 case REMOVE:
2032 hibernateCascadeSet.add( CascadeType.REMOVE );
2033 break;
2034 case REFRESH:
2035 hibernateCascadeSet.add( CascadeType.REFRESH );
2036 break;
2037 }
2038 }
2039 }
2040
2041 return hibernateCascadeSet;
2042 }
2043
2044 private static String getCascadeStrategy(
2045 javax.persistence.CascadeType[] ejbCascades, Cascade hibernateCascadeAnnotation
2046 ) {
2047 EnumSet<CascadeType> hibernateCascadeSet = convertToHibernateCascadeType( ejbCascades );
2048 CascadeType[] hibernateCascades = hibernateCascadeAnnotation == null ?
2049 null :
2050 hibernateCascadeAnnotation.value();
2051
2052 if ( hibernateCascades != null && hibernateCascades.length > 0 ) {
2053 for ( CascadeType cascadeType : hibernateCascades ) {
2054 hibernateCascadeSet.add( cascadeType );
2055 }
2056 }
2057
2058 StringBuilder cascade = new StringBuilder();
2059 Iterator<CascadeType> cascadeType = hibernateCascadeSet.iterator();
2060 while ( cascadeType.hasNext() ) {
2061 switch (cascadeType.next()) {
2062 case ALL:
2063 cascade.append( "," ).append( "all" );
2064 break;
2065 case SAVE_UPDATE:
2066 cascade.append( "," ).append( "save-update" );
2067 break;
2068 case PERSIST:
2069 cascade.append( "," ).append( "persist" );
2070 break;
2071 case MERGE:
2072 cascade.append( "," ).append( "merge" );
2073 break;
2074 case LOCK:
2075 cascade.append( "," ).append( "lock" );
2076 break;
2077 case REFRESH:
2078 cascade.append( "," ).append( "refresh" );
2079 break;
2080 case REPLICATE:
2081 cascade.append( "," ).append( "replicate" );
2082 break;
2083 case EVICT:
2084 cascade.append( "," ).append( "evict" );
2085 break;
2086 case DELETE:
2087 cascade.append( "," ).append( "delete" );
2088 break;
2089 case DELETE_ORPHAN:
2090 cascade.append( "," ).append( "delete-orphan" );
2091 break;
2092 case REMOVE:
2093 cascade.append( "," ).append( "delete" );
2094 break;
2095 }
2096 }
2097 return cascade.length() > 0 ?
2098 cascade.substring( 1 ) :
2099 "none";
2100 }
2101
2102 public static FetchMode getFetchMode(FetchType fetch) {
2103 if ( fetch == FetchType.EAGER ) {
2104 return FetchMode.JOIN;
2105 }
2106 else {
2107 return FetchMode.SELECT;
2108 }
2109 }
2110
2111 private static HashMap<String, IdGenerator> buildLocalGenerators(XAnnotatedElement annElt, Mappings mappings) {
2112 HashMap<String, IdGenerator> generators = new HashMap<String, IdGenerator>();
2113 TableGenerator tabGen = annElt.getAnnotation( TableGenerator.class );
2114 SequenceGenerator seqGen = annElt.getAnnotation( SequenceGenerator.class );
2115 GenericGenerator genGen = annElt.getAnnotation( GenericGenerator.class );
2116 if ( tabGen != null ) {
2117 IdGenerator idGen = buildIdGenerator( tabGen, mappings );
2118 generators.put( idGen.getName(), idGen );
2119 }
2120 if ( seqGen != null ) {
2121 IdGenerator idGen = buildIdGenerator( seqGen, mappings );
2122 generators.put( idGen.getName(), idGen );
2123 }
2124 if ( genGen != null ) {
2125 IdGenerator idGen = buildIdGenerator( genGen, mappings );
2126 generators.put( idGen.getName(), idGen );
2127 }
2128 return generators;
2129 }
2130
2131 public static boolean isDefault(XClass clazz, ExtendedMappings mappings) {
2132 return mappings.getReflectionManager().equals( clazz, void.class );
2133 }
2134
2135 public static Map<XClass, InheritanceState> buildInheritanceStates(
2136 List<XClass> orderedClasses, ReflectionManager reflectionManager
2137 ) {
2138 Map<XClass, InheritanceState> inheritanceStatePerClass = new HashMap<XClass, InheritanceState>(
2139 orderedClasses.size()
2140 );
2141 for ( XClass clazz : orderedClasses ) {
2142 InheritanceState superclassState = InheritanceState.getSuperclassInheritanceState(
2143 clazz, inheritanceStatePerClass,
2144 reflectionManager
2145 );
2146 InheritanceState state = new InheritanceState( clazz );
2147 if ( superclassState != null ) {
2148 //the classes are ordered thus preventing an NPE
2149 //FIXME if an entity has subclasses annotated @MappedSperclass wo sub @Entity this is wrong
2150 superclassState.hasSons = true;
2151 InheritanceState superEntityState = InheritanceState.getSuperEntityInheritanceState(
2152 clazz, inheritanceStatePerClass,
2153 reflectionManager
2154 );
2155 state.hasParents = superEntityState != null;
2156 final boolean nonDefault = state.type != null && !InheritanceType.SINGLE_TABLE.equals( state.type );
2157 if ( superclassState.type != null ) {
2158 final boolean mixingStrategy = state.type != null && !state.type.equals( superclassState.type );
2159 if ( nonDefault && mixingStrategy ) {
2160 log.warn(
2161 "Mixing inheritance strategy in a entity hierarchy is not allowed, ignoring sub strategy in: " + clazz
2162 .getName()
2163 );
2164 }
2165 state.type = superclassState.type;
2166 }
2167 }
2168 inheritanceStatePerClass.put( clazz, state );
2169 }
2170 return inheritanceStatePerClass;
2171 }
2172 }