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.Map ; 8 import java.util.StringTokenizer ; 9 import java.util.List ; 10 import javax.persistence.FetchType; 11 import javax.persistence.MapKey; 12 13 import org.apache.commons.logging.Log; 14 import org.apache.commons.logging.LogFactory; 15 import org.hibernate.AnnotationException; 16 import org.hibernate.AssertionFailure; 17 import org.hibernate.FetchMode; 18 import org.hibernate.MappingException; 19 import org.hibernate.annotations.BatchSize; 20 import org.hibernate.annotations.Cache; 21 import org.hibernate.annotations.OrderBy; 22 import org.hibernate.annotations.Sort; 23 import org.hibernate.annotations.SortType; 24 import org.hibernate.annotations.Where; 25 import org.hibernate.cfg.AnnotationBinder; 26 import org.hibernate.cfg.Ejb3JoinColumn; 27 import org.hibernate.cfg.ExtendedMappings; 28 import org.hibernate.cfg.IndexColumn; 29 import org.hibernate.cfg.PropertyHolder; 30 import org.hibernate.cfg.SecondPass; 31 import org.hibernate.cfg.PropertyInferredData; 32 import org.hibernate.cfg.BinderHelper; 33 import org.hibernate.mapping.Backref; 34 import org.hibernate.mapping.Collection; 35 import org.hibernate.mapping.DependantValue; 36 import org.hibernate.mapping.Join; 37 import org.hibernate.mapping.KeyValue; 38 import org.hibernate.mapping.ManyToOne; 39 import org.hibernate.mapping.OneToMany; 40 import org.hibernate.mapping.PersistentClass; 41 import org.hibernate.mapping.Property; 42 import org.hibernate.mapping.SimpleValue; 43 import org.hibernate.mapping.Table; 44 import org.hibernate.mapping.Column; 45 import org.hibernate.util.StringHelper; 46 47 53 public abstract class CollectionBinder { 54 55 private static final Log log = LogFactory.getLog(CollectionBinder.class); 56 57 protected Collection collection; 58 protected String propertyName; 59 FetchMode fetchMode; 60 PropertyHolder propertyHolder; 61 int batchSize; 62 String where; 63 private String mappedBy; 64 private Table table; 65 private Class collectionType; 66 private String targetEntity; 67 private ExtendedMappings mappings; 68 private boolean unique; 69 private Ejb3JoinColumn[] inverseJoinColumns; 70 private String cascadeStrategy; 71 String cacheConcurrencyStrategy; 72 Map <String , String > filters = new HashMap <String , String >(); 73 String cacheRegionName; 74 private boolean oneToMany; 75 protected IndexColumn indexColumn; 76 private String orderBy; 77 protected String hqlOrderBy; 78 private boolean isSorted; 79 private Class comparator; 80 private boolean hasToBeSorted; 81 protected boolean cascadeDeleteEnabled; 82 protected String mapKeyPropertyName; 83 private boolean insertable = true; 84 private boolean updatable = true; 85 86 public void setUpdatable(boolean updatable) { 87 this.updatable = updatable; 88 } 89 90 public void setInsertable(boolean insertable) { 91 this.insertable = insertable; 92 } 93 94 95 public void setCascadeStrategy(String cascadeStrategy) { 96 this.cascadeStrategy = cascadeStrategy; 97 } 98 99 public void setPropertyAccessorName(String propertyAccessorName) { 100 this.propertyAccessorName = propertyAccessorName; 101 } 102 103 private String propertyAccessorName; 104 105 public void setUnique(boolean unique) { 106 this.unique = unique; 107 } 108 109 public void setInverseJoinColumns(Ejb3JoinColumn[] inverseJoinColumns) { 110 this.inverseJoinColumns = inverseJoinColumns; 111 } 112 113 public void setJoinColumns(Ejb3JoinColumn[] joinColumns) { 114 this.joinColumns = joinColumns; 115 } 116 117 private Ejb3JoinColumn[] joinColumns; 118 119 public void setPropertyHolder(PropertyHolder propertyHolder) { 120 this.propertyHolder = propertyHolder; 121 } 122 123 public void setBatchSize(BatchSize batchSize) { 124 this.batchSize = batchSize == null ? -1 : batchSize.size(); 125 } 126 127 public void setEjb3OrderBy(javax.persistence.OrderBy orderByAnn) { 128 if (orderByAnn != null) { 129 hqlOrderBy = orderByAnn.value(); 130 } 131 } 132 133 public void setSqlOrderBy(OrderBy orderByAnn) { 134 if (orderByAnn != null) { 135 if ( ! AnnotationBinder.isDefault( orderByAnn.clause() ) ) orderBy = orderByAnn.clause(); 136 } 137 } 138 139 public void setSort(Sort sortAnn) { 140 if (sortAnn != null) { 141 isSorted = ! SortType.UNSORTED.equals( sortAnn.type() ); 142 if (isSorted && SortType.COMPARATOR.equals( sortAnn.type() ) ) { 143 comparator = sortAnn.comparator(); 144 } 145 } 146 } 147 148 149 public static CollectionBinder getCollectionBinder(String entityName, PropertyInferredData inferredData, 150 boolean isIndexed) { 151 CollectionBinder binder = null; 152 Class returnedClass = inferredData.getReturnedClassOrElement(); 153 if ( inferredData.isArray() ) { 154 binder = new ArrayBinder(); 155 } 156 else if ( java.util.Set .class.equals(returnedClass) ) { 157 binder = new SetBinder(); 158 } 159 else if ( java.util.SortedSet .class.equals(returnedClass) ) { 160 binder = new SetBinder(true); 161 } 162 else if ( java.util.Map .class.equals(returnedClass) ) { 163 binder = new MapBinder(); 164 } 165 else if ( java.util.Collection .class.equals(returnedClass)) { 166 binder = new BagBinder(); 167 } 168 else if ( java.util.List .class.equals(returnedClass) ) { 169 if (isIndexed == true) { 170 binder = new ListBinder(); 171 } 172 else { 173 binder = new BagBinder(); 174 } 175 } 176 else { 177 throw new AnnotationException(returnedClass.getName() + " collection not yet supported: " 178 + entityName + inferredData.getPropertyName() ); 179 } 180 return binder; 181 } 182 183 protected CollectionBinder() {} 184 185 protected CollectionBinder(boolean sorted) { 186 this.hasToBeSorted = sorted; 187 } 188 189 public void setMappedBy(String mappedBy) { 190 this.mappedBy = mappedBy; 191 } 192 193 public void setTable(Table table) { 194 this.table = table; 195 } 196 197 public void setCollectionType(Class collectionType) { 198 this.collectionType = collectionType; 199 } 200 201 public void setTargetEntity(Class targetEntity) { 202 if ( AnnotationBinder.isDefault(targetEntity) ) { 203 this.targetEntity = AnnotationBinder.ANNOTATION_STRING_DEFAULT; 204 } 205 else { 206 this.targetEntity = targetEntity.getName(); 207 } 208 } 209 210 public void setMappings(ExtendedMappings mappings) { 211 this.mappings = mappings; 212 } 213 214 protected abstract Collection createCollection(PersistentClass persistentClass); 215 216 public Collection getCollection() { 217 return collection; 218 } 219 220 public void setPropertyName(String propertyName) { 221 this.propertyName = propertyName; 222 } 223 224 public void bind() { 225 if ( log.isDebugEnabled() ) { 226 if (! oneToMany) { 227 if ( unique == false ) { 228 log.debug("Binding as ManyToMany: " + propertyHolder.getEntityName() + "." + propertyName); 229 } 230 else { 231 log.debug("Binding a OneToMany: " + propertyHolder.getEntityName() + "." + propertyName + " through an association table"); 232 } 233 } else { 234 log.debug("Binding a OneToMany: " + propertyHolder.getEntityName() + "." + propertyName + " through a foreign key"); 235 } 236 } 237 this.collection = createCollection( propertyHolder.getPersistentClass() ); 238 log.debug( "Collection role: " + StringHelper.qualify( propertyHolder.getPath(), propertyName) ); 239 collection.setRole( StringHelper.qualify( propertyHolder.getPath(), propertyName) ); 240 241 collection.setFetchMode(fetchMode); 243 collection.setLazy(fetchMode == FetchMode.SELECT); 244 collection.setBatchSize(batchSize); 245 if (orderBy != null && hqlOrderBy != null) 246 throw new AnnotationException("Cannot use sql order by clause in conjunction of EJB3 order by clause: " + safeCollectionRole() ); 247 if (orderBy != null) collection.setOrderBy(orderBy); 248 if (isSorted) { 249 collection.setSorted(true); 250 if (comparator != null) { 251 try { 252 collection.setComparator( (Comparator ) comparator.newInstance() ); 253 } 254 catch (Exception e) { 255 throw new AnnotationException( "Could not instantiate comparator class: " 256 + comparator.getName() + "(" + safeCollectionRole() + ")" ); 257 } 258 } 259 } 260 else { 261 if (hasToBeSorted) throw new AnnotationException("A sorted collection has to define @Sort: " 262 + safeCollectionRole() ); 263 } 264 265 if ( StringHelper.isNotEmpty( cacheConcurrencyStrategy ) ) { 266 collection.setCacheConcurrencyStrategy(cacheConcurrencyStrategy); 267 collection.setCacheRegionName(cacheRegionName); 268 } 269 Iterator <Map.Entry <String , String >> iter = filters.entrySet().iterator(); 270 if ( StringHelper.isNotEmpty(where) ) collection.setWhere(where); 271 while (iter.hasNext()) { 272 Map.Entry <String , String > filter = iter.next(); 273 collection.addFilter(filter.getKey(), filter.getValue()); 274 } 275 boolean isMappedBy = ! AnnotationBinder.isDefault(mappedBy); 276 collection.setInverse( isMappedBy ); 277 collection.setCollectionTable(table); 278 String collType = getCollectionType(); 279 if (oneToMany) { 280 org.hibernate.mapping.OneToMany oneToMany = new org.hibernate.mapping.OneToMany( collection.getOwner() ); 281 collection.setElement(oneToMany); 282 oneToMany.setReferencedEntityName( collType ); 283 } 284 else { 285 if ( isMappedBy ) { 287 mappings.addMappedBy( collType, mappedBy, propertyName ); 288 } 289 } 290 mappings.addSecondPass( 291 getSecondPass(mappings, joinColumns, inverseJoinColumns, collType, fetchMode, unique), 292 ! isMappedBy 293 ); 294 mappings.addCollection(collection); 295 PropertyBinder binder = new PropertyBinder(); 296 binder.setName(propertyName); 297 binder.setValue(collection); 298 binder.setCascade(cascadeStrategy); 299 binder.setPropertyAccessorName(propertyAccessorName); 300 binder.setInsertable( insertable ); 301 binder.setUpdatable( updatable ); 302 Property prop = binder.make(); 303 propertyHolder.addProperty(prop); 305 } 306 307 private String getCollectionType() { 308 if ( AnnotationBinder.isDefault(targetEntity) ) { 309 if (collectionType != null) { 310 return collectionType.getName(); 311 } 312 else { 313 String errorMsg = "Collection has neither generic type or OneToMany.targetEntity() defined: " 314 + safeCollectionRole(); 315 throw new AnnotationException(errorMsg); 316 } 317 } 318 else { 319 return targetEntity; 320 } 321 } 322 323 public SecondPass getSecondPass(final ExtendedMappings mappings, 324 final Ejb3JoinColumn[] keyColumns, 325 final Ejb3JoinColumn[] inverseColumns, 326 final String collType, 327 final FetchMode fetchMode, 328 final boolean unique) { 329 if (inverseColumns != null) { 330 return new SecondPass(mappings, collection) { 331 public void secondPass(Map persistentClasses, Map inheritedMetas) 332 throws MappingException { 333 bindManyToManySecondPass( 334 getCollection(), 335 persistentClasses, 336 keyColumns, 337 inverseColumns, 338 collType, 339 fetchMode, 340 unique, 341 cascadeDeleteEnabled, (ExtendedMappings) getMappings() 342 ); 343 } 344 }; 345 } 346 else { 347 return new SecondPass(mappings, collection) { 348 public void secondPass(Map persistentClasses, Map inheritedMetas) 349 throws MappingException { 350 bindCollectionSecondPass( 351 getCollection(), 352 persistentClasses, 353 keyColumns, 354 cascadeDeleteEnabled, 355 hqlOrderBy, 356 (ExtendedMappings) getMappings() 357 ); 358 } 359 }; 360 } 361 } 362 363 public void setCache(Cache cacheAnn) { 364 if (cacheAnn != null) { 365 cacheRegionName = AnnotationBinder.isDefault( cacheAnn.region() ) ? null : cacheAnn.region(); 366 cacheConcurrencyStrategy = EntityBinder.getCacheConcurrencyStrategy( cacheAnn.usage() ); 367 } 368 else { 369 cacheConcurrencyStrategy = null; 370 cacheRegionName = null; 371 } 372 } 373 374 public void setFetchType(FetchType fetch) { 375 if (fetch == FetchType.EAGER) { 376 fetchMode = FetchMode.JOIN; 377 } 378 else { 379 fetchMode = FetchMode.SELECT; 380 } 381 } 382 383 public void addFilter(String name, String condition) { 384 filters.put(name, condition); 385 } 386 387 public void setWhere(Where whereAnn) { 388 if (whereAnn != null) { 389 where = whereAnn.clause(); 390 } 391 } 392 393 public void setOneToMany(boolean oneToMany) { 394 this.oneToMany = oneToMany; 395 } 396 397 public void setIndexColumn(IndexColumn indexColumn) { 398 this.indexColumn = indexColumn; 399 } 400 401 public void setMapKey(MapKey key) { 402 if (key != null) { 403 mapKeyPropertyName = key.name(); 404 } 405 } 406 407 protected static void bindCollectionSecondPass( 408 Collection collValue, Map persistentClasses, Ejb3JoinColumn[] columns, boolean cascadeDeleteEnabled, 409 String hqlOrderBy, ExtendedMappings mappings 410 ) throws MappingException { 411 if ( collValue.isOneToMany() ) { 412 org.hibernate.mapping.OneToMany oneToMany = 413 (org.hibernate.mapping.OneToMany) collValue.getElement(); 414 String assocClass = oneToMany.getReferencedEntityName(); 415 PersistentClass associatedClass = (PersistentClass) persistentClasses.get(assocClass); 416 String orderBy = buildOrderByClauseFromHql(hqlOrderBy, associatedClass, collValue.getRole() ); 417 if (orderBy != null) collValue.setOrderBy(orderBy); 418 if (mappings == null) { 419 throw new AssertionFailure("CollectionSecondPass for oneToMany should not be called with null mappings"); 420 } 421 Map <String , Join> joins = mappings.getJoins(assocClass); 422 if (associatedClass==null) throw new MappingException( 423 "Association references unmapped class: " + assocClass 424 ); 425 oneToMany.setAssociatedClass(associatedClass); 426 for (Ejb3JoinColumn column : columns) { 428 column.setPersistentClass(associatedClass); 429 column.setJoins(joins); 430 collValue.setCollectionTable( column.getTable() ); 431 } 432 log.info("Mapping collection: " + collValue.getRole() + " -> " + collValue.getCollectionTable().getName() ); 433 } 434 435 bindCollectionSecondPass(collValue, null, columns, cascadeDeleteEnabled, mappings ); 436 if ( collValue.isOneToMany() 437 && !collValue.isInverse() 438 && !collValue.getKey().isNullable() ) { 439 String entityName = ( (OneToMany) collValue.getElement() ).getReferencedEntityName(); 441 PersistentClass referenced = mappings.getClass( entityName ); 442 Backref prop = new Backref(); 443 prop.setName( '_' + columns[0].getPropertyName() + "Backref" ); 444 prop.setUpdateable( false ); 445 prop.setSelectable( false ); 446 prop.setCollectionRole( collValue.getRole() ); 447 prop.setEntityName( collValue.getOwner().getEntityName() ); 448 prop.setValue( collValue.getKey() ); 449 referenced.addProperty( prop ); 450 } 451 } 452 453 private static String buildOrderByClauseFromHql(String hqlOrderBy, PersistentClass associatedClass, String role) { 454 String orderByString = null; 455 if (hqlOrderBy != null) { 456 List <String > properties = new ArrayList <String >(); 457 List <String > ordering = new ArrayList <String >(); 458 StringBuffer orderByBuffer = new StringBuffer (); 459 if ( "".equals( hqlOrderBy ) ) { 460 Iterator it = associatedClass.getIdentifier().getColumnIterator(); 462 while( it.hasNext() ) { 463 Column col = (Column) it.next(); 464 orderByBuffer.append( col.getName() ).append(" asc").append(", "); 465 } 466 } 467 else { 468 StringTokenizer st = new StringTokenizer (hqlOrderBy, " ,", false); 469 String currentOrdering = null; 470 while( st.hasMoreTokens() ) { 472 String token = st.nextToken(); 473 if ( isNonPropertyToken(token) ) { 474 if (currentOrdering != null) 475 throw new AnnotationException("Error while parsing HQL orderBy clause: " + hqlOrderBy 476 + " (" + role + ")"); 477 currentOrdering = token; 478 } 479 else { 480 if (currentOrdering == null) { 482 ordering.add("asc"); 484 } 485 else { 486 ordering.add(currentOrdering); 487 currentOrdering = null; 488 } 489 properties.add(token); 490 } 491 } 492 ordering.remove(0); if (currentOrdering == null) { 495 ordering.add("asc"); 497 } 498 else { 499 ordering.add(currentOrdering); 500 currentOrdering = null; 501 } 502 int index = 0; 503 504 for (String property : properties) { 505 Property p = BinderHelper.findPropertyByName(associatedClass, property ); 506 if (p == null) { 507 throw new AnnotationException("property from @OrderBy clause not found: " 508 + associatedClass.getEntityName() + "." + property); 509 } 510 511 Iterator propertyColumns = p.getColumnIterator(); 512 while ( propertyColumns.hasNext() ) { 513 Column column = (Column) propertyColumns.next(); 514 orderByBuffer.append( column.getName() ).append(" ").append( ordering.get(index) ).append(", "); 515 } 516 index++; 517 } 518 } 519 orderByString = orderByBuffer.substring(0, orderByBuffer.length() - 2); 520 } 521 return orderByString; 522 } 523 524 private static boolean isNonPropertyToken(String token) { 525 if ( " ".equals(token) ) return true; 526 if ( ",".equals(token) ) return true; 527 if ( token.equalsIgnoreCase("desc") ) return true; 528 if ( token.equalsIgnoreCase("asc") ) return true; 529 return false; 530 } 531 532 private static SimpleValue buildCollectionKey( 533 Collection collValue, Ejb3JoinColumn[] joinColumns, boolean cascadeDeleteEnabled, 534 ExtendedMappings mappings 535 ) { 536 KeyValue keyVal; 538 if (joinColumns.length > 0 && StringHelper.isNotEmpty( joinColumns[0].getMappedBy() ) ) { 541 String propRef = mappings.getPropertyReferencedAssociation( 542 joinColumns[0].getPropertyHolder().getEntityName(), 543 joinColumns[0].getMappedBy() 544 ); 545 if (propRef != null) { 546 collValue.setReferencedPropertyName( propRef ); 547 mappings.addPropertyReference( collValue.getOwnerEntityName(), propRef ); 548 } 549 } 550 String propRef = collValue.getReferencedPropertyName(); 551 if (propRef==null) { 552 keyVal = collValue.getOwner().getIdentifier(); 553 } 554 else { 555 keyVal = (KeyValue) collValue.getOwner() 556 .getProperty(propRef) 557 .getValue(); 558 } 559 DependantValue key = new DependantValue( collValue.getCollectionTable(), keyVal ); 560 key.setTypeName(null); 561 Ejb3JoinColumn.checkPropertyConsistency( joinColumns, collValue.getOwnerEntityName() ); 562 key.setNullable( joinColumns.length == 0 ? true : joinColumns[0].isNullable() ); 563 key.setUpdateable( joinColumns.length == 0 ? true : joinColumns[0].isUpdatable() ); 564 key.setCascadeDeleteEnabled(cascadeDeleteEnabled); 565 collValue.setKey(key); 566 return key; 567 } 568 569 protected static void bindManyToManySecondPass( 570 Collection collValue, 571 Map persistentClasses, 572 Ejb3JoinColumn[] joinColumns, 573 Ejb3JoinColumn[] inverseJoinColumns, 574 String collType, 575 FetchMode fetchMode, 576 boolean unique, 577 boolean cascadeDeleteEnabled, ExtendedMappings mappings 578 ) throws MappingException { 579 580 PersistentClass collectionEntity = (PersistentClass) persistentClasses.get(collType); 581 if (collectionEntity == null) { 582 throw new MappingException( 583 "Association references unmapped class: " + collType 584 ); 585 } 586 boolean mappedBy = ! AnnotationBinder.isDefault(joinColumns[0].getMappedBy() ); 587 if ( mappedBy ) { 588 Property otherSideProperty; 589 try { 590 otherSideProperty = collectionEntity.getProperty( joinColumns[0].getMappedBy() ); 591 } catch (MappingException e) { 592 throw new AnnotationException("mappedBy reference an unknown property: " + collType + "." + joinColumns[0].getMappedBy() ); 593 } 594 Table table = ( (Collection) otherSideProperty.getValue() ).getCollectionTable(); 595 collValue.setCollectionTable(table); 596 for (Ejb3JoinColumn column : joinColumns) { 597 column.setDefaultColumnHeader( joinColumns[0].getMappedBy() ); 598 } 599 } 600 else { 601 for (Ejb3JoinColumn column : joinColumns) { 603 String header = mappings.getFromMappedBy( collValue.getOwnerEntityName() , column.getPropertyName() ); 604 header = (header == null) ? collValue.getOwner().getTable().getName() : header; 605 column.setDefaultColumnHeader( header ); 606 } 607 if ( collValue.getCollectionTable() == null ) { 608 String tableName = collValue.getOwner().getTable().getName() 610 + "_" 611 + collectionEntity.getTable().getName(); 612 Table table = TableBinder.fillTable("", "", tableName, false, new ArrayList (), null, null, mappings); 613 collValue.setCollectionTable(table); 614 } 615 } 616 bindCollectionSecondPass(collValue, collectionEntity, joinColumns, cascadeDeleteEnabled, mappings ); 617 618 ManyToOne element = 619 new ManyToOne( collValue.getCollectionTable() ); 620 collValue.setElement(element); 621 element.setReferencedEntityName(collType); 622 element.setFetchMode(fetchMode); 623 element.setLazy(fetchMode!=FetchMode.JOIN); 624 625 if ( ( collValue.getFilterMap().size() != 0 || StringHelper.isNotEmpty( collValue.getWhere() ) ) && 627 collValue.getFetchMode() == FetchMode.JOIN && 628 collValue.getElement().getFetchMode() != FetchMode.JOIN ) { 629 throw new MappingException("@ManyToMany defining filter or where without join fetching " 630 + "not valid within collection using join fetching[" + collValue.getRole() + "]"); 631 } 632 633 if (collectionEntity==null) throw new MappingException( 635 "Association references unmapped class: " + collType 636 ); 637 TableBinder.bindManytoManyInverseFk(collectionEntity, inverseJoinColumns, element, unique, mappings ); 638 639 } 640 641 private static void bindCollectionSecondPass( 642 Collection collValue, PersistentClass collectionEntity, Ejb3JoinColumn[] joinColumns, boolean cascadeDeleteEnabled, 643 ExtendedMappings mappings 644 ) { 645 BinderHelper.createSyntheticPropertyReference( joinColumns, collValue.getOwner(), collValue, mappings ); 646 SimpleValue key = buildCollectionKey(collValue, joinColumns, cascadeDeleteEnabled, mappings ); 647 TableBinder.bindFk(collValue.getOwner(), collectionEntity, joinColumns, key, false ); 648 } 649 650 public void setCascadeDeleteEnabled(boolean onDeleteCascade) { 651 this.cascadeDeleteEnabled = onDeleteCascade; 652 } 653 654 private String safeCollectionRole() { 655 if (propertyHolder != null) { 656 return propertyHolder.getEntityName() + "." + propertyName; 657 } 658 else { 659 return ""; 660 } 661 } 662 663 664 } | Popular Tags |