1 // $Id: AnnotationConfiguration.java 14990 2008-07-29 18:14:14Z hardy.ferentschik $
2 package org.hibernate.cfg;
3
4 import java.io.File;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.lang.reflect.Constructor;
8 import java.lang.reflect.InvocationTargetException;
9 import java.lang.reflect.Method;
10 import java.net.URL;
11 import java.util.ArrayList;
12 import java.util.Collection;
13 import java.util.HashMap;
14 import java.util.HashSet;
15 import java.util.Iterator;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Properties;
19 import java.util.ResourceBundle;
20 import java.util.Set;
21 import java.util.StringTokenizer;
22
23 import javax.persistence.Entity;
24 import javax.persistence.MappedSuperclass;
25
26 import org.dom4j.Attribute;
27 import org.dom4j.Document;
28 import org.dom4j.DocumentException;
29 import org.dom4j.Element;
30 import org.dom4j.io.SAXReader;
31 import org.hibernate.AnnotationException;
32 import org.hibernate.HibernateException;
33 import org.hibernate.Interceptor;
34 import org.hibernate.MappingException;
35 import org.hibernate.SessionFactory;
36 import org.hibernate.annotations.AnyMetaDef;
37 import org.hibernate.annotations.common.reflection.ReflectionManager;
38 import org.hibernate.annotations.common.reflection.XClass;
39 import org.hibernate.cfg.annotations.Version;
40 import org.hibernate.cfg.annotations.reflection.EJB3ReflectionManager;
41 import org.hibernate.event.EventListeners;
42 import org.hibernate.event.PreInsertEventListener;
43 import org.hibernate.event.PreUpdateEventListener;
44 import org.hibernate.mapping.Column;
45 import org.hibernate.mapping.Join;
46 import org.hibernate.mapping.PersistentClass;
47 import org.hibernate.mapping.Table;
48 import org.hibernate.mapping.UniqueKey;
49 import org.hibernate.util.JoinedIterator;
50 import org.hibernate.util.ReflectHelper;
51 import org.hibernate.util.StringHelper;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54 import org.xml.sax.InputSource;
55 import org.xml.sax.SAXException;
56
57 /**
58 * Similar to the {@link Configuration} object but handles EJB3 and Hibernate
59 * specific annotations as a metadata facility.
60 *
61 * @author Emmanuel Bernard
62 * @author Hardy Ferentschik
63 */
64 public class AnnotationConfiguration extends Configuration {
65 private Logger log = LoggerFactory.getLogger( AnnotationConfiguration.class );
66
67 /**
68 * Class name of the class needed to enable Search.
69 */
70 private static final String SEARCH_STARTUP_CLASS = "org.hibernate.search.event.EventListenerRegister";
71
72 /**
73 * Method to call to enable Search.
74 */
75 private static final String SEARCH_STARTUP_METHOD = "enableHibernateSearch";
76
77 static {
78 Version.touch(); //touch version
79 }
80
81 public static final String ARTEFACT = "hibernate.mapping.precedence";
82 public static final String DEFAULT_PRECEDENCE = "hbm, class";
83
84 private Map namedGenerators;
85 private Map<String, Map<String, Join>> joins;
86 private Map<String, AnnotatedClassType> classTypes;
87 private Set<String> defaultNamedQueryNames;
88 private Set<String> defaultNamedNativeQueryNames;
89 private Set<String> defaultSqlResulSetMappingNames;
90 private Set<String> defaultNamedGenerators;
91 private Map<String, Properties> generatorTables;
92 private Map<Table, List<String[]>> tableUniqueConstraints;
93 private Map<String, String> mappedByResolver;
94 private Map<String, String> propertyRefResolver;
95 private Map<String, AnyMetaDef> anyMetaDefs;
96 private List<XClass> annotatedClasses;
97 private Map<String, XClass> annotatedClassEntities;
98 private Map<String, Document> hbmEntities;
99 private List<CacheHolder> caches;
100 private List<Document> hbmDocuments; //user ordering matters, hence the list
101 private String precedence = null;
102 private boolean inSecondPass = false;
103 private transient ReflectionManager reflectionManager;
104 private boolean isDefaultProcessed = false;
105 private boolean isValidatorNotPresentLogged;
106
107 public AnnotationConfiguration() {
108 super();
109 }
110
111 public AnnotationConfiguration(SettingsFactory sf) {
112 super( sf );
113 }
114
115 protected List<XClass> orderAndFillHierarchy(List<XClass> original) {
116 //TODO remove embeddable
117 List<XClass> copy = new ArrayList<XClass>( original );
118 //for each class, copy all the relevant hierarchy
119 for (XClass clazz : original) {
120 XClass superClass = clazz.getSuperclass();
121 while ( superClass != null && !reflectionManager.equals( superClass, Object.class ) && !copy.contains( superClass ) ) {
122 if ( superClass.isAnnotationPresent( Entity.class )
123 || superClass.isAnnotationPresent( MappedSuperclass.class ) ) {
124 copy.add( superClass );
125 }
126 superClass = superClass.getSuperclass();
127 }
128 }
129 List<XClass> workingCopy = new ArrayList<XClass>( copy );
130 List<XClass> newList = new ArrayList<XClass>( copy.size() );
131 while ( workingCopy.size() > 0 ) {
132 XClass clazz = workingCopy.get( 0 );
133 orderHierarchy( workingCopy, newList, copy, clazz );
134 }
135 return newList;
136 }
137
138 private void orderHierarchy(List<XClass> copy, List<XClass> newList, List<XClass> original, XClass clazz) {
139 if ( clazz == null || reflectionManager.equals( clazz, Object.class ) ) return;
140 //process superclass first
141 orderHierarchy( copy, newList, original, clazz.getSuperclass() );
142 if ( original.contains( clazz ) ) {
143 if ( !newList.contains( clazz ) ) {
144 newList.add( clazz );
145 }
146 copy.remove( clazz );
147 }
148 }
149
150 /**
151 * Read a mapping from the class annotation metadata (JSR 175).
152 *
153 * @param persistentClass the mapped class
154 * @return the configuration object
155 */
156 public AnnotationConfiguration addAnnotatedClass(Class persistentClass) throws MappingException {
157 XClass persistentXClass = reflectionManager.toXClass( persistentClass );
158 try {
159 annotatedClasses.add( persistentXClass );
160 return this;
161 }
162 catch (MappingException me) {
163 log.error( "Could not compile the mapping annotations", me );
164 throw me;
165 }
166 }
167
168 /**
169 * Read package level metadata
170 *
171 * @param packageName java package name
172 * @return the configuration object
173 */
174 public AnnotationConfiguration addPackage(String packageName) throws MappingException {
175 log.info( "Mapping package {}", packageName );
176 try {
177 AnnotationBinder.bindPackage( packageName, createExtendedMappings() );
178 return this;
179 }
180 catch (MappingException me) {
181 log.error( "Could not compile the mapping annotations", me );
182 throw me;
183 }
184 }
185
186 public ExtendedMappings createExtendedMappings() {
187 return new ExtendedMappings(
188 classes,
189 collections,
190 tables,
191 namedQueries,
192 namedSqlQueries,
193 sqlResultSetMappings,
194 defaultNamedQueryNames,
195 defaultNamedNativeQueryNames,
196 defaultSqlResulSetMappingNames,
197 defaultNamedGenerators,
198 imports,
199 secondPasses,
200 propertyReferences,
201 namingStrategy,
202 typeDefs,
203 filterDefinitions,
204 namedGenerators,
205 joins,
206 classTypes,
207 extendsQueue,
208 tableNameBinding, columnNameBindingPerTable, auxiliaryDatabaseObjects,
209 generatorTables,
210 tableUniqueConstraints,
211 mappedByResolver,
212 propertyRefResolver,
213 anyMetaDefs,
214 reflectionManager
215 );
216 }
217
218 @Override
219 public void setCacheConcurrencyStrategy(
220 String clazz, String concurrencyStrategy, String region, boolean cacheLazyProperty
221 ) throws MappingException {
222 caches.add( new CacheHolder( clazz, concurrencyStrategy, region, true, cacheLazyProperty ) );
223 }
224
225 @Override
226 public void setCollectionCacheConcurrencyStrategy(String collectionRole, String concurrencyStrategy, String region)
227 throws MappingException {
228 caches.add( new CacheHolder( collectionRole, concurrencyStrategy, region, false, false ) );
229 }
230
231 @Override
232 protected void reset() {
233 super.reset();
234 namedGenerators = new HashMap();
235 joins = new HashMap<String, Map<String, Join>>();
236 classTypes = new HashMap<String, AnnotatedClassType>();
237 generatorTables = new HashMap<String, Properties>();
238 defaultNamedQueryNames = new HashSet<String>();
239 defaultNamedNativeQueryNames = new HashSet<String>();
240 defaultSqlResulSetMappingNames = new HashSet<String>();
241 defaultNamedGenerators = new HashSet<String>();
242 tableUniqueConstraints = new HashMap<Table, List<String[]>>();
243 mappedByResolver = new HashMap<String, String>();
244 propertyRefResolver = new HashMap<String, String>();
245 annotatedClasses = new ArrayList<XClass>();
246 caches = new ArrayList<CacheHolder>();
247 hbmEntities = new HashMap<String, Document>();
248 annotatedClassEntities = new HashMap<String, XClass>();
249 hbmDocuments = new ArrayList<Document>();
250 namingStrategy = EJB3NamingStrategy.INSTANCE;
251 setEntityResolver( new EJB3DTDEntityResolver() );
252 anyMetaDefs = new HashMap<String, AnyMetaDef>();
253 reflectionManager = new EJB3ReflectionManager();
254 }
255
256 @Override
257 protected void secondPassCompile() throws MappingException {
258 log.debug( "Execute first pass mapping processing" );
259 //build annotatedClassEntities
260 {
261 List<XClass> tempAnnotatedClasses = new ArrayList<XClass>( annotatedClasses.size() );
262 for (XClass clazz : annotatedClasses) {
263 if ( clazz.isAnnotationPresent( Entity.class ) ) {
264 annotatedClassEntities.put( clazz.getName(), clazz );
265 tempAnnotatedClasses.add( clazz );
266 }
267 else if ( clazz.isAnnotationPresent( MappedSuperclass.class ) ) {
268 tempAnnotatedClasses.add( clazz );
269 }
270 //only keep MappedSuperclasses and Entity in this list
271 }
272 annotatedClasses = tempAnnotatedClasses;
273 }
274
275 //process default values first
276 if ( !isDefaultProcessed ) {
277 AnnotationBinder.bindDefaults( createExtendedMappings() );
278 isDefaultProcessed = true;
279 }
280
281 //process entities
282 if ( precedence == null ) precedence = getProperties().getProperty( ARTEFACT );
283 if ( precedence == null ) precedence = DEFAULT_PRECEDENCE;
284 StringTokenizer precedences = new StringTokenizer( precedence, ",; ", false );
285 if ( !precedences.hasMoreElements() ) {
286 throw new MappingException( ARTEFACT + " cannot be empty: " + precedence );
287 }
288 while ( precedences.hasMoreElements() ) {
289 String artifact = (String) precedences.nextElement();
290 removeConflictedArtifact( artifact );
291 processArtifactsOfType( artifact );
292 }
293
294 int cacheNbr = caches.size();
295 for (int index = 0; index < cacheNbr; index++) {
296 CacheHolder cacheHolder = caches.get( index );
297 if ( cacheHolder.isClass ) {
298 super.setCacheConcurrencyStrategy(
299 cacheHolder.role, cacheHolder.usage, cacheHolder.region, cacheHolder.cacheLazy
300 );
301 }
302 else {
303 super.setCollectionCacheConcurrencyStrategy( cacheHolder.role, cacheHolder.usage, cacheHolder.region );
304 }
305 }
306 caches.clear();
307 try {
308 inSecondPass = true;
309 processFkSecondPassInOrder();
310 Iterator iter = secondPasses.iterator();
311 while ( iter.hasNext() ) {
312 SecondPass sp = (SecondPass) iter.next();
313 //do the second pass of fk before the others and remove them
314 if ( sp instanceof CreateKeySecondPass ) {
315 sp.doSecondPass( classes );
316 iter.remove();
317 }
318 }
319
320 iter = secondPasses.iterator();
321 while ( iter.hasNext() ) {
322 SecondPass sp = (SecondPass) iter.next();
323 //do the SecondaryTable second pass before any association becasue associations can be built on joins
324 if ( sp instanceof SecondaryTableSecondPass ) {
325 sp.doSecondPass( classes );
326 iter.remove();
327 }
328 }
329 super.secondPassCompile();
330 inSecondPass = false;
331 }
332 catch (RecoverableException e) {
333 //the exception was not recoverable after all
334 throw (RuntimeException) e.getCause();
335 }
336 Iterator tables = tableUniqueConstraints.entrySet().iterator();
337 Table table;
338 Map.Entry entry;
339 String keyName;
340 int uniqueIndexPerTable;
341 while ( tables.hasNext() ) {
342 entry = (Map.Entry) tables.next();
343 table = (Table) entry.getKey();
344 List<String[]> uniqueConstraints = (List<String[]>) entry.getValue();
345 uniqueIndexPerTable = 0;
346 for (String[] columnNames : uniqueConstraints) {
347 keyName = "key" + uniqueIndexPerTable++;
348 buildUniqueKeyFromColumnNames( columnNames, table, keyName );
349 }
350 }
351 boolean applyOnDdl = getProperties().getProperty(
352 "hibernate.validator.apply_to_ddl", //org.hibernate.validator.Environment.APPLY_TO_DDL
353 "true" )
354 .equalsIgnoreCase( "true" );
355
356 //TODO search for the method only once and cache it?
357 Constructor validatorCtr = null;
358 Method applyMethod = null;
359 try {
360 Class classValidator = ReflectHelper.classForName( "org.hibernate.validator.ClassValidator", this.getClass() );
361 Class messageInterpolator = ReflectHelper.classForName( "org.hibernate.validator.MessageInterpolator", this.getClass() );
362 validatorCtr = classValidator.getDeclaredConstructor(
363 Class.class, ResourceBundle.class, messageInterpolator, Map.class, ReflectionManager.class
364 );
365 applyMethod = classValidator.getMethod( "apply", PersistentClass.class );
366 }
367 catch (ClassNotFoundException e) {
368 if ( !isValidatorNotPresentLogged ) {
369 log.info( "Hibernate Validator not found: ignoring" );
370 }
371 isValidatorNotPresentLogged = true;
372 }
373 catch (NoSuchMethodException e) {
374 throw new AnnotationException( e );
375 }
376 if ( applyMethod != null && applyOnDdl ) {
377 for (PersistentClass persistentClazz : (Collection<PersistentClass>) classes.values()) {
378 //integrate the validate framework
379 String className = persistentClazz.getClassName();
380 if ( StringHelper.isNotEmpty( className ) ) {
381 try {
382 Object validator = validatorCtr.newInstance(
383 ReflectHelper.classForName( className ), null, null, null, reflectionManager
384 );
385 applyMethod.invoke( validator, persistentClazz );
386 }
387 catch (Exception e) {
388 log.warn( "Unable to apply constraints on DDL for " + className, e );
389 }
390 }
391 }
392 }
393 }
394
395 /**
396 * Processes FKSecondPass instances trying to resolve any
397 * graph circularity (ie PK made of a many to one linking to
398 * an entity having a PK made of a ManyToOne ...).
399 */
400 private void processFkSecondPassInOrder() {
401 log.debug( "processing fk mappings (*ToOne and JoinedSubclass)" );
402 List<FkSecondPass> fkSecondPasses = getFKSecondPassesOnly();
403
404 if (fkSecondPasses.size() == 0) {
405 return; // nothing to do here
406 }
407
408 // split FkSecondPass instances into primary key and non primary key FKs.
409 // While doing so build a map of class names to FkSecondPass instances depending on this class.
410 Map<String, Set<FkSecondPass>> isADependencyOf = new HashMap<String, Set<FkSecondPass>>();
411 List endOfQueueFkSecondPasses = new ArrayList( fkSecondPasses.size() );
412 for (FkSecondPass sp : fkSecondPasses) {
413 if ( sp.isInPrimaryKey() ) {
414 String referenceEntityName = sp.getReferencedEntityName();
415 PersistentClass classMapping = getClassMapping( referenceEntityName );
416 String dependentTable = classMapping.getTable().getQuotedName();
417 if ( !isADependencyOf.containsKey( dependentTable ) ) {
418 isADependencyOf.put( dependentTable, new HashSet<FkSecondPass>() );
419 }
420 isADependencyOf.get( dependentTable ).add( sp );
421 }
422 else {
423 endOfQueueFkSecondPasses.add( sp );
424 }
425 }
426
427 // using the isADependencyOf map we order the FkSecondPass recursively instances into the right order for processing
428 List<FkSecondPass> orderedFkSecondPasses = new ArrayList( fkSecondPasses.size() );
429 for (String tableName : isADependencyOf.keySet()) {
430 buildRecursiveOrderedFkSecondPasses(orderedFkSecondPasses, isADependencyOf, tableName, tableName);
431 }
432
433 // process the ordered FkSecondPasses
434 for ( FkSecondPass sp : orderedFkSecondPasses ) {
435 sp.doSecondPass( classes );
436 }
437
438 processEndOfQueue(endOfQueueFkSecondPasses);
439 }
440
441 private void processEndOfQueue(List endOfQueueFkSecondPasses) {
442 /*
443 * If a second pass raises a recoverableException, queue it for next round
444 * stop of no pass has to be processed or if the number of pass to processes
445 * does not diminish between two rounds.
446 * If some failing pass remain, raise the original exception
447 */
448 boolean stopProcess = false;
449 RuntimeException originalException = null;
450 while ( ! stopProcess ) {
451 List failingSecondPasses = new ArrayList();
452 Iterator it = endOfQueueFkSecondPasses.listIterator();
453 while ( it.hasNext() ) {
454 final SecondPass pass = (SecondPass) it.next();
455 try {
456 pass.doSecondPass( classes );
457 }
458 catch (RecoverableException e) {
459 failingSecondPasses.add( pass );
460 if (originalException == null) originalException = (RuntimeException) e.getCause();
461 }
462 }
463 stopProcess = failingSecondPasses.size() == 0 || failingSecondPasses.size() == endOfQueueFkSecondPasses.size();
464 endOfQueueFkSecondPasses = failingSecondPasses;
465 }
466 if (endOfQueueFkSecondPasses.size() > 0) {
467 throw originalException;
468 }
469 }
470
471 /**
472 * @return Returns a list of all <code>secondPasses</code> instances which are a instance of
473 * <code>FkSecondPass</code>.
474 */
475 private List<FkSecondPass> getFKSecondPassesOnly() {
476 Iterator iter = secondPasses.iterator();
477 List<FkSecondPass> fkSecondPasses = new ArrayList<FkSecondPass>(secondPasses.size());
478 while ( iter.hasNext() ) {
479 SecondPass sp = (SecondPass) iter.next();
480 //do the second pass of fk before the others and remove them
481 if ( sp instanceof FkSecondPass ) {
482 fkSecondPasses.add( (FkSecondPass) sp );
483 iter.remove();
484 }
485 }
486 return fkSecondPasses;
487 }
488
489 /**
490 * Recursively builds a list of FkSecondPass instances ready to be processed in this order.
491 * Checking all dependencies recursively seems quite expensive, but the original code just relied
492 * on some sort of table name sorting which failed in certain circumstances.
493 *
494 * @param orderedFkSecondPasses The list containing the <code>FkSecondPass<code> instances ready
495 * for processing.
496 * @param isADependencyOf Our lookup data structure to determine dependencies between tables
497 * @param startTable Table name to start recursive algorithm.
498 * @param currentTable The current table name used to check for 'new' dependencies.
499 *
500 * @see ANN-722 ANN-730
501 */
502 private void buildRecursiveOrderedFkSecondPasses(
503 List orderedFkSecondPasses,
504 Map<String, Set<FkSecondPass>> isADependencyOf, String startTable, String currentTable) {
505
506 Set<FkSecondPass> dependencies = isADependencyOf.get(currentTable);
507
508 // bottom out
509 if (dependencies == null || dependencies.size() == 0) {
510 return;
511 }
512
513 for (FkSecondPass sp : dependencies) {
514 String dependentTable = sp.getValue().getTable().getQuotedName();
515 if (dependentTable.compareTo(startTable) == 0) {
516 StringBuilder sb = new StringBuilder(
517 "Foreign key circularity dependency involving the following tables: ");
518 throw new AnnotationException(sb.toString());
519 }
520 buildRecursiveOrderedFkSecondPasses(orderedFkSecondPasses, isADependencyOf, startTable, dependentTable);
521 if (!orderedFkSecondPasses.contains(sp)) {
522 orderedFkSecondPasses.add(0, sp);
523 }
524 }
525 }
526
527 private void processArtifactsOfType(String artifact) {
528 if ( "hbm".equalsIgnoreCase( artifact ) ) {
529 log.debug( "Process hbm files" );
530 for (Document document : hbmDocuments) {
531 super.add( document );
532 }
533 hbmDocuments.clear();
534 hbmEntities.clear();
535 }
536 else if ( "class".equalsIgnoreCase( artifact ) ) {
537 log.debug( "Process annotated classes" );
538 //bind classes in the correct order calculating some inheritance state
539 List<XClass> orderedClasses = orderAndFillHierarchy( annotatedClasses );
540 Map<XClass, InheritanceState> inheritanceStatePerClass = AnnotationBinder.buildInheritanceStates(
541 orderedClasses, reflectionManager
542 );
543 ExtendedMappings mappings = createExtendedMappings();
544 for (XClass clazz : orderedClasses) {
545 //todo use the same extended mapping
546 AnnotationBinder.bindClass( clazz, inheritanceStatePerClass, mappings );
547 }
548 annotatedClasses.clear();
549 annotatedClassEntities.clear();
550 }
551 else {
552 log.warn( "Unknown artifact: {}", artifact );
553 }
554 }
555
556 private void removeConflictedArtifact(String artifact) {
557 if ( "hbm".equalsIgnoreCase( artifact ) ) {
558 for (String entity : hbmEntities.keySet()) {
559 if ( annotatedClassEntities.containsKey( entity ) ) {
560 annotatedClasses.remove( annotatedClassEntities.get( entity ) );
561 annotatedClassEntities.remove( entity );
562 }
563 }
564 }
565 else if ( "class".equalsIgnoreCase( artifact ) ) {
566 for (String entity : annotatedClassEntities.keySet()) {
567 if ( hbmEntities.containsKey( entity ) ) {
568 hbmDocuments.remove( hbmEntities.get( entity ) );
569 hbmEntities.remove( entity );
570 }
571 }
572 }
573 }
574
575 private void buildUniqueKeyFromColumnNames(String[] columnNames, Table table, String keyName) {
576 UniqueKey uc;
577 int size = columnNames.length;
578 Column[] columns = new Column[size];
579 Set<Column> unbound = new HashSet<Column>();
580 Set<Column> unboundNoLogical = new HashSet<Column>();
581 ExtendedMappings mappings = createExtendedMappings();
582 for (int index = 0; index < size; index++) {
583 String columnName;
584 try {
585 columnName = mappings.getPhysicalColumnName( columnNames[index], table );
586 columns[index] = new Column( columnName );
587 unbound.add( columns[index] );
588 //column equals and hashcode is based on column name
589 }
590 catch (MappingException e) {
591 unboundNoLogical.add( new Column( columnNames[index] ) );
592 }
593 }
594 for (Column column : columns) {
595 if ( table.containsColumn( column ) ) {
596 uc = table.getOrCreateUniqueKey( keyName );
597 uc.addColumn( table.getColumn( column ) );
598 unbound.remove( column );
599 }
600 }
601 if ( unbound.size() > 0 || unboundNoLogical.size() > 0 ) {
602 StringBuilder sb = new StringBuilder( "Unable to create unique key constraint (" );
603 for (String columnName : columnNames) {
604 sb.append( columnName ).append( ", " );
605 }
606 sb.setLength( sb.length() - 2 );
607 sb.append( ") on table " ).append( table.getName() ).append( ": " );
608 for (Column column : unbound) {
609 sb.append( column.getName() ).append( ", " );
610 }
611 for (Column column : unboundNoLogical) {
612 sb.append( column.getName() ).append( ", " );
613 }
614 sb.setLength( sb.length() - 2 );
615 sb.append( " not found" );
616 throw new AnnotationException( sb.toString() );
617 }
618 }
619
620 @Override
621 protected void parseMappingElement(Element subelement, String name) {
622 Attribute rsrc = subelement.attribute( "resource" );
623 Attribute file = subelement.attribute( "file" );
624 Attribute jar = subelement.attribute( "jar" );
625 Attribute pckg = subelement.attribute( "package" );
626 Attribute clazz = subelement.attribute( "class" );
627 if ( rsrc != null ) {
628 log.debug( "{} <- {}", name, rsrc );
629 addResource( rsrc.getValue() );
630 }
631 else if ( jar != null ) {
632 log.debug( "{} <- {}", name, jar );
633 addJar( new File( jar.getValue() ) );
634 }
635 else if ( file != null ) {
636 log.debug( "{} <- {}", name, file );
637 addFile( file.getValue() );
638 }
639 else if ( pckg != null ) {
640 log.debug( "{} <- {}", name, pckg );
641 addPackage( pckg.getValue() );
642 }
643 else if ( clazz != null ) {
644 log.debug( "{} <- {}", name, clazz );
645 Class loadedClass;
646 try {
647 loadedClass = ReflectHelper.classForName( clazz.getValue() );
648 }
649 catch (ClassNotFoundException cnf) {
650 throw new MappingException(
651 "Unable to load class declared as <mapping class=\"" + clazz.getValue() + "\"/> in the configuration:",
652 cnf
653 );
654 }
655 catch (NoClassDefFoundError ncdf) {
656 throw new MappingException(
657 "Unable to load class declared as <mapping class=\"" + clazz.getValue() + "\"/> in the configuration:",
658 ncdf
659 );
660 }
661
662 addAnnotatedClass( loadedClass );
663 }
664 else {
665 throw new MappingException( "<mapping> element in configuration specifies no attributes" );
666 }
667 }
668
669 @Override
670 protected void add(org.dom4j.Document doc) throws MappingException {
671 boolean ejb3Xml = "entity-mappings".equals( doc.getRootElement().getName() );
672 if ( inSecondPass ) {
673 //if in second pass bypass the queueing, getExtendedQueue reuse this method
674 if ( !ejb3Xml ) super.add( doc );
675 }
676 else {
677 if ( !ejb3Xml ) {
678 final Element hmNode = doc.getRootElement();
679 Attribute packNode = hmNode.attribute( "package" );
680 String defaultPackage = packNode != null
681 ? packNode.getValue()
682 : "";
683 Set<String> entityNames = new HashSet<String>();
684 findClassNames( defaultPackage, hmNode, entityNames );
685 for (String entity : entityNames) {
686 hbmEntities.put( entity, doc );
687 }
688 hbmDocuments.add( doc );
689 }
690 else {
691 List<String> classnames = ( (EJB3ReflectionManager) reflectionManager ).getXMLContext().addDocument( doc );
692 for (String classname : classnames) {
693 try {
694 annotatedClasses.add( reflectionManager.classForName( classname, this.getClass() ) );
695 }
696 catch (ClassNotFoundException e) {
697 throw new AnnotationException( "Unable to load class defined in XML: " + classname, e );
698 }
699 }
700 }
701 }
702 }
703
704 private static void findClassNames(
705 String defaultPackage, final Element startNode,
706 final java.util.Set names
707 ) {
708 // if we have some extends we need to check if those classes possibly could be inside the
709 // same hbm.xml file...
710 Iterator[] classes = new Iterator[4];
711 classes[0] = startNode.elementIterator( "class" );
712 classes[1] = startNode.elementIterator( "subclass" );
713 classes[2] = startNode.elementIterator( "joined-subclass" );
714 classes[3] = startNode.elementIterator( "union-subclass" );
715
716 Iterator classIterator = new JoinedIterator( classes );
717 while ( classIterator.hasNext() ) {
718 Element element = (Element) classIterator.next();
719 String entityName = element.attributeValue( "entity-name" );
720 if ( entityName == null ) entityName = getClassName( element.attribute( "name" ), defaultPackage );
721 names.add( entityName );
722 findClassNames( defaultPackage, element, names );
723 }
724 }
725
726 private static String getClassName(Attribute name, String defaultPackage) {
727 if ( name == null ) return null;
728 String unqualifiedName = name.getValue();
729 if ( unqualifiedName == null ) return null;
730 if ( unqualifiedName.indexOf( '.' ) < 0 && defaultPackage != null ) {
731 return defaultPackage + '.' + unqualifiedName;
732 }
733 return unqualifiedName;
734 }
735
736 public void setPrecedence(String precedence) {
737 this.precedence = precedence;
738 }
739
740 private static class CacheHolder {
741 public CacheHolder(String role, String usage, String region, boolean isClass, boolean cacheLazy) {
742 this.role = role;
743 this.usage = usage;
744 this.region = region;
745 this.isClass = isClass;
746 this.cacheLazy = cacheLazy;
747 }
748
749 public String role;
750 public String usage;
751 public String region;
752 public boolean isClass;
753 public boolean cacheLazy;
754 }
755
756 @Override
757 public AnnotationConfiguration addInputStream(InputStream xmlInputStream) throws MappingException {
758 try {
759 List errors = new ArrayList();
760 SAXReader saxReader = xmlHelper.createSAXReader( "XML InputStream", errors, getEntityResolver() );
761 try {
762 saxReader.setFeature( "http://apache.org/xml/features/validation/schema", true );
763 //saxReader.setFeature( "http://apache.org/xml/features/validation/dynamic", true );
764 //set the default schema locators
765 saxReader.setProperty(
766 "http://apache.org/xml/properties/schema/external-schemaLocation",
767 "http://java.sun.com/xml/ns/persistence/orm orm_1_0.xsd"
768 );
769 }
770 catch (SAXException e) {
771 saxReader.setValidation( false );
772 }
773 org.dom4j.Document doc = saxReader
774 .read( new InputSource( xmlInputStream ) );
775
776 if ( errors.size() != 0 ) {
777 throw new MappingException( "invalid mapping", (Throwable) errors.get( 0 ) );
778 }
779 add( doc );
780 return this;
781 }
782 catch (DocumentException e) {
783 throw new MappingException( "Could not parse mapping document in input stream", e );
784 }
785 finally {
786 try {
787 xmlInputStream.close();
788 }
789 catch (IOException ioe) {
790 log.warn( "Could not close input stream", ioe );
791 }
792 }
793 }
794
795 public SessionFactory buildSessionFactory() throws HibernateException {
796 //add validator events if the jar is available
797 boolean enableValidatorListeners = !"false".equalsIgnoreCase( getProperty( "hibernate.validator.autoregister_listeners" ) );
798 Class validateEventListenerClass = null;
799 try {
800 validateEventListenerClass = ReflectHelper.classForName(
801 "org.hibernate.validator.event.ValidateEventListener",
802 AnnotationConfiguration.class );
803 }
804 catch (ClassNotFoundException e) {
805 //validator is not present
806 log.debug( "Validator not present in classpath, ignoring event listener registration" );
807 }
808 if ( enableValidatorListeners && validateEventListenerClass != null ) {
809 //TODO so much duplication
810 Object validateEventListener;
811 try {
812 validateEventListener = validateEventListenerClass.newInstance();
813 }
814 catch (Exception e) {
815 throw new AnnotationException( "Unable to load Validator event listener", e );
816 }
817 {
818 boolean present = false;
819 PreInsertEventListener[] listeners = getEventListeners().getPreInsertEventListeners();
820 if ( listeners != null ) {
821 for (Object eventListener : listeners) {
822 //not isAssignableFrom since the user could subclass
823 present = present || validateEventListenerClass == eventListener.getClass();
824 }
825 if ( !present ) {
826 int length = listeners.length + 1;
827 PreInsertEventListener[] newListeners = new PreInsertEventListener[length];
828 System.arraycopy( listeners, 0, newListeners, 0, length - 1 );
829 newListeners[length - 1] = (PreInsertEventListener) validateEventListener;
830 getEventListeners().setPreInsertEventListeners( newListeners );
831 }
832 }
833 else {
834 getEventListeners().setPreInsertEventListeners(
835 new PreInsertEventListener[] { (PreInsertEventListener) validateEventListener }
836 );
837 }
838 }
839
840 //update event listener
841 {
842 boolean present = false;
843 PreUpdateEventListener[] listeners = getEventListeners().getPreUpdateEventListeners();
844 if ( listeners != null ) {
845 for (Object eventListener : listeners) {
846 //not isAssignableFrom since the user could subclass
847 present = present || validateEventListenerClass == eventListener.getClass();
848 }
849 if ( !present ) {
850 int length = listeners.length + 1;
851 PreUpdateEventListener[] newListeners = new PreUpdateEventListener[length];
852 System.arraycopy( listeners, 0, newListeners, 0, length - 1 );
853 newListeners[length - 1] = (PreUpdateEventListener) validateEventListener;
854 getEventListeners().setPreUpdateEventListeners( newListeners );
855 }
856 }
857 else {
858 getEventListeners().setPreUpdateEventListeners(
859 new PreUpdateEventListener[] { (PreUpdateEventListener) validateEventListener }
860 );
861 }
862 }
863 }
864
865 enableHibernateSearch();
866
867 return super.buildSessionFactory();
868 }
869
870 /**
871 * Tries to automatically register Hibernate Search event listeners by locating the
872 * appropriate bootstrap class and calling the <code>enableHibernateSearch</code> method.
873 */
874 private void enableHibernateSearch() {
875 // load the bootstrap class
876 Class searchStartupClass;
877 try {
878 searchStartupClass = ReflectHelper.classForName(SEARCH_STARTUP_CLASS, AnnotationConfiguration.class);
879 } catch ( ClassNotFoundException e ) {
880 // TODO remove this together with SearchConfiguration after 3.1.0 release of Search
881 // try loading deprecated HibernateSearchEventListenerRegister
882 try {
883 searchStartupClass = ReflectHelper.classForName("org.hibernate.cfg.search.HibernateSearchEventListenerRegister", AnnotationConfiguration.class);
884 } catch ( ClassNotFoundException cnfe ) {
885 log.debug("Search not present in classpath, ignoring event listener registration.");
886 return;
887 }
888 }
889
890 // call the method for registering the listeners
891 try {
892 Object searchStartupInstance = searchStartupClass.newInstance();
893 Method enableSearchMethod = searchStartupClass.getDeclaredMethod(SEARCH_STARTUP_METHOD,
894 EventListeners.class, Properties.class);
895 enableSearchMethod.invoke(searchStartupInstance, getEventListeners(), getProperties());
896 } catch ( InstantiationException e ) {
897 log.debug("Unable to instantiate {}, ignoring event listener registration.", SEARCH_STARTUP_CLASS);
898 } catch ( IllegalAccessException e ) {
899 log.debug("Unable to instantiate {}, ignoring event listener registration.", SEARCH_STARTUP_CLASS);
900 } catch ( NoSuchMethodException e ) {
901 log.debug("Method enableHibernateSearch() not found in {}.", SEARCH_STARTUP_CLASS);
902 } catch ( InvocationTargetException e ) {
903 log.debug("Unable to execute {}, ignoring event listener registration.", SEARCH_STARTUP_METHOD);
904 }
905 }
906
907 @Override
908 public AnnotationConfiguration addFile(String xmlFile) throws MappingException {
909 super.addFile( xmlFile );
910 return this;
911 }
912
913 @Override
914 public AnnotationConfiguration addFile(File xmlFile) throws MappingException {
915 super.addFile( xmlFile );
916 return this;
917 }
918
919 @Override
920 public AnnotationConfiguration addCacheableFile(File xmlFile) throws MappingException {
921 super.addCacheableFile( xmlFile );
922 return this;
923 }
924
925 @Override
926 public AnnotationConfiguration addCacheableFile(String xmlFile) throws MappingException {
927 super.addCacheableFile( xmlFile );
928 return this;
929 }
930
931 @Override
932 public AnnotationConfiguration addXML(String xml) throws MappingException {
933 super.addXML( xml );
934 return this;
935 }
936
937 @Override
938 public AnnotationConfiguration addURL(URL url) throws MappingException {
939 super.addURL( url );
940 return this;
941 }
942
943 @Override
944 public AnnotationConfiguration addResource(String resourceName, ClassLoader classLoader) throws MappingException {
945 super.addResource( resourceName, classLoader );
946 return this;
947 }
948
949 @Override
950 public AnnotationConfiguration addDocument(org.w3c.dom.Document doc) throws MappingException {
951 super.addDocument( doc );
952 return this;
953 }
954
955 @Override
956 public AnnotationConfiguration addResource(String resourceName) throws MappingException {
957 super.addResource( resourceName );
958 return this;
959 }
960
961 @Override
962 public AnnotationConfiguration addClass(Class persistentClass) throws MappingException {
963 super.addClass( persistentClass );
964 return this;
965 }
966
967 @Override
968 public AnnotationConfiguration addJar(File jar) throws MappingException {
969 super.addJar( jar );
970 return this;
971 }
972
973 @Override
974 public AnnotationConfiguration addDirectory(File dir) throws MappingException {
975 super.addDirectory( dir );
976 return this;
977 }
978
979 @Override
980 public AnnotationConfiguration setInterceptor(Interceptor interceptor) {
981 super.setInterceptor( interceptor );
982 return this;
983 }
984
985 @Override
986 public AnnotationConfiguration setProperties(Properties properties) {
987 super.setProperties( properties );
988 return this;
989 }
990
991 @Override
992 public AnnotationConfiguration addProperties(Properties extraProperties) {
993 super.addProperties( extraProperties );
994 return this;
995 }
996
997 @Override
998 public AnnotationConfiguration mergeProperties(Properties properties) {
999 super.mergeProperties( properties );
1000 return this;
1001 }
1002
1003 @Override
1004 public AnnotationConfiguration setProperty(String propertyName, String value) {
1005 super.setProperty( propertyName, value );
1006 return this;
1007 }
1008
1009 @Override
1010 public AnnotationConfiguration configure() throws HibernateException {
1011 super.configure();
1012 return this;
1013 }
1014
1015 @Override
1016 public AnnotationConfiguration configure(String resource) throws HibernateException {
1017 super.configure( resource );
1018 return this;
1019 }
1020
1021 @Override
1022 public AnnotationConfiguration configure(URL url) throws HibernateException {
1023 super.configure( url );
1024 return this;
1025 }
1026
1027 @Override
1028 public AnnotationConfiguration configure(File configFile) throws HibernateException {
1029 super.configure( configFile );
1030 return this;
1031 }
1032
1033 @Override
1034 protected AnnotationConfiguration doConfigure(InputStream stream, String resourceName) throws HibernateException {
1035 super.doConfigure( stream, resourceName );
1036 return this;
1037 }
1038
1039 @Override
1040 public AnnotationConfiguration configure(org.w3c.dom.Document document) throws HibernateException {
1041 super.configure( document );
1042 return this;
1043 }
1044
1045 @Override
1046 protected AnnotationConfiguration doConfigure(Document doc) throws HibernateException {
1047 super.doConfigure( doc );
1048 return this;
1049 }
1050
1051 @Override
1052 public AnnotationConfiguration setCacheConcurrencyStrategy(String clazz, String concurrencyStrategy) throws MappingException {
1053 super.setCacheConcurrencyStrategy( clazz, concurrencyStrategy );
1054 return this;
1055 }
1056
1057 @Override
1058 public AnnotationConfiguration setCollectionCacheConcurrencyStrategy(String collectionRole, String concurrencyStrategy) throws MappingException {
1059 super.setCollectionCacheConcurrencyStrategy( collectionRole, concurrencyStrategy );
1060 return this;
1061 }
1062
1063 @Override
1064 public AnnotationConfiguration setNamingStrategy(NamingStrategy namingStrategy) {
1065 super.setNamingStrategy( namingStrategy );
1066 return this;
1067 }
1068
1069 //not a public API
1070 public ReflectionManager getReflectionManager() {
1071 return reflectionManager;
1072 }
1073 }