/******************************************************************************* * Copyright (c) 1998, 2016 Oracle and/or its affiliates. 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: * 08/10/2009-2.0 Guy Pelletier * - 267391: JPA 2.0 implement/extend/use an APT tooling library for MetaModel API canonical classes * 11/20/2009-2.0 Guy Pelletier/Mitesh Meswani * - 295376: Improve usability of MetaModel generator * 04/27/2010-2.1 Guy Pelletier * - 309856: MappedSuperclasses from XML are not being initialized properly * 06/01/2010-2.1 Guy Pelletier * - 315195: Add new property to avoid reading XML during the canonical model generation * 11/23/2010-2.2 Guy Pelletier * - 330660: Canonical model generator throws ClassCastException when using package-info.java * 02/14/2013-2.5 Guy Pelletier * - 338610: JPA 2.1 Functionality for Java EE 7 (JSR-338) * 05/26/2016-2.7 Tomas Kraus * - 494610: Session Properties map should be Map<String, Object> ******************************************************************************/ package org.eclipse.persistence.internal.jpa.modelgen.objects; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.tools.Diagnostic.Kind; import org.eclipse.persistence.config.PersistenceUnitProperties; import org.eclipse.persistence.exceptions.XMLMarshalException; import org.eclipse.persistence.internal.jpa.deployment.SEPersistenceUnitInfo; import org.eclipse.persistence.internal.jpa.deployment.SEPersistenceUnitProperty; import org.eclipse.persistence.internal.jpa.metadata.MetadataHelper; import org.eclipse.persistence.internal.jpa.metadata.MetadataProject; import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.ClassAccessor; 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.MetadataAnnotation; import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataClass; import org.eclipse.persistence.internal.jpa.metadata.xml.XMLEntityMappings; import org.eclipse.persistence.internal.jpa.metadata.xml.XMLEntityMappingsReader; import org.eclipse.persistence.internal.jpa.modelgen.MetadataMirrorFactory; import org.eclipse.persistence.internal.jpa.modelgen.objects.PersistenceUnitReader; import org.eclipse.persistence.oxm.XMLContext; import static org.eclipse.persistence.internal.jpa.modelgen.CanonicalModelProperties.CANONICAL_MODEL_SUB_PACKAGE; import static org.eclipse.persistence.internal.jpa.modelgen.CanonicalModelProperties.CANONICAL_MODEL_PREFIX; import static org.eclipse.persistence.internal.jpa.modelgen.CanonicalModelProperties.CANONICAL_MODEL_SUFFIX; import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_EMBEDDABLE; import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_ENTITY; import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_MAPPED_SUPERCLASS; /** * A representation of a persistence unit definition. * * @author Guy Pelletier, Doug Clarke * @since EclipseLink 1.2 */ public class PersistenceUnit { protected List<XMLEntityMappings> xmlEntityMappings; protected MetadataProject project; protected MetadataMirrorFactory factory; protected PersistenceUnitReader persistenceUnitReader; protected ProcessingEnvironment processingEnv; protected SEPersistenceUnitInfo persistenceUnitInfo; protected HashMap<String, Object> persistenceUnitProperties; /** * INTERNAL: */ public PersistenceUnit(SEPersistenceUnitInfo puInfo, MetadataMirrorFactory mirrorFactory, PersistenceUnitReader reader) { factory = mirrorFactory; persistenceUnitInfo = puInfo; persistenceUnitReader = reader; // Ask the factory for the project and processing environment processingEnv = factory.getProcessingEnvironment(); project = factory.getMetadataProject(persistenceUnitInfo); // Init our OX mapped properties into a map. initPersistenceUnitProperties(); // Init our mapping files. This method relies on properties being // initialized first. initXMLEntityMappings(); } /** * INTERNAL: * Add an element decorated with an Embeddable annotation. * * The possibilities here: * 1 - New element and not exclude unlisted classes - add it * 2 - New element but exclude unlisted classes - ignore it. * 3 - Existing element, but accessor loaded from XML - don't touch it * 4 - Existing element, but accessor loaded from Annotations - add new accessor overriding the old. */ public void addEmbeddableAccessor(Element element) { MetadataClass metadataClass = factory.getMetadataClass(element); // Remove the accessor from other maps if the type changed. removeEntityAccessor(metadataClass); removeMappedSuperclassAccessor(metadataClass); if (project.hasEmbeddable(metadataClass)) { EmbeddableAccessor embeddableAccessor = project.getEmbeddableAccessor(metadataClass); // If it was loaded from XML, reset the pre-processed flag. if (embeddableAccessor.loadedFromXML()) { embeddableAccessor.clearPreProcessed(); } else { // Was not loaded from XML and existed in the project. if (excludeUnlistedClasses(metadataClass)) { // Exclude unlisted classes is now false, remove it! removeEmbeddableAccessor(metadataClass); } else { // Otherwise, override the existing accessor! addEmbeddableAccessor(new EmbeddableAccessor(metadataClass.getAnnotation(JPA_EMBEDDABLE), metadataClass, project)); } } } else if (! excludeUnlistedClasses(metadataClass)) { // add it! addEmbeddableAccessor(new EmbeddableAccessor(metadataClass.getAnnotation(JPA_EMBEDDABLE), metadataClass, project)); } } /** * INTERNAL: * Add an embeddable accessor to the project, preserving any previous * owning descriptors set if applicable. */ protected void addEmbeddableAccessor(EmbeddableAccessor embeddableAccessor) { // We need to preserve owning descriptors to ensure we process // an embeddable accessor in the correct context. That is, if the // user changed only the embeddable class then we won't process // the owning entity (which ensures the owning descriptor, itself, // is set on the embeddable accessor). // If ownership changed, then those entities involved will be // in the round elements and we will correctly set the owning // descriptor in the pre-process stage of those entities, overriding // this setting) if (project.hasEmbeddable(embeddableAccessor.getJavaClass())) { EmbeddableAccessor existingEmbeddableAccessor = project.getEmbeddableAccessor(embeddableAccessor.getJavaClass()); embeddableAccessor.addEmbeddingAccessors(existingEmbeddableAccessor.getEmbeddingAccessors()); embeddableAccessor.addOwningDescriptors(existingEmbeddableAccessor.getOwningDescriptors()); } project.addEmbeddableAccessor(embeddableAccessor); } /** * INTERNAL: * Add an element decorated with an Entity annotation. * * The possibilities here: * 1 - New element and not exclude unlisted classes - add it * 2 - New element but exclude unlisted classes - ignore it. * 3 - Existing element, loaded from XML - don't touch it * 4 - Existing element, loaded from Annotations - add new accessor overriding the old. */ public void addEntityAccessor(Element element) { MetadataClass metadataClass = factory.getMetadataClass(element); // Remove the accessor from other maps if the type changed. removeEmbeddableAccessor(metadataClass); removeMappedSuperclassAccessor(metadataClass); if (project.hasEntity(metadataClass)) { EntityAccessor entityAccessor = project.getEntityAccessor(metadataClass); // If it was loaded from XML, reset the pre-processed flag. if (entityAccessor.loadedFromXML()) { entityAccessor.clearPreProcessed(); } else { // Was not loaded from XML and existed in the project. if (excludeUnlistedClasses(metadataClass)) { // Exclude unlisted classes is now false, remove it! removeEntityAccessor(metadataClass); } else { // Otherwise, override the existing accessor! project.addEntityAccessor(new EntityAccessor(metadataClass.getAnnotation(JPA_ENTITY), metadataClass, project)); } } } else if (! excludeUnlistedClasses(metadataClass)) { // add it! project.addEntityAccessor(new EntityAccessor(metadataClass.getAnnotation(JPA_ENTITY), metadataClass, project)); } } /** * INTERNAL: * Add an element decorated with a MappedSuperclass annotation. * * The possibilities here: * 1 - New element and not exclude unlisted classes - add it * 2 - New element but exclude unlisted classes - ignore it. * 3 - Existing element, but accessor loaded from XML - don't touch it * 4 - Existing element, but accessor loaded from Annotations - add new accessor overridding the old. */ public void addMappedSuperclassAccessor(Element element) { MetadataClass metadataClass = factory.getMetadataClass(element); // Remove the accessor from other maps if the type changed. removeEntityAccessor(metadataClass); removeEmbeddableAccessor(metadataClass); if (project.hasMappedSuperclass(metadataClass)) { MappedSuperclassAccessor mappedSuperclassAccessor = project.getMappedSuperclassAccessor(metadataClass); // If it was loaded from XML, reset the pre-processed flag. if (mappedSuperclassAccessor.loadedFromXML()) { mappedSuperclassAccessor.clearPreProcessed(); } else { // Was not loaded from XML and existed in the project. if (excludeUnlistedClasses(metadataClass)) { // Exclude unlisted classes is now false, remove it! project.removeMappedSuperclassAccessor(metadataClass); } else { // Otherwise, override the existing accessor! project.addMappedSuperclass(new MappedSuperclassAccessor(metadataClass.getAnnotation(JPA_MAPPED_SUPERCLASS), metadataClass, project)); } } } else if (! excludeUnlistedClasses(metadataClass)) { // add it! project.addMappedSuperclass(new MappedSuperclassAccessor(metadataClass.getAnnotation(JPA_MAPPED_SUPERCLASS), metadataClass, project)); } } /** * INTERNAL: */ protected void addPropertyFromOptions(String propertyName) { if (! persistenceUnitProperties.containsKey(propertyName) || persistenceUnitProperties.get(propertyName) == null) { persistenceUnitProperties.put(propertyName, processingEnv.getOptions().get(propertyName)); } } /** * INTERNAL: */ protected void addXMLEntityMappings(String mappingFile, XMLContext context) { // If the input stream is null, we didn't find the mapping file, so // don't bother trying to read it. InputStream inputStream = persistenceUnitReader.getInputStream(mappingFile, false); if (inputStream != null) { try { XMLEntityMappings entityMappings = (XMLEntityMappings) context.createUnmarshaller().unmarshal(inputStream); // For eclipselink-orm merging and overriding these need to be set. entityMappings.setIsEclipseLinkORMFile(mappingFile.equals(MetadataHelper.ECLIPSELINK_ORM_FILE)); entityMappings.setMappingFile(mappingFile); processingEnv.getMessager().printMessage(Kind.NOTE, "File loaded : " + mappingFile + ", is eclipselink-orm file: " + entityMappings.isEclipseLinkORMFile()); xmlEntityMappings.add(entityMappings); } finally { persistenceUnitReader.closeInputStream(inputStream); } } } /** * INTERNAL: */ protected void addXMLEntityMappings(String mappingFile) { try { // Try eclipselink project addXMLEntityMappings(mappingFile, XMLEntityMappingsReader.getEclipseLinkOrmProject()); } catch (XMLMarshalException e) { try { // Try JPA 2.1 project addXMLEntityMappings(mappingFile, XMLEntityMappingsReader.getOrm2_1Project()); } catch (XMLMarshalException ee) { try { // Try JPA 2.0 project addXMLEntityMappings(mappingFile, XMLEntityMappingsReader.getOrm2_0Project()); } catch (XMLMarshalException eee) { // Try JPA 1.0 project (don't catch exceptions at this point) addXMLEntityMappings(mappingFile, XMLEntityMappingsReader.getOrm1_0Project()); } } } } /** * INTERNAL: * If the accessor is no longer valid it we should probably look into * removing the accessor from the project. For now I don't think it's * a big deal. */ public boolean containsClass(MetadataClass metadataClass) { if (project.hasEntity(metadataClass)) { return isValidAccessor(project.getEntityAccessor(metadataClass), metadataClass.getAnnotation(JPA_ENTITY)); } if (project.hasEmbeddable(metadataClass)) { return isValidAccessor(project.getEmbeddableAccessor(metadataClass), metadataClass.getAnnotation(JPA_EMBEDDABLE)); } if (project.hasMappedSuperclass(metadataClass)) { return isValidAccessor(project.getMappedSuperclassAccessor(metadataClass), metadataClass.getAnnotation(JPA_MAPPED_SUPERCLASS)); } return false; } /** * INTERNAL: * Return true if the metadata class given is not a managed class and * exclude-unlisted-classes is set to true for this PU. */ protected boolean excludeUnlistedClasses(MetadataClass cls) { return (! persistenceUnitInfo.getManagedClassNames().contains(cls.getName())) && persistenceUnitInfo.excludeUnlistedClasses(); } /** * INTERNAL: */ public ClassAccessor getClassAccessor(MetadataClass metadataClass) { if (project.hasEntity(metadataClass)) { return project.getEntityAccessor(metadataClass); } if (project.hasEmbeddable(metadataClass)) { return project.getEmbeddableAccessor(metadataClass); } if (project.hasMappedSuperclass(metadataClass)) { return project.getMappedSuperclassAccessor(metadataClass); } return null; } /** * INTERNAL: */ public String getQualifiedCanonicalName(String qualifiedName) { return MetadataHelper.getQualifiedCanonicalName(qualifiedName, persistenceUnitProperties); } /** * INTERNAL: */ public void initPersistenceUnitProperties() { persistenceUnitProperties = new HashMap<String, Object>(); // Determine how much validation we want to do. For now, last one // in wins (in the case of multiple settings) and we ignore null // named properties. for (SEPersistenceUnitProperty property : persistenceUnitInfo.getPersistenceUnitProperties()) { if (property.getName() != null) { //processingEnv.getMessager().printMessage(Kind.NOTE, "Key: " + property.getName() + " , value: " + property.getValue()); persistenceUnitProperties.put(property.getName(), property.getValue()); } } // Check for user specified options and add them to the properties // if they were not specified in the persistence.xml. addPropertyFromOptions(CANONICAL_MODEL_PREFIX); addPropertyFromOptions(CANONICAL_MODEL_SUFFIX); addPropertyFromOptions(CANONICAL_MODEL_SUB_PACKAGE); } /** * INTERNAL: */ protected void initXMLEntityMappings() { xmlEntityMappings = new ArrayList<XMLEntityMappings>(); // Load the orm.xml if it exists. addXMLEntityMappings(MetadataHelper.JPA_ORM_FILE); // Load the eclipselink-orm.xml if it exists and is not excluded. Boolean excludeEclipseLinkORM = false; if (persistenceUnitProperties.containsKey(PersistenceUnitProperties.EXCLUDE_ECLIPSELINK_ORM_FILE)) { excludeEclipseLinkORM = Boolean.valueOf((String) persistenceUnitProperties.get(PersistenceUnitProperties.EXCLUDE_ECLIPSELINK_ORM_FILE)); } if (! excludeEclipseLinkORM) { addXMLEntityMappings(MetadataHelper.ECLIPSELINK_ORM_FILE); } // Load the listed mapping files. for (String mappingFile : persistenceUnitInfo.getMappingFileNames()) { if (! mappingFile.equals(MetadataHelper.JPA_ORM_FILE) && ! mappingFile.equals(MetadataHelper.ECLIPSELINK_ORM_FILE)) { addXMLEntityMappings(mappingFile); } } // 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. for (XMLEntityMappings entityMappings : xmlEntityMappings) { entityMappings.setLoader(factory.getLoader()); entityMappings.setProject(project); entityMappings.setMetadataFactory(factory); entityMappings.setLoadedForCanonicalModel(true); // Process the persistence unit metadata if defined. entityMappings.processPersistenceUnitMetadata(); } // 2 - 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 : xmlEntityMappings) { entityMappings.initPersistenceUnitClasses(entities, embeddables); } // 3 - 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. project.addEntityAccessor(entity); // This will override any global settings. entity.getEntityMappings().processEntityMappingsDefaults(entity); } // 4 - 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. addEmbeddableAccessor(embeddable); // This will override any global settings. embeddable.getEntityMappings().processEntityMappingsDefaults(embeddable); } } /** * INTERNAL: */ protected boolean isValidAccessor(ClassAccessor accessor, MetadataAnnotation annotation) { if (! accessor.loadedFromXML()) { // If it wasn't loaded from XML, we need to look at it further. It // could have been deleted and brought back (without an annotation) // or simply have had its annotation removed. Check for an annotation. return annotation != null; } return true; } /** * INTERNAL: * Steps are important here, so don't change them. */ public void preProcessForCanonicalModel() { // 1 - Pre-Process the list of entities first. This will discover/build // the list of embeddable accessors. for (EntityAccessor entityAccessor : project.getEntityAccessors()) { // Some entity accessors can be fast tracked for pre-processing. // That is, an inheritance subclass will tell its parents to // pre-process. So don't pre-process it again. if (shouldPreProcess(entityAccessor)) { // Update the accessible object in case it changed. entityAccessor.setAccessibleObject(factory.getMetadataClass(entityAccessor.getAccessibleObjectName())); // Pre-process the accessor now. entityAccessor.preProcessForCanonicalModel(); } } // 2 - Pre-Process the list of mapped superclasses. for (MappedSuperclassAccessor mappedSuperclassAccessor : project.getMappedSuperclasses()) { if (shouldPreProcess(mappedSuperclassAccessor)) { // Update the accessible object first, in case it changed. mappedSuperclassAccessor.setAccessibleObject(factory.getMetadataClass(mappedSuperclassAccessor.getAccessibleObjectName())); // Pre-process the accessor now. mappedSuperclassAccessor.preProcessForCanonicalModel(); } } // 3 - Pre-process the list of root embeddable accessors. This list will // have been built from step 1. Root embeddable accessors will have // an owning descriptor (used to determine access type). for (EmbeddableAccessor embeddableAccessor : project.getRootEmbeddableAccessors()) { if (shouldPreProcess(embeddableAccessor)) { // Update the accessible object first, in case it changed. embeddableAccessor.setAccessibleObject(factory.getMetadataClass(embeddableAccessor.getAccessibleObjectName())); // Pre-process the accessor now. embeddableAccessor.preProcessForCanonicalModel(); } } // 4 - Go through the whole list of embeddables and process any that // were not pre-processed. Only way this is true is if the embeddable // was not referenced from an entity (be it root or nested in a root // embeddable) for (EmbeddableAccessor embeddableAccessor : project.getEmbeddableAccessors()) { if (shouldPreProcess(embeddableAccessor)) { // Update the accessible object first, in case it changed. embeddableAccessor.setAccessibleObject(factory.getMetadataClass(embeddableAccessor.getAccessibleObjectName())); // Pre-process the accessor now. embeddableAccessor.preProcessForCanonicalModel(); } } } /** * INTERNAL: * If it is not already pre-processed or is included in the round elements * (presumably because something changed), then pre-process the accessor. * You may get a previously loaded and processed XML class accessor that * may have had a change made to its associated class (that does not have * either an Entity, Embeddable or MappedSuperclass annotation) with the * load xml flag turned off. Therefore we must make sure we preProcess the * accessor again. */ protected boolean shouldPreProcess(ClassAccessor accessor) { MetadataClass cls = (MetadataClass) accessor.getAccessibleObject(); if (accessor.isPreProcessed() && factory.isRoundElement(cls)) { accessor.clearPreProcessed(); } return ! accessor.isPreProcessed(); } /** * INTERNAL: * Remove the entity accessor for the given metadata class if one exists. */ protected void removeEntityAccessor(MetadataClass metadataClass) { if (project.hasEntity(metadataClass)) { project.removeEntityAccessor(metadataClass); } } /** * INTERNAL: * Remove the embeddable accessor for the given metadata class if one exists. */ protected void removeEmbeddableAccessor(MetadataClass metadataClass) { if (project.hasEmbeddable(metadataClass)) { project.removeEmbeddableAccessor(metadataClass); } } /** * INTERNAL: * Remove the embeddable accessor for the given metadata class if one exists. */ protected void removeMappedSuperclassAccessor(MetadataClass metadataClass) { if (project.hasMappedSuperclass(metadataClass)) { project.removeMappedSuperclassAccessor(metadataClass); } } /** * INTERNAL: */ @Override public String toString() { return persistenceUnitInfo.getPersistenceUnitName(); } }