Java tutorial
// $Id: AnnotationConfiguration.java 18924 2010-03-04 21:55:10Z hardy.ferentschik $ /* * Hibernate, Relational Persistence for Idiomatic Java * * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat Middleware LLC. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.hibernate.cfg; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.ResourceBundle; import java.util.Set; import java.util.StringTokenizer; import javax.persistence.Embeddable; import javax.persistence.Entity; import javax.persistence.MappedSuperclass; import javax.persistence.MapsId; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.hibernate.AnnotationException; import org.hibernate.DuplicateMappingException; import org.hibernate.HibernateException; import org.hibernate.Interceptor; import org.hibernate.MappingException; import org.hibernate.SessionFactory; import org.hibernate.annotations.AnyMetaDef; import org.hibernate.annotations.Cache; import org.hibernate.annotations.common.AssertionFailure; import org.hibernate.annotations.common.reflection.MetadataProvider; import org.hibernate.annotations.common.reflection.MetadataProviderInjector; import org.hibernate.annotations.common.reflection.ReflectionManager; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.java.JavaReflectionManager; import org.hibernate.cfg.annotations.Version; import org.hibernate.cfg.annotations.reflection.JPAMetadataProvider; import org.hibernate.cfg.beanvalidation.BeanValidationActivator; import org.hibernate.engine.NamedQueryDefinition; import org.hibernate.engine.NamedSQLQueryDefinition; import org.hibernate.engine.ResultSetMappingDefinition; import org.hibernate.event.EventListeners; import org.hibernate.event.PreInsertEventListener; import org.hibernate.event.PreUpdateEventListener; import org.hibernate.mapping.Column; import org.hibernate.mapping.FetchProfile; import org.hibernate.mapping.IdGenerator; import org.hibernate.mapping.Join; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Table; import org.hibernate.mapping.UniqueKey; import org.hibernate.util.CollectionHelper; import org.hibernate.util.JoinedIterator; import org.hibernate.util.ReflectHelper; import org.hibernate.util.StringHelper; /** * Similar to the {@link Configuration} object but handles EJB3 and Hibernate * specific annotations as a metadata facility. * * @author Emmanuel Bernard * @author Hardy Ferentschik */ public class AnnotationConfiguration extends Configuration { private Logger log = LoggerFactory.getLogger(AnnotationConfiguration.class); /** * Setting used to give the name of the default {@link org.hibernate.annotations.CacheConcurrencyStrategy} * to use when either {@link javax.persistence.Cacheable @Cacheable} or * {@link Cache @Cache} is used. {@link Cache @Cache(strategy="..")} is used to override. */ public static final String DEFAULT_CACHE_CONCURRENCY_STRATEGY = "hibernate.cache.default_cache_concurrency_strategy"; /** * Setting which indicates whether or not the new {@link org.hibernate.id.IdentifierGenerator} are used * for AUTO, TABLE and SEQUENCE. * Default to false to keep backward compatibility. */ public static final String USE_NEW_ID_GENERATOR_MAPPINGS = "hibernate.id.new_generator_mappings"; /** * Class name of the class needed to enable Search. */ private static final String SEARCH_STARTUP_CLASS = "org.hibernate.search.event.EventListenerRegister"; /** * Method to call to enable Search. */ private static final String SEARCH_STARTUP_METHOD = "enableHibernateSearch"; static { Version.touch(); //touch version } public static final String ARTEFACT_PROCESSING_ORDER = "hibernate.mapping.precedence"; public static final ConfigurationArtefactType[] DEFAULT_ARTEFACT_PROCESSING_ORDER = new ConfigurationArtefactType[] { ConfigurationArtefactType.HBM, ConfigurationArtefactType.CLASS }; private Map<String, IdGenerator> namedGenerators; private Map<String, Map<String, Join>> joins; private Map<String, AnnotatedClassType> classTypes; private Set<String> defaultNamedQueryNames; private Set<String> defaultNamedNativeQueryNames; private Set<String> defaultSqlResulSetMappingNames; private Set<String> defaultNamedGenerators; private Map<String, Properties> generatorTables; private Map<Table, List<UniqueConstraintHolder>> uniqueConstraintHoldersByTable; private Map<String, String> mappedByResolver; private Map<String, String> propertyRefResolver; private Map<String, AnyMetaDef> anyMetaDefs; private List<XClass> annotatedClasses; private Map<String, XClass> annotatedClassEntities; private Map<String, Document> hbmEntities; private List<CacheHolder> caches; private List<Document> hbmDocuments; //user ordering matters, hence the list private List<ConfigurationArtefactType> configurationArtefactPrecedence; private boolean inSecondPass = false; private transient ReflectionManager reflectionManager; private boolean isDefaultProcessed = false; private boolean isValidatorNotPresentLogged; private Map<XClass, Map<String, PropertyData>> propertiesAnnotatedWithMapsId; private Map<XClass, Map<String, PropertyData>> propertiesAnnotatedWithIdAndToOne; public AnnotationConfiguration() { super(); } public AnnotationConfiguration(SettingsFactory sf) { super(sf); } /** * Takes the list of entities annotated with {@code @Entity} or {@code @MappedSuperclass} and returns them in an * ordered list. * * @param original The list of all entities annotated with {@code @Entity} or {@code @MappedSuperclass} * * @return Ordered list of entities including superclasses for entities which have any. Class hierachies are * listed bottom up (starting from the top level base class). There is no indication in the list when a new class * (hierarchy) starts. */ protected List<XClass> orderAndFillHierarchy(List<XClass> original) { List<XClass> copy = new ArrayList<XClass>(original); insertMappedSuperclasses(original, copy); // order the hierarchy List<XClass> workingCopy = new ArrayList<XClass>(copy); List<XClass> newList = new ArrayList<XClass>(copy.size()); while (workingCopy.size() > 0) { XClass clazz = workingCopy.get(0); orderHierarchy(workingCopy, newList, copy, clazz); } return newList; } private void insertMappedSuperclasses(List<XClass> original, List<XClass> copy) { for (XClass clazz : original) { XClass superClass = clazz.getSuperclass(); while (superClass != null && !reflectionManager.equals(superClass, Object.class) && !copy.contains(superClass)) { if (superClass.isAnnotationPresent(Entity.class) || superClass.isAnnotationPresent(MappedSuperclass.class)) { copy.add(superClass); } superClass = superClass.getSuperclass(); } } } private void orderHierarchy(List<XClass> copy, List<XClass> newList, List<XClass> original, XClass clazz) { if (clazz == null || reflectionManager.equals(clazz, Object.class)) { return; } //process superclass first orderHierarchy(copy, newList, original, clazz.getSuperclass()); if (original.contains(clazz)) { if (!newList.contains(clazz)) { newList.add(clazz); } copy.remove(clazz); } } /** * Read a mapping from the class annotation metadata (JSR 175). * * @param persistentClass the mapped class * * @return the configuration object * * @throws MappingException in case there is a configuration error for the specified class */ public AnnotationConfiguration addAnnotatedClass(Class persistentClass) throws MappingException { XClass persistentXClass = reflectionManager.toXClass(persistentClass); try { annotatedClasses.add(persistentXClass); return this; } catch (MappingException me) { log.error("Could not compile the mapping annotations", me); throw me; } } /** * Read package level metadata. * * @param packageName java package name * * @return the configuration object * * @throws MappingException in case there is an error in the mapping data */ public AnnotationConfiguration addPackage(String packageName) throws MappingException { log.info("Mapping package {}", packageName); try { AnnotationBinder.bindPackage(packageName, createExtendedMappings()); return this; } catch (MappingException me) { log.error("Could not compile the mapping annotations", me); throw me; } } public ExtendedMappings createExtendedMappings() { return new ExtendedMappingsImpl(); } @Override public void setCacheConcurrencyStrategy(String clazz, String concurrencyStrategy, String region, boolean cacheLazyProperty) throws MappingException { caches.add(new CacheHolder(clazz, concurrencyStrategy, region, true, cacheLazyProperty)); } @Override public void setCollectionCacheConcurrencyStrategy(String collectionRole, String concurrencyStrategy, String region) throws MappingException { caches.add(new CacheHolder(collectionRole, concurrencyStrategy, region, false, false)); } @Override protected void reset() { super.reset(); namedGenerators = new HashMap<String, IdGenerator>(); joins = new HashMap<String, Map<String, Join>>(); classTypes = new HashMap<String, AnnotatedClassType>(); generatorTables = new HashMap<String, Properties>(); defaultNamedQueryNames = new HashSet<String>(); defaultNamedNativeQueryNames = new HashSet<String>(); defaultSqlResulSetMappingNames = new HashSet<String>(); defaultNamedGenerators = new HashSet<String>(); uniqueConstraintHoldersByTable = new HashMap<Table, List<UniqueConstraintHolder>>(); mappedByResolver = new HashMap<String, String>(); propertyRefResolver = new HashMap<String, String>(); annotatedClasses = new ArrayList<XClass>(); caches = new ArrayList<CacheHolder>(); hbmEntities = new HashMap<String, Document>(); annotatedClassEntities = new HashMap<String, XClass>(); hbmDocuments = new ArrayList<Document>(); namingStrategy = EJB3NamingStrategy.INSTANCE; setEntityResolver(new EJB3DTDEntityResolver()); anyMetaDefs = new HashMap<String, AnyMetaDef>(); propertiesAnnotatedWithMapsId = new HashMap<XClass, Map<String, PropertyData>>(); propertiesAnnotatedWithIdAndToOne = new HashMap<XClass, Map<String, PropertyData>>(); reflectionManager = new JavaReflectionManager(); ((MetadataProviderInjector) reflectionManager).setMetadataProvider(new JPAMetadataProvider()); configurationArtefactPrecedence = Collections.emptyList(); } @Override protected void secondPassCompile() throws MappingException { log.debug("Execute first pass mapping processing"); //build annotatedClassEntities { List<XClass> tempAnnotatedClasses = new ArrayList<XClass>(annotatedClasses.size()); for (XClass clazz : annotatedClasses) { if (clazz.isAnnotationPresent(Entity.class)) { annotatedClassEntities.put(clazz.getName(), clazz); tempAnnotatedClasses.add(clazz); } else if (clazz.isAnnotationPresent(MappedSuperclass.class)) { tempAnnotatedClasses.add(clazz); } //only keep MappedSuperclasses and Entity in this list } annotatedClasses = tempAnnotatedClasses; } //process default values first if (!isDefaultProcessed) { //use global delimiters if orm.xml declare it final Object isDelimited = reflectionManager.getDefaults().get("delimited-identifier"); if (isDelimited != null && isDelimited == Boolean.TRUE) { getProperties().put(Environment.GLOBALLY_QUOTED_IDENTIFIERS, "true"); } AnnotationBinder.bindDefaults(createExtendedMappings()); isDefaultProcessed = true; } //process entities if (configurationArtefactPrecedence.isEmpty() && StringHelper.isNotEmpty(getProperties().getProperty(ARTEFACT_PROCESSING_ORDER))) { configurationArtefactPrecedence = parsePrecedence( getProperties().getProperty(ARTEFACT_PROCESSING_ORDER)); } if (configurationArtefactPrecedence.isEmpty()) { configurationArtefactPrecedence = Arrays.asList(DEFAULT_ARTEFACT_PROCESSING_ORDER); } configurationArtefactPrecedence = Collections.unmodifiableList(configurationArtefactPrecedence); for (ConfigurationArtefactType p : configurationArtefactPrecedence) { removeConflictedArtifact(p); processArtifactsOfType(p); } int cacheNbr = caches.size(); for (int index = 0; index < cacheNbr; index++) { CacheHolder cacheHolder = caches.get(index); if (cacheHolder.isClass) { super.setCacheConcurrencyStrategy(cacheHolder.role, cacheHolder.usage, cacheHolder.region, cacheHolder.cacheLazy); } else { super.setCollectionCacheConcurrencyStrategy(cacheHolder.role, cacheHolder.usage, cacheHolder.region); } } caches.clear(); try { inSecondPass = true; processSecondPassesOfType(PkDrivenByDefaultMapsIdSecondPass.class); processSecondPassesOfType(SetSimpleValueTypeSecondPass.class); processSecondPassesOfType(CopyIdentifierComponentSecondPass.class); processFkSecondPassInOrder(); processSecondPassesOfType(CreateKeySecondPass.class); processSecondPassesOfType(SecondaryTableSecondPass.class); super.secondPassCompile(); inSecondPass = false; } catch (RecoverableException e) { //the exception was not recoverable after all throw (RuntimeException) e.getCause(); } for (Map.Entry<Table, List<UniqueConstraintHolder>> tableListEntry : uniqueConstraintHoldersByTable .entrySet()) { final Table table = tableListEntry.getKey(); final List<UniqueConstraintHolder> uniqueConstraints = tableListEntry.getValue(); int uniqueIndexPerTable = 0; for (UniqueConstraintHolder holder : uniqueConstraints) { uniqueIndexPerTable++; final String keyName = StringHelper.isEmpty(holder.getName()) ? "key" + uniqueIndexPerTable : holder.getName(); buildUniqueKeyFromColumnNames(table, keyName, holder.getColumns()); } } applyConstraintsToDDL(); } private void processSecondPassesOfType(Class<? extends SecondPass> type) { Iterator iter = secondPasses.iterator(); while (iter.hasNext()) { SecondPass sp = (SecondPass) iter.next(); //do the second pass of simple value types first and remove them if (type.isInstance(sp)) { sp.doSecondPass(classes); iter.remove(); } } } private void applyConstraintsToDDL() { boolean applyOnDdl = getProperties().getProperty("hibernate.validator.apply_to_ddl", "true") .equalsIgnoreCase("true"); if (!applyOnDdl) { return; // nothing to do in this case } applyHibernateValidatorLegacyConstraintsOnDDL(); applyBeanValidationConstraintsOnDDL(); } private void applyHibernateValidatorLegacyConstraintsOnDDL() { //TODO search for the method only once and cache it? Constructor validatorCtr = null; Method applyMethod = null; try { Class classValidator = ReflectHelper.classForName("org.hibernate.validator.ClassValidator", this.getClass()); Class messageInterpolator = ReflectHelper.classForName("org.hibernate.validator.MessageInterpolator", this.getClass()); validatorCtr = classValidator.getDeclaredConstructor(Class.class, ResourceBundle.class, messageInterpolator, Map.class, ReflectionManager.class); applyMethod = classValidator.getMethod("apply", PersistentClass.class); } catch (ClassNotFoundException e) { if (!isValidatorNotPresentLogged) { log.info("Hibernate Validator not found: ignoring"); } isValidatorNotPresentLogged = true; } catch (NoSuchMethodException e) { throw new AnnotationException(e); } if (applyMethod != null) { for (PersistentClass persistentClazz : (Collection<PersistentClass>) classes.values()) { //integrate the validate framework String className = persistentClazz.getClassName(); if (StringHelper.isNotEmpty(className)) { try { Object validator = validatorCtr.newInstance(ReflectHelper.classForName(className), null, null, null, reflectionManager); applyMethod.invoke(validator, persistentClazz); } catch (Exception e) { log.warn("Unable to apply constraints on DDL for " + className, e); } } } } } private void applyBeanValidationConstraintsOnDDL() { BeanValidationActivator.applyDDL((Collection<PersistentClass>) classes.values(), getProperties()); } /** * Processes FKSecondPass instances trying to resolve any * graph circularity (ie PK made of a many to one linking to * an entity having a PK made of a ManyToOne ...). */ private void processFkSecondPassInOrder() { log.debug("processing fk mappings (*ToOne and JoinedSubclass)"); List<FkSecondPass> fkSecondPasses = getFKSecondPassesOnly(); if (fkSecondPasses.size() == 0) { return; // nothing to do here } // split FkSecondPass instances into primary key and non primary key FKs. // While doing so build a map of class names to FkSecondPass instances depending on this class. Map<String, Set<FkSecondPass>> isADependencyOf = new HashMap<String, Set<FkSecondPass>>(); List endOfQueueFkSecondPasses = new ArrayList(fkSecondPasses.size()); for (FkSecondPass sp : fkSecondPasses) { if (sp.isInPrimaryKey()) { String referenceEntityName = sp.getReferencedEntityName(); PersistentClass classMapping = getClassMapping(referenceEntityName); String dependentTable = classMapping.getTable().getQuotedName(); if (!isADependencyOf.containsKey(dependentTable)) { isADependencyOf.put(dependentTable, new HashSet<FkSecondPass>()); } isADependencyOf.get(dependentTable).add(sp); } else { endOfQueueFkSecondPasses.add(sp); } } // using the isADependencyOf map we order the FkSecondPass recursively instances into the right order for processing List<FkSecondPass> orderedFkSecondPasses = new ArrayList(fkSecondPasses.size()); for (String tableName : isADependencyOf.keySet()) { buildRecursiveOrderedFkSecondPasses(orderedFkSecondPasses, isADependencyOf, tableName, tableName); } // process the ordered FkSecondPasses for (FkSecondPass sp : orderedFkSecondPasses) { sp.doSecondPass(classes); } processEndOfQueue(endOfQueueFkSecondPasses); } private void processEndOfQueue(List endOfQueueFkSecondPasses) { /* * If a second pass raises a recoverableException, queue it for next round * stop of no pass has to be processed or if the number of pass to processes * does not diminish between two rounds. * If some failing pass remain, raise the original exception */ boolean stopProcess = false; RuntimeException originalException = null; while (!stopProcess) { List failingSecondPasses = new ArrayList(); Iterator it = endOfQueueFkSecondPasses.listIterator(); while (it.hasNext()) { final SecondPass pass = (SecondPass) it.next(); try { pass.doSecondPass(classes); } catch (RecoverableException e) { failingSecondPasses.add(pass); if (originalException == null) { originalException = (RuntimeException) e.getCause(); } } } stopProcess = failingSecondPasses.size() == 0 || failingSecondPasses.size() == endOfQueueFkSecondPasses.size(); endOfQueueFkSecondPasses = failingSecondPasses; } if (endOfQueueFkSecondPasses.size() > 0) { throw originalException; } } /** * @return Returns a list of all <code>secondPasses</code> instances which are a instance of * <code>FkSecondPass</code>. */ private List<FkSecondPass> getFKSecondPassesOnly() { Iterator iter = secondPasses.iterator(); List<FkSecondPass> fkSecondPasses = new ArrayList<FkSecondPass>(secondPasses.size()); while (iter.hasNext()) { SecondPass sp = (SecondPass) iter.next(); //do the second pass of fk before the others and remove them if (sp instanceof FkSecondPass) { fkSecondPasses.add((FkSecondPass) sp); iter.remove(); } } return fkSecondPasses; } /** * Recursively builds a list of FkSecondPass instances ready to be processed in this order. * Checking all dependencies recursively seems quite expensive, but the original code just relied * on some sort of table name sorting which failed in certain circumstances. * <p/> * See <tt>ANN-722</tt> and <tt>ANN-730</tt> * * @param orderedFkSecondPasses The list containing the <code>FkSecondPass<code> instances ready * for processing. * @param isADependencyOf Our lookup data structure to determine dependencies between tables * @param startTable Table name to start recursive algorithm. * @param currentTable The current table name used to check for 'new' dependencies. */ private void buildRecursiveOrderedFkSecondPasses(List orderedFkSecondPasses, Map<String, Set<FkSecondPass>> isADependencyOf, String startTable, String currentTable) { Set<FkSecondPass> dependencies = isADependencyOf.get(currentTable); // bottom out if (dependencies == null || dependencies.size() == 0) { return; } for (FkSecondPass sp : dependencies) { String dependentTable = sp.getValue().getTable().getQuotedName(); if (dependentTable.compareTo(startTable) == 0) { StringBuilder sb = new StringBuilder( "Foreign key circularity dependency involving the following tables: "); throw new AnnotationException(sb.toString()); } buildRecursiveOrderedFkSecondPasses(orderedFkSecondPasses, isADependencyOf, startTable, dependentTable); if (!orderedFkSecondPasses.contains(sp)) { orderedFkSecondPasses.add(0, sp); } } } private void processArtifactsOfType(ConfigurationArtefactType p) { if (ConfigurationArtefactType.HBM.equals(p)) { log.debug("Process hbm files"); for (Document document : hbmDocuments) { super.add(document); } hbmDocuments.clear(); hbmEntities.clear(); } else if (ConfigurationArtefactType.CLASS.equals(p)) { log.debug("Process annotated classes"); //bind classes in the correct order calculating some inheritance state List<XClass> orderedClasses = orderAndFillHierarchy(annotatedClasses); ExtendedMappings mappings = createExtendedMappings(); Map<XClass, InheritanceState> inheritanceStatePerClass = AnnotationBinder .buildInheritanceStates(orderedClasses, mappings); for (XClass clazz : orderedClasses) { //todo use the same extended mapping AnnotationBinder.bindClass(clazz, inheritanceStatePerClass, mappings); } annotatedClasses.clear(); annotatedClassEntities.clear(); } } private void removeConflictedArtifact(ConfigurationArtefactType p) { if (ConfigurationArtefactType.HBM.equals(p)) { for (String entity : hbmEntities.keySet()) { if (annotatedClassEntities.containsKey(entity)) { annotatedClasses.remove(annotatedClassEntities.get(entity)); annotatedClassEntities.remove(entity); } } } else if (ConfigurationArtefactType.CLASS.equals(p)) { for (String entity : annotatedClassEntities.keySet()) { if (hbmEntities.containsKey(entity)) { hbmDocuments.remove(hbmEntities.get(entity)); hbmEntities.remove(entity); } } } } private void buildUniqueKeyFromColumnNames(Table table, String keyName, String[] columnNames) { ExtendedMappings mappings = createExtendedMappings(); keyName = mappings.getObjectNameNormalizer().normalizeIdentifierQuoting(keyName); UniqueKey uc; int size = columnNames.length; Column[] columns = new Column[size]; Set<Column> unbound = new HashSet<Column>(); Set<Column> unboundNoLogical = new HashSet<Column>(); for (int index = 0; index < size; index++) { final String logicalColumnName = mappings.getObjectNameNormalizer() .normalizeIdentifierQuoting(columnNames[index]); try { final String columnName = mappings.getPhysicalColumnName(logicalColumnName, table); columns[index] = new Column(columnName); unbound.add(columns[index]); //column equals and hashcode is based on column name } catch (MappingException e) { unboundNoLogical.add(new Column(logicalColumnName)); } } for (Column column : columns) { if (table.containsColumn(column)) { uc = table.getOrCreateUniqueKey(keyName); uc.addColumn(table.getColumn(column)); unbound.remove(column); } } if (unbound.size() > 0 || unboundNoLogical.size() > 0) { StringBuilder sb = new StringBuilder("Unable to create unique key constraint ("); for (String columnName : columnNames) { sb.append(columnName).append(", "); } sb.setLength(sb.length() - 2); sb.append(") on table ").append(table.getName()).append(": "); for (Column column : unbound) { sb.append(column.getName()).append(", "); } for (Column column : unboundNoLogical) { sb.append(column.getName()).append(", "); } sb.setLength(sb.length() - 2); sb.append(" not found"); throw new AnnotationException(sb.toString()); } } @Override protected void parseMappingElement(Element subelement, String name) { Attribute rsrc = subelement.attribute("resource"); Attribute file = subelement.attribute("file"); Attribute jar = subelement.attribute("jar"); Attribute pckg = subelement.attribute("package"); Attribute clazz = subelement.attribute("class"); if (rsrc != null) { log.debug("{} <- {}", name, rsrc); addResource(rsrc.getValue()); } else if (jar != null) { log.debug("{} <- {}", name, jar); addJar(new File(jar.getValue())); } else if (file != null) { log.debug("{} <- {}", name, file); addFile(file.getValue()); } else if (pckg != null) { log.debug("{} <- {}", name, pckg); addPackage(pckg.getValue()); } else if (clazz != null) { log.debug("{} <- {}", name, clazz); Class loadedClass; try { loadedClass = ReflectHelper.classForName(clazz.getValue()); } catch (ClassNotFoundException cnf) { throw new MappingException("Unable to load class declared as <mapping class=\"" + clazz.getValue() + "\"/> in the configuration:", cnf); } catch (NoClassDefFoundError ncdf) { throw new MappingException("Unable to load class declared as <mapping class=\"" + clazz.getValue() + "\"/> in the configuration:", ncdf); } addAnnotatedClass(loadedClass); } else { throw new MappingException("<mapping> element in configuration specifies no attributes"); } } @Override protected void add(org.dom4j.Document doc) throws MappingException { boolean ejb3Xml = "entity-mappings".equals(doc.getRootElement().getName()); if (inSecondPass) { //if in second pass bypass the queueing, getExtendedQueue reuse this method if (!ejb3Xml) { super.add(doc); } } else { if (!ejb3Xml) { final Element hmNode = doc.getRootElement(); Attribute packNode = hmNode.attribute("package"); String defaultPackage = packNode != null ? packNode.getValue() : ""; Set<String> entityNames = new HashSet<String>(); findClassNames(defaultPackage, hmNode, entityNames); for (String entity : entityNames) { hbmEntities.put(entity, doc); } hbmDocuments.add(doc); } else { final MetadataProvider metadataProvider = ((MetadataProviderInjector) reflectionManager) .getMetadataProvider(); JPAMetadataProvider jpaMetadataProvider = (JPAMetadataProvider) metadataProvider; List<String> classnames = jpaMetadataProvider.getXMLContext().addDocument(doc); for (String classname : classnames) { try { annotatedClasses.add(reflectionManager.classForName(classname, this.getClass())); } catch (ClassNotFoundException e) { throw new AnnotationException("Unable to load class defined in XML: " + classname, e); } } } } } private static void findClassNames(String defaultPackage, final Element startNode, final java.util.Set<String> names) { // if we have some extends we need to check if those classes possibly could be inside the // same hbm.xml file... Iterator[] classes = new Iterator[4]; classes[0] = startNode.elementIterator("class"); classes[1] = startNode.elementIterator("subclass"); classes[2] = startNode.elementIterator("joined-subclass"); classes[3] = startNode.elementIterator("union-subclass"); Iterator classIterator = new JoinedIterator(classes); while (classIterator.hasNext()) { Element element = (Element) classIterator.next(); String entityName = element.attributeValue("entity-name"); if (entityName == null) { entityName = getClassName(element.attribute("name"), defaultPackage); } names.add(entityName); findClassNames(defaultPackage, element, names); } } private static String getClassName(Attribute name, String defaultPackage) { if (name == null) { return null; } String unqualifiedName = name.getValue(); if (unqualifiedName == null) { return null; } if (unqualifiedName.indexOf('.') < 0 && defaultPackage != null) { return defaultPackage + '.' + unqualifiedName; } return unqualifiedName; } public void setPrecedence(String precedence) { this.configurationArtefactPrecedence = parsePrecedence(precedence); } private List<ConfigurationArtefactType> parsePrecedence(String s) { if (StringHelper.isEmpty(s)) { return Collections.emptyList(); } StringTokenizer precedences = new StringTokenizer(s, ",; ", false); List<ConfigurationArtefactType> tmpPrecedences = new ArrayList<ConfigurationArtefactType>(); while (precedences.hasMoreElements()) { tmpPrecedences.add(ConfigurationArtefactType.parsePrecedence((String) precedences.nextElement())); } return tmpPrecedences; } @Override public AnnotationConfiguration addInputStream(InputStream xmlInputStream) throws MappingException { try { /* * try and parse the document: * - try and validate the document with orm_2_0.xsd * - if it fails because of the version attribute mismatch, try and validate the document with orm_1_0.xsd */ List<SAXParseException> errors = new ArrayList<SAXParseException>(); SAXReader saxReader = new SAXReader(); saxReader.setEntityResolver(getEntityResolver()); saxReader.setErrorHandler(new ErrorLogger(errors)); saxReader.setMergeAdjacentText(true); saxReader.setValidation(true); setValidationFor(saxReader, "orm_2_0.xsd"); org.dom4j.Document doc = null; try { doc = saxReader.read(new InputSource(xmlInputStream)); } catch (DocumentException e) { //the document is syntactically incorrect //DOM4J sometimes wraps the SAXParseException wo much interest final Throwable throwable = e.getCause(); if (e.getCause() == null || !(throwable instanceof SAXParseException)) { throw new MappingException("Could not parse JPA mapping document", e); } errors.add((SAXParseException) throwable); } boolean isV1Schema = false; if (errors.size() != 0) { SAXParseException exception = errors.get(0); final String errorMessage = exception.getMessage(); //does the error look like a schema mismatch? isV1Schema = doc != null && errorMessage.contains("1.0") && errorMessage.contains("2.0") && errorMessage.contains("version"); } if (isV1Schema) { //reparse with v1 errors.clear(); setValidationFor(saxReader, "orm_1_0.xsd"); try { //too bad we have to reparse to validate again :( saxReader.read(new StringReader(doc.asXML())); } catch (DocumentException e) { //oops asXML fails even if the core doc parses initially throw new AssertionFailure("Error in DOM4J leads to a bug in Hibernate", e); } } if (errors.size() != 0) { //report errors in exception StringBuilder errorMessage = new StringBuilder(); for (SAXParseException error : errors) { errorMessage.append("Error parsing XML (line").append(error.getLineNumber()) .append(" : column ").append(error.getColumnNumber()).append("): ") .append(error.getMessage()).append("\n"); } throw new MappingException("Invalid ORM mapping file.\n" + errorMessage.toString()); } add(doc); return this; } finally { try { xmlInputStream.close(); } catch (IOException ioe) { log.warn("Could not close input stream", ioe); } } } private void setValidationFor(SAXReader saxReader, String xsd) { try { saxReader.setFeature("http://apache.org/xml/features/validation/schema", true); //saxReader.setFeature( "http://apache.org/xml/features/validation/dynamic", true ); //set the default schema locators saxReader.setProperty("http://apache.org/xml/properties/schema/external-schemaLocation", "http://java.sun.com/xml/ns/persistence/orm " + xsd); } catch (SAXException e) { saxReader.setValidation(false); } } public SessionFactory buildSessionFactory() throws HibernateException { enableLegacyHibernateValidator(); enableBeanValidation(); enableHibernateSearch(); return super.buildSessionFactory(); } private void enableLegacyHibernateValidator() { //add validator events if the jar is available boolean enableValidatorListeners = !"false" .equalsIgnoreCase(getProperty("hibernate.validator.autoregister_listeners")); Class validateEventListenerClass = null; try { validateEventListenerClass = ReflectHelper.classForName( "org.hibernate.validator.event.ValidateEventListener", AnnotationConfiguration.class); } catch (ClassNotFoundException e) { //validator is not present log.debug("Legacy Validator not present in classpath, ignoring event listener registration"); } if (enableValidatorListeners && validateEventListenerClass != null) { //TODO so much duplication Object validateEventListener; try { validateEventListener = validateEventListenerClass.newInstance(); } catch (Exception e) { throw new AnnotationException("Unable to load Validator event listener", e); } { boolean present = false; PreInsertEventListener[] listeners = getEventListeners().getPreInsertEventListeners(); if (listeners != null) { for (Object eventListener : listeners) { //not isAssignableFrom since the user could subclass present = present || validateEventListenerClass == eventListener.getClass(); } if (!present) { int length = listeners.length + 1; PreInsertEventListener[] newListeners = new PreInsertEventListener[length]; System.arraycopy(listeners, 0, newListeners, 0, length - 1); newListeners[length - 1] = (PreInsertEventListener) validateEventListener; getEventListeners().setPreInsertEventListeners(newListeners); } } else { getEventListeners().setPreInsertEventListeners( new PreInsertEventListener[] { (PreInsertEventListener) validateEventListener }); } } //update event listener { boolean present = false; PreUpdateEventListener[] listeners = getEventListeners().getPreUpdateEventListeners(); if (listeners != null) { for (Object eventListener : listeners) { //not isAssignableFrom since the user could subclass present = present || validateEventListenerClass == eventListener.getClass(); } if (!present) { int length = listeners.length + 1; PreUpdateEventListener[] newListeners = new PreUpdateEventListener[length]; System.arraycopy(listeners, 0, newListeners, 0, length - 1); newListeners[length - 1] = (PreUpdateEventListener) validateEventListener; getEventListeners().setPreUpdateEventListeners(newListeners); } } else { getEventListeners().setPreUpdateEventListeners( new PreUpdateEventListener[] { (PreUpdateEventListener) validateEventListener }); } } } } private void enableBeanValidation() { BeanValidationActivator.activateBeanValidation(getEventListeners(), getProperties()); } /** * Tries to automatically register Hibernate Search event listeners by locating the * appropriate bootstrap class and calling the <code>enableHibernateSearch</code> method. */ private void enableHibernateSearch() { // load the bootstrap class Class searchStartupClass; try { searchStartupClass = ReflectHelper.classForName(SEARCH_STARTUP_CLASS, AnnotationConfiguration.class); } catch (ClassNotFoundException e) { // TODO remove this together with SearchConfiguration after 3.1.0 release of Search // try loading deprecated HibernateSearchEventListenerRegister try { searchStartupClass = ReflectHelper.classForName( "org.hibernate.cfg.search.HibernateSearchEventListenerRegister", AnnotationConfiguration.class); } catch (ClassNotFoundException cnfe) { log.debug("Search not present in classpath, ignoring event listener registration."); return; } } // call the method for registering the listeners try { Object searchStartupInstance = searchStartupClass.newInstance(); Method enableSearchMethod = searchStartupClass.getDeclaredMethod(SEARCH_STARTUP_METHOD, EventListeners.class, Properties.class); enableSearchMethod.invoke(searchStartupInstance, getEventListeners(), getProperties()); } catch (InstantiationException e) { log.debug("Unable to instantiate {}, ignoring event listener registration.", SEARCH_STARTUP_CLASS); } catch (IllegalAccessException e) { log.debug("Unable to instantiate {}, ignoring event listener registration.", SEARCH_STARTUP_CLASS); } catch (NoSuchMethodException e) { log.debug("Method enableHibernateSearch() not found in {}.", SEARCH_STARTUP_CLASS); } catch (InvocationTargetException e) { log.debug("Unable to execute {}, ignoring event listener registration.", SEARCH_STARTUP_METHOD); } } @Override public AnnotationConfiguration addFile(String xmlFile) throws MappingException { super.addFile(xmlFile); return this; } @Override public AnnotationConfiguration addFile(File xmlFile) throws MappingException { super.addFile(xmlFile); return this; } @Override public AnnotationConfiguration addCacheableFile(File xmlFile) throws MappingException { super.addCacheableFile(xmlFile); return this; } @Override public AnnotationConfiguration addCacheableFile(String xmlFile) throws MappingException { super.addCacheableFile(xmlFile); return this; } @Override public AnnotationConfiguration addXML(String xml) throws MappingException { super.addXML(xml); return this; } @Override public AnnotationConfiguration addURL(URL url) throws MappingException { super.addURL(url); return this; } @Override public AnnotationConfiguration addResource(String resourceName, ClassLoader classLoader) throws MappingException { super.addResource(resourceName, classLoader); return this; } @Override public AnnotationConfiguration addDocument(org.w3c.dom.Document doc) throws MappingException { super.addDocument(doc); return this; } @Override public AnnotationConfiguration addResource(String resourceName) throws MappingException { super.addResource(resourceName); return this; } @Override public AnnotationConfiguration addClass(Class persistentClass) throws MappingException { super.addClass(persistentClass); return this; } @Override public AnnotationConfiguration addJar(File jar) throws MappingException { super.addJar(jar); return this; } @Override public AnnotationConfiguration addDirectory(File dir) throws MappingException { super.addDirectory(dir); return this; } @Override public AnnotationConfiguration setInterceptor(Interceptor interceptor) { super.setInterceptor(interceptor); return this; } @Override public AnnotationConfiguration setProperties(Properties properties) { super.setProperties(properties); return this; } @Override public AnnotationConfiguration addProperties(Properties extraProperties) { super.addProperties(extraProperties); return this; } @Override public AnnotationConfiguration mergeProperties(Properties properties) { super.mergeProperties(properties); return this; } @Override public AnnotationConfiguration setProperty(String propertyName, String value) { super.setProperty(propertyName, value); return this; } @Override public AnnotationConfiguration configure() throws HibernateException { super.configure(); return this; } @Override public AnnotationConfiguration configure(String resource) throws HibernateException { super.configure(resource); return this; } @Override public AnnotationConfiguration configure(URL url) throws HibernateException { super.configure(url); return this; } @Override public AnnotationConfiguration configure(File configFile) throws HibernateException { super.configure(configFile); return this; } @Override protected AnnotationConfiguration doConfigure(InputStream stream, String resourceName) throws HibernateException { super.doConfigure(stream, resourceName); return this; } @Override public AnnotationConfiguration configure(org.w3c.dom.Document document) throws HibernateException { super.configure(document); return this; } @Override protected AnnotationConfiguration doConfigure(Document doc) throws HibernateException { super.doConfigure(doc); return this; } @Override public AnnotationConfiguration setCacheConcurrencyStrategy(String clazz, String concurrencyStrategy) throws MappingException { super.setCacheConcurrencyStrategy(clazz, concurrencyStrategy); return this; } @Override public AnnotationConfiguration setCollectionCacheConcurrencyStrategy(String collectionRole, String concurrencyStrategy) throws MappingException { super.setCollectionCacheConcurrencyStrategy(collectionRole, concurrencyStrategy); return this; } @Override public AnnotationConfiguration setNamingStrategy(NamingStrategy namingStrategy) { super.setNamingStrategy(namingStrategy); return this; } //not a public API public ReflectionManager getReflectionManager() { return reflectionManager; } protected class ExtendedMappingsImpl extends MappingsImpl implements ExtendedMappings { private Boolean useNewGeneratorMappings; private Collection<FetchProfile> annotationConfiguredProfile; public ExtendedMappingsImpl() { annotationConfiguredProfile = new ArrayList<FetchProfile>(); } public void addDefaultGenerator(IdGenerator generator) { this.addGenerator(generator); defaultNamedGenerators.add(generator.getName()); } public boolean isInSecondPass() { return inSecondPass; } public PropertyData getPropertyAnnotatedWithMapsId(XClass entityType, String propertyName) { final Map<String, PropertyData> map = propertiesAnnotatedWithMapsId.get(entityType); return map == null ? null : map.get(propertyName); } public void addPropertyAnnotatedWithMapsId(XClass entityType, PropertyData property) { Map<String, PropertyData> map = propertiesAnnotatedWithMapsId.get(entityType); if (map == null) { map = new HashMap<String, PropertyData>(); propertiesAnnotatedWithMapsId.put(entityType, map); } map.put(property.getProperty().getAnnotation(MapsId.class).value(), property); } public PropertyData getPropertyAnnotatedWithIdAndToOne(XClass entityType, String propertyName) { final Map<String, PropertyData> map = propertiesAnnotatedWithIdAndToOne.get(entityType); return map == null ? null : map.get(propertyName); } public void addToOneAndIdProperty(XClass entityType, PropertyData property) { Map<String, PropertyData> map = propertiesAnnotatedWithIdAndToOne.get(entityType); if (map == null) { map = new HashMap<String, PropertyData>(); propertiesAnnotatedWithIdAndToOne.put(entityType, map); } map.put(property.getPropertyName(), property); } @SuppressWarnings({ "UnnecessaryUnboxing" }) public boolean useNewGeneratorMappings() { if (useNewGeneratorMappings == null) { final String booleanName = getConfigurationProperties().getProperty(USE_NEW_ID_GENERATOR_MAPPINGS); useNewGeneratorMappings = Boolean.valueOf(booleanName); } return useNewGeneratorMappings.booleanValue(); } public IdGenerator getGenerator(String name) { return getGenerator(name, null); } public IdGenerator getGenerator(String name, Map<String, IdGenerator> localGenerators) { if (localGenerators != null) { IdGenerator result = localGenerators.get(name); if (result != null) { return result; } } return namedGenerators.get(name); } public void addGenerator(IdGenerator generator) { if (!defaultNamedGenerators.contains(generator.getName())) { IdGenerator old = namedGenerators.put(generator.getName(), generator); if (old != null) { log.warn("duplicate generator name {}", old.getName()); } } } public void addGeneratorTable(String name, Properties params) { Object old = generatorTables.put(name, params); if (old != null) { log.warn("duplicate generator table: {}", name); } } public Properties getGeneratorTableProperties(String name, Map<String, Properties> localGeneratorTables) { if (localGeneratorTables != null) { Properties result = localGeneratorTables.get(name); if (result != null) { return result; } } return generatorTables.get(name); } public Map<String, Join> getJoins(String entityName) { return joins.get(entityName); } public void addJoins(PersistentClass persistentClass, Map<String, Join> joins) { Object old = AnnotationConfiguration.this.joins.put(persistentClass.getEntityName(), joins); if (old != null) { log.warn("duplicate joins for class: {}", persistentClass.getEntityName()); } } public AnnotatedClassType getClassType(XClass clazz) { AnnotatedClassType type = classTypes.get(clazz.getName()); if (type == null) { return addClassType(clazz); } else { return type; } } //FIXME should be private but is part of the ExtendedMapping contract public AnnotatedClassType addClassType(XClass clazz) { AnnotatedClassType type; if (clazz.isAnnotationPresent(Entity.class)) { type = AnnotatedClassType.ENTITY; } else if (clazz.isAnnotationPresent(Embeddable.class)) { type = AnnotatedClassType.EMBEDDABLE; } else if (clazz.isAnnotationPresent(MappedSuperclass.class)) { type = AnnotatedClassType.EMBEDDABLE_SUPERCLASS; } else { type = AnnotatedClassType.NONE; } classTypes.put(clazz.getName(), type); return type; } /** * {@inheritDoc} */ public Map<Table, List<String[]>> getTableUniqueConstraints() { final Map<Table, List<String[]>> deprecatedStructure = new HashMap<Table, List<String[]>>( CollectionHelper.determineProperSizing(getUniqueConstraintHoldersByTable()), CollectionHelper.LOAD_FACTOR); for (Map.Entry<Table, List<UniqueConstraintHolder>> entry : getUniqueConstraintHoldersByTable() .entrySet()) { List<String[]> columnsPerConstraint = new ArrayList<String[]>( CollectionHelper.determineProperSizing(entry.getValue().size())); deprecatedStructure.put(entry.getKey(), columnsPerConstraint); for (UniqueConstraintHolder holder : entry.getValue()) { columnsPerConstraint.add(holder.getColumns()); } } return deprecatedStructure; } public Map<Table, List<UniqueConstraintHolder>> getUniqueConstraintHoldersByTable() { return uniqueConstraintHoldersByTable; } @SuppressWarnings({ "unchecked" }) public void addUniqueConstraints(Table table, List uniqueConstraints) { List<UniqueConstraintHolder> constraintHolders = new ArrayList<UniqueConstraintHolder>( CollectionHelper.determineProperSizing(uniqueConstraints.size())); int keyNameBase = determineCurrentNumberOfUniqueConstraintHolders(table); for (String[] columns : (List<String[]>) uniqueConstraints) { final String keyName = "key" + keyNameBase++; constraintHolders.add(new UniqueConstraintHolder().setName(keyName).setColumns(columns)); } addUniqueConstraintHolders(table, constraintHolders); } private int determineCurrentNumberOfUniqueConstraintHolders(Table table) { List currentHolders = getUniqueConstraintHoldersByTable().get(table); return currentHolders == null ? 0 : currentHolders.size(); } public void addUniqueConstraintHolders(Table table, List<UniqueConstraintHolder> uniqueConstraintHolders) { List<UniqueConstraintHolder> holderList = getUniqueConstraintHoldersByTable().get(table); if (holderList == null) { holderList = new ArrayList<UniqueConstraintHolder>(); getUniqueConstraintHoldersByTable().put(table, holderList); } holderList.addAll(uniqueConstraintHolders); } public void addMappedBy(String entityName, String propertyName, String inversePropertyName) { mappedByResolver.put(entityName + "." + propertyName, inversePropertyName); } public String getFromMappedBy(String entityName, String propertyName) { return mappedByResolver.get(entityName + "." + propertyName); } public void addPropertyReferencedAssociation(String entityName, String propertyName, String propertyRef) { propertyRefResolver.put(entityName + "." + propertyName, propertyRef); } public String getPropertyReferencedAssociation(String entityName, String propertyName) { return propertyRefResolver.get(entityName + "." + propertyName); } @Override public void addUniquePropertyReference(String referencedClass, String propertyName) { super.addUniquePropertyReference(referencedClass, propertyName); } @Override public void addPropertyReference(String referencedClass, String propertyName) { super.addPropertyReference(referencedClass, propertyName); } public ReflectionManager getReflectionManager() { return reflectionManager; } public void addDefaultQuery(String name, NamedQueryDefinition query) { super.addQuery(name, query); defaultNamedQueryNames.add(name); } public void addDefaultSQLQuery(String name, NamedSQLQueryDefinition query) { super.addSQLQuery(name, query); defaultNamedNativeQueryNames.add(name); } public void addDefaultResultSetMapping(ResultSetMappingDefinition definition) { final String name = definition.getName(); if (!defaultSqlResulSetMappingNames.contains(name) && super.getResultSetMapping(name) != null) { removeResultSetMapping(name); } super.addResultSetMapping(definition); defaultSqlResulSetMappingNames.add(name); } @Override public void addQuery(String name, NamedQueryDefinition query) throws DuplicateMappingException { if (!defaultNamedQueryNames.contains(name)) { super.addQuery(name, query); } } @Override public void addResultSetMapping(ResultSetMappingDefinition definition) throws DuplicateMappingException { if (!defaultSqlResulSetMappingNames.contains(definition.getName())) { super.addResultSetMapping(definition); } } @Override public void addSQLQuery(String name, NamedSQLQueryDefinition query) throws DuplicateMappingException { if (!defaultNamedNativeQueryNames.contains(name)) { super.addSQLQuery(name, query); } } public Map getClasses() { return classes; } public void addAnyMetaDef(AnyMetaDef defAnn) throws AnnotationException { if (anyMetaDefs.containsKey(defAnn.name())) { throw new AnnotationException("Two @AnyMetaDef with the same name defined: " + defAnn.name()); } anyMetaDefs.put(defAnn.name(), defAnn); } public AnyMetaDef getAnyMetaDef(String name) { return anyMetaDefs.get(name); } public void addAnnotationConfiguredFetchProfile(FetchProfile fetchProfile) { annotationConfiguredProfile.add(fetchProfile); } public boolean containsAnnotationConfiguredFetchProfile(FetchProfile fetchProfile) { for (FetchProfile profile : annotationConfiguredProfile) { // we need reference equality there!! if (profile == fetchProfile) { return true; } } return false; } } private static class CacheHolder { public CacheHolder(String role, String usage, String region, boolean isClass, boolean cacheLazy) { this.role = role; this.usage = usage; this.region = region; this.isClass = isClass; this.cacheLazy = cacheLazy; } public String role; public String usage; public String region; public boolean isClass; public boolean cacheLazy; } private static class ErrorLogger implements ErrorHandler { private List<SAXParseException> errors; public ErrorLogger(List<SAXParseException> errors) { this.errors = errors; } public void warning(SAXParseException exception) throws SAXException { errors.add(exception); } public void error(SAXParseException exception) throws SAXException { errors.add(exception); } public void fatalError(SAXParseException exception) throws SAXException { } } }