/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates, IBM Corporation. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink * 05/16/2008-1.0M8 Guy Pelletier * - 218084: Implement metadata merging functionality between mapping files * 05/23/2008-1.0M8 Guy Pelletier * - 211330: Add attributes-complete support to the EclipseLink-ORM.XML Schema * 12/10/2008-1.1 Michael O'Brien * - 257606: Add orm.xml schema validation true/(false) flag support in persistence.xml * 01/28/2009-2.0 Guy Pelletier * - 248293: JPA 2.0 Element Collections (part 1) * 03/27/2009-2.0 Guy Pelletier * - 241413: JPA 2.0 Add EclipseLink support for Map type attributes * 03/08/2010-2.1 Guy Pelletier * - 303632: Add attribute-type for mapping attributes to EclipseLink-ORM * 05/14/2010-2.1 Guy Pelletier * - 253083: Add support for dynamic persistence using ORM.xml/eclipselink-orm.xml * 04/01/2011-2.3 Guy Pelletier * - 337323: Multi-tenant with shared schema support (part 2) * 09/20/2011-2.3.1 Guy Pelletier * - 357476: Change caching default to ISOLATED for multitenant's using a shared EMF. * 06/20/2012-2.5 Guy Pelletier * - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls * 10/09/2012-2.5 Guy Pelletier * - 374688: JPA 2.1 Converter support * 08/18/2014-2.5 Jody Grassel (IBM Corporation) * - 440802: xml-mapping-metadata-complete does not exclude @Entity annotated entities ******************************************************************************/ package org.eclipse.persistence.internal.jpa.metadata; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.persistence.spi.PersistenceUnitInfo; import org.eclipse.persistence.config.DescriptorCustomizer; import org.eclipse.persistence.config.PersistenceUnitProperties; import org.eclipse.persistence.exceptions.PersistenceUnitLoadingException; import org.eclipse.persistence.exceptions.ValidationException; import org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl; import org.eclipse.persistence.internal.jpa.deployment.PersistenceUnitProcessor; import org.eclipse.persistence.internal.jpa.deployment.PersistenceUnitProcessor.Mode; import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.ClassAccessor; import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.ConverterAccessor; import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.EmbeddableAccessor; import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.EntityAccessor; import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.MappedSuperclassAccessor; import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataAsmFactory; import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataClass; import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataFactory; import org.eclipse.persistence.internal.jpa.metadata.converters.StructConverterMetadata; import org.eclipse.persistence.internal.jpa.metadata.xml.XMLEntityMappings; import org.eclipse.persistence.internal.jpa.metadata.xml.XMLEntityMappingsReader; import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.jpa.Archive; import org.eclipse.persistence.jpa.metadata.MetadataSource; import org.eclipse.persistence.logging.AbstractSessionLog; import org.eclipse.persistence.logging.SessionLog; /** * INTERNAL: * The object/relational metadata processor for the EJB3.0 specification. * * @author Guy Pelletier * @since TopLink EJB 3.0 Reference Implementation */ public class MetadataProcessor { protected ClassLoader m_loader; protected MetadataFactory m_factory; protected MetadataProject m_project; protected AbstractSession m_session; protected Map m_predeployProperties; protected MetadataProcessor m_compositeProcessor; protected Set<MetadataProcessor> m_compositeMemberProcessors; protected MetadataSource m_metadataSource; /** * INTERNAL: * Empty processor to be used as a composite processor. */ public MetadataProcessor() {} /** * INTERNAL: * Called from EntityManagerSetupImpl. The 'real' EJB 3.0 processing * that includes XML and annotations. */ public MetadataProcessor(PersistenceUnitInfo puInfo, AbstractSession session, ClassLoader loader, boolean weaveLazy, boolean weaveEager, boolean weaveFetchGroups, boolean multitenantSharedEmf, boolean multitenantSharedCache, Map predeployProperties, MetadataProcessor compositeProcessor) { m_loader = loader; m_session = session; m_project = new MetadataProject(puInfo, session, weaveLazy, weaveEager, weaveFetchGroups, multitenantSharedEmf, multitenantSharedCache); m_predeployProperties = predeployProperties; m_compositeProcessor = compositeProcessor; if (m_compositeProcessor != null) { m_compositeProcessor.addCompositeMemberProcessor(this); m_project.setCompositeProcessor(m_compositeProcessor); } } /** * INTERNAL: * Add containedProcessor to compositeProcessor. */ public void addCompositeMemberProcessor(MetadataProcessor compositeMemberProcessor) { if (m_compositeMemberProcessors == null) { m_compositeMemberProcessors = new HashSet(); } m_compositeMemberProcessors.add(compositeMemberProcessor); } /** * INTERNAL: * Method to place EntityListener's on the descriptors from the given * session. This call is made from the EntityManagerSetup deploy call. */ public void addEntityListeners() { // Process the listeners for all the class accessors, but before // doing so, update the accessors associated class since the loader // should have changed changed. for (EntityAccessor accessor : m_project.getEntityAccessors()) { accessor.setJavaClass(accessor.getDescriptor().getJavaClass()); accessor.processListeners(m_loader); } } /** * INTERNAL: * Method to place NamedQueries and NamedNativeQueries on the given session. * This call is made from the EntityManagerSetup deploy call. */ public void addNamedQueries() { m_project.processQueries(); } /** * INTERNAL: * During EntityManagerSetup deploy, using the real class loader we must * create our dynamic classes. */ public void createDynamicClasses() { m_project.createDynamicClasses(m_loader); } public void createRestInterfaces(){ m_project.createRestInterfaces(m_loader); } /** * INTERNAL: * Return compositeProcessor. */ public MetadataProcessor getCompositeProcessor() { return m_compositeProcessor; } /** * INTERNAL: */ public MetadataFactory getMetadataFactory() { return m_factory; } /** * INTERNAL: */ public MetadataSource getMetadataSource(){ return m_metadataSource; } /** * INTERNAL: * Returns projects owned by compositeProcessor minus the passed project. */ public Set<MetadataProject> getPearProjects(MetadataProject project) { Set<MetadataProject> pearProjects = new HashSet(); if (m_compositeMemberProcessors != null) { for(MetadataProcessor processor : m_compositeMemberProcessors) { MetadataProject pearProject = processor.getProject(); if(pearProject != project) { pearProjects.add(pearProject); } } } return pearProjects; } /** * INTERNAL: * Return a set of class names for each entity, embeddable and mapped * superclass found in the mapping files to be processed by the * MetadataProcessor. */ public Set<String> getPersistenceUnitClassSetFromMappingFiles() { HashSet<String> classSet = new HashSet<String>(); for (XMLEntityMappings entityMappings : m_project.getEntityMappings()) { for (ClassAccessor entity : entityMappings.getEntities()) { classSet.add(entityMappings.getPackageQualifiedClassName(entity.getClassName())); } for (ClassAccessor embeddable : entityMappings.getEmbeddables()) { classSet.add(entityMappings.getPackageQualifiedClassName(embeddable.getClassName())); } for (ClassAccessor mappedSuperclass : entityMappings.getMappedSuperclasses()) { classSet.add(entityMappings.getPackageQualifiedClassName(mappedSuperclass.getClassName())); } } return classSet; } /** * INTERNAL: */ public MetadataProject getProject() { return m_project; } /** * INTERNAL: * Adds a list of StructConverter string names that were defined in the * metadata of this project to the native EclipseLink project. * * These StructConverters can be added to the Project to be processed later */ public void addStructConverterNames() { List<String> structConverters = new ArrayList<String>(); for (StructConverterMetadata converter: m_project.getStructConverters()) { structConverters.add(converter.getConverterClassName()); } if (!structConverters.isEmpty()) { m_session.getProject().setStructConverters(structConverters); } } /** * INTERNAL: * Handle an exception that occurred while processing ORM xml. */ protected void handleORMException(RuntimeException e, String mappingFile, boolean throwException){ if (m_session == null) { // Metadata processor is mainly used with a session. Java SE // bootstrapping uses some functions such as ORM processing without // a session. In these cases, it is impossible to get the session // to properly handle the exception. As a result we log an error. // The same code will be called later in the bootstrapping code // and the error will be handled then. AbstractSessionLog.getLog().log(SessionLog.WARNING, SessionLog.METADATA, EntityManagerSetupImpl.ERROR_LOADING_XML_FILE, new Object[] {mappingFile, e}); } else if (!throwException) { // fail quietly m_session.log(SessionLog.WARNING, SessionLog.METADATA, EntityManagerSetupImpl.ERROR_LOADING_XML_FILE, new Object[] {mappingFile, e}); } else { // fail loudly m_session.handleException(e); } } /** * INTERNAL: * This method is responsible for discovering all the entity classes for * this PU and adding corresponding MetadataDescriptor in the * MetadataProject. * * This method will also gather all the weavable classes for this PU. * Currently, entity and embeddable classes are weavable. * * NOTE: The order of processing should not be changed as the steps are * dependent on one another. */ protected void initPersistenceUnitClasses() { // 1 - Iterate through the classes that are defined in the <mapping> // files and add them to the map. This will merge the accessors where // necessary. HashMap<String, EntityAccessor> entities = new HashMap<String, EntityAccessor>(); HashMap<String, EmbeddableAccessor> embeddables = new HashMap<String, EmbeddableAccessor>(); for (XMLEntityMappings entityMappings : m_project.getEntityMappings()) { entityMappings.initPersistenceUnitClasses(entities, embeddables); } // 2 - Iterate through all the XML entities and add them to the project // and apply any persistence unit defaults. for (EntityAccessor entity : entities.values()) { // This will apply global persistence unit defaults. m_project.addEntityAccessor(entity); // This will override any global settings. entity.getEntityMappings().processEntityMappingsDefaults(entity); } // 3 - Iterate though all the XML embeddables and add them to the // project and apply any persistence unit defaults. for (EmbeddableAccessor embeddable : embeddables.values()) { // This will apply global persistence unit defaults. m_project.addEmbeddableAccessor(embeddable); // This will override any global settings. embeddable.getEntityMappings().processEntityMappingsDefaults(embeddable); } // Check if the xml-mapping-metadata-complete was declared. If so, then ignore <class> entries defined // in the persistence unit, as by definition only elements declared in ORM XML are to be permitted. if (m_project.getPersistenceUnitMetadata() != null && m_project.getPersistenceUnitMetadata().isXMLMappingMetadataComplete()) { return; } // 4 - Iterate through the classes that are referenced from the // persistence.xml file. PersistenceUnitInfo persistenceUnitInfo = m_project.getPersistenceUnitInfo(); List<String> classNames = new ArrayList<String>(); // Add all the <class> specifications. classNames.addAll(persistenceUnitInfo.getManagedClassNames()); // Add all the classes from the <jar> specifications. for (URL url : persistenceUnitInfo.getJarFileUrls()) { classNames.addAll(PersistenceUnitProcessor.getClassNamesFromURL(url, m_loader, null)); } // Add all the classes off the classpath at the persistence unit root url. Set<String> unlistedClasses = Collections.EMPTY_SET; if (! persistenceUnitInfo.excludeUnlistedClasses()) { unlistedClasses = PersistenceUnitProcessor.getClassNamesFromURL(persistenceUnitInfo.getPersistenceUnitRootUrl(), m_loader, m_predeployProperties); } // 5 - Go through all the class names we found and add those classes // that have not yet been added. Be sure to check that the accessor // does not already exist since adding an accessor will merge its // contents with an existing accessor and we only want that to happen // in the XML case. Also, don't add an entity accessor if an embeddable // accessor to the same class exists (and vice versa). XML accessors // are loaded first, so preserve what we find there. Iterator<String> iterator = classNames.iterator(); boolean unlisted = false; while (iterator.hasNext() || !unlisted) { if (!iterator.hasNext() && !unlisted) { iterator = unlistedClasses.iterator(); unlisted = true; } if (iterator.hasNext()) { String className = iterator.next(); MetadataClass candidateClass = m_factory.getMetadataClass(className, unlisted); // JBoss Bug 227630: Do not process a null class whether it was from a // NPE or a CNF, a warning or exception is thrown in loadClass() if (candidateClass != null) { if (PersistenceUnitProcessor.isEntity(candidateClass) && ! m_project.hasEntity(candidateClass) && ! m_project.hasEmbeddable(candidateClass)) { m_project.addEntityAccessor(new EntityAccessor(PersistenceUnitProcessor.getEntityAnnotation(candidateClass), candidateClass, m_project)); } else if (PersistenceUnitProcessor.isEmbeddable(candidateClass) && ! m_project.hasEmbeddable(candidateClass) && ! m_project.hasEntity(candidateClass)) { m_project.addEmbeddableAccessor(new EmbeddableAccessor(PersistenceUnitProcessor.getEmbeddableAnnotation(candidateClass), candidateClass, m_project)); } else if (PersistenceUnitProcessor.isStaticMetamodelClass(candidateClass)) { m_project.addStaticMetamodelClass(PersistenceUnitProcessor.getStaticMetamodelAnnotation(candidateClass), candidateClass); } else if (PersistenceUnitProcessor.isConverter(candidateClass) && ! m_project.hasConverterAccessor(candidateClass)) { m_project.addConverterAccessor(new ConverterAccessor(PersistenceUnitProcessor.getConverterAnnotation(candidateClass), candidateClass, m_project)); } else if (PersistenceUnitProcessor.isMappedSuperclass(candidateClass) && ! m_project.hasMappedSuperclass(candidateClass)) { // ensure mapped superclasses will be added to the metamodel even if they do not have entity subclasses // add the mapped superclass to keep track of it in case it is not processed later (has no subclasses). m_project.addMappedSuperclass(new MappedSuperclassAccessor( PersistenceUnitProcessor.getMappedSuperclassAnnotation(candidateClass), candidateClass, m_project)); } } } } } /** * INTERNAL: * This method is responsible for figuring out list of mapping files to * read into XMLEntityMappings objects and store on the project. Note, * the order the files are discovered and read is very important so do * not change the order of invocation. */ public void loadMappingFiles(boolean throwExceptionOnFail) { // Read all the standard XML mapping files first. loadStandardMappingFiles(MetadataHelper.JPA_ORM_FILE); // Read all the explicitly specified mapping files second. loadSpecifiedMappingFiles(throwExceptionOnFail); // Read all the standard eclipselink files last (if the user hasn't // explicitly excluded them). The eclipselink orm files will be // processed last therefore allowing them to override and merge // metadata where necessary. Note: we want the eclipselink orm // metadata to merge into standard jpa files and not vice versa. // Loading them last will ensure this happens. Boolean excludeEclipseLinkORM = Boolean.valueOf((String) m_project.getPersistenceUnitInfo().getProperties().get(PersistenceUnitProperties.EXCLUDE_ECLIPSELINK_ORM_FILE)); if (! excludeEclipseLinkORM) { loadStandardMappingFiles(MetadataHelper.ECLIPSELINK_ORM_FILE); } if (m_metadataSource !=null) { XMLEntityMappings entityMappings = m_metadataSource.getEntityMappings(this.m_predeployProperties, m_loader, m_session.getSessionLog()); if (entityMappings != null) { m_project.addEntityMappings(entityMappings); } } } /** * INTERNAL: */ protected void loadSpecifiedMappingFiles(boolean throwExceptionOnFail) { PersistenceUnitInfo puInfo = m_project.getPersistenceUnitInfo(); for (String mappingFileName : puInfo.getMappingFileNames()) { try { Enumeration<URL> mappingFileURLs = m_loader.getResources(mappingFileName); if (!mappingFileURLs.hasMoreElements()){ mappingFileURLs = m_loader.getResources("/./" + mappingFileName); } if (mappingFileURLs.hasMoreElements()) { URL nextURL = mappingFileURLs.nextElement(); if (nextURL == null) { nextURL = mappingFileURLs.nextElement(); } if (mappingFileURLs.hasMoreElements()) { // Switched to warning, same file can be on the classpath twice in some deployments, // should not be an error. Throwable throwable = ValidationException.nonUniqueMappingFileName(puInfo.getPersistenceUnitName(), mappingFileName); getSessionLog().logThrowable(SessionLog.FINER, SessionLog.METADATA, throwable); } // Read the document through OX and add it to the project. m_project.addEntityMappings(XMLEntityMappingsReader.read(nextURL, m_loader, m_project.getPersistenceUnitInfo().getProperties())); } else { handleORMException(ValidationException.mappingFileNotFound(puInfo.getPersistenceUnitName(), mappingFileName), mappingFileName, throwExceptionOnFail); } } catch (IOException e) { handleORMException(PersistenceUnitLoadingException.exceptionLoadingORMXML(mappingFileName, e), mappingFileName, throwExceptionOnFail); } } } /** * INTERNAL: */ protected void loadStandardMappingFiles(String ormXMLFile) { PersistenceUnitInfo puInfo = m_project.getPersistenceUnitInfo(); Collection<URL> rootUrls = new HashSet<URL>(puInfo.getJarFileUrls()); rootUrls.add(puInfo.getPersistenceUnitRootUrl()); for (URL rootURL : rootUrls) { getSessionLog().log(SessionLog.FINER, SessionLog.METADATA, "searching_for_default_mapping_file", new Object[] { ormXMLFile, rootURL }, true); URL ormURL = null; Archive par = null; try { par = PersistenceUnitProcessor.getArchiveFactory(m_loader).createArchive(rootURL, null); if (par != null) { ormURL = par.getEntryAsURL(ormXMLFile); if (ormURL != null) { getSessionLog().log(SessionLog.FINER, SessionLog.METADATA, "found_default_mapping_file", new Object[] { ormURL, rootURL }, true); // Read the document through OX and add it to the project., pass persistence unit properties for any orm properties set there XMLEntityMappings entityMappings = XMLEntityMappingsReader.read(ormURL, m_loader, m_project.getPersistenceUnitInfo().getProperties()); entityMappings.setIsEclipseLinkORMFile(ormXMLFile.equals(MetadataHelper.ECLIPSELINK_ORM_FILE)); m_project.addEntityMappings(entityMappings); } } } catch (IOException e) { throw new RuntimeException(e); } catch (URISyntaxException e) { throw new RuntimeException(e); } finally { if (par != null) { par.close(); } } } } /** * INTERNAL: * Return the SessionLog from the Session. If the session is null, * return the AbstractSessionLog's SessionLog. * @return SessionLog */ protected SessionLog getSessionLog() { if (m_session != null) { return m_session.getSessionLog(); } else { return AbstractSessionLog.getLog(); } } /** * INTERNAL: * Process the customizer for those entities and embeddables that have one * defined. This must be the last thing called on this processor before * cleanup. */ public void processCustomizers() { for (ClassAccessor classAccessor: m_project.getAccessorsWithCustomizer()) { DescriptorCustomizer customizer = (DescriptorCustomizer) MetadataHelper.getClassInstance(classAccessor.getCustomizerClass().getName(), m_loader); try { customizer.customize(classAccessor.getDescriptor().getClassDescriptor()); } catch (Exception e) { getSessionLog().logThrowable(SessionLog.FINER, SessionLog.METADATA, e); } } } /** * INTERNAL: * Performs the initialization of the persistence unit classes and then * processes the xml metadata. * Note: Do not change the order of invocation of various methods. */ public void processEntityMappings(PersistenceUnitProcessor.Mode mode) { if (mode == PersistenceUnitProcessor.Mode.ALL || mode == PersistenceUnitProcessor.Mode.COMPOSITE_MEMBER_INITIAL) { m_factory = new MetadataAsmFactory(m_project.getLogger(), m_loader); // 1 - Process persistence unit meta data/defaults defined in ORM XML // instance documents in the persistence unit. If multiple conflicting // persistence unit meta data is found, this call will throw an // exception. The meta data we find here will be applied in the // initialize call below. for (XMLEntityMappings entityMappings : m_project.getEntityMappings()) { // Since this our first iteration through the entity mappings list, // set the project and loader references. This is very important // and must be done first! entityMappings.setLoader(m_loader); entityMappings.setProject(m_project); entityMappings.setMetadataFactory(m_factory); // Process the persistence unit metadata if defined. entityMappings.processPersistenceUnitMetadata(); } // 2 - Initialize all the persistence unit class with the meta data we // processed in step 1. initPersistenceUnitClasses(); // 3 - Now process the entity mappings metadata. for (XMLEntityMappings entityMappings : m_project.getEntityMappings()) { entityMappings.process(); } } } /** * INTERNAL: * Process the ORM metadata on this processors metadata project * (representing a single persistence-unit) */ public void processORMMetadata(PersistenceUnitProcessor.Mode mode) { if (mode == Mode.ALL || mode == Mode.COMPOSITE_MEMBER_INITIAL) { m_project.processStage1(); m_project.processStage2(); } if (mode != PersistenceUnitProcessor.Mode.COMPOSITE_MEMBER_INITIAL) { m_project.processStage3(mode); } } /** * INTERNAL: * Use this method to set the correct class loader that should be used * during processing. Currently, the class loader should only change * once, from preDeploy to deploy. */ public void setClassLoader(ClassLoader loader) { m_loader = loader; m_factory.setLoader(loader); // Update the loader on all the entity mappings for this project. for (XMLEntityMappings entityMappings : m_project.getEntityMappings()) { entityMappings.setLoader(m_loader); } } /** * INTERNAL: * Use this method to set the MetadataSource class to use for loading * extensible mappings */ public void setMetadataSource(MetadataSource source){ m_metadataSource = source; } }