/******************************************************************************* * Copyright (c) 1998, 2015 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 * 06/01/2010-2.1 Guy Pelletier * - 315195: Add new property to avoid reading XML during the canonical model generation * 08/25/2010-2.2 Guy Pelletier * - 309445: CannonicalModelProcessor process all files * 11/23/2010-2.2 Guy Pelletier * - 330660: Canonical model generator throws ClassCastException when using package-info.java * 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. ******************************************************************************/ package org.eclipse.persistence.internal.jpa.modelgen; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic.Kind; import org.eclipse.persistence.internal.jpa.deployment.SEPersistenceUnitInfo; import org.eclipse.persistence.internal.jpa.metadata.MetadataDescriptor; import org.eclipse.persistence.internal.jpa.metadata.MetadataLogger; import org.eclipse.persistence.internal.jpa.metadata.MetadataProject; import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataFactory; import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataClass; import org.eclipse.persistence.internal.jpa.modelgen.MetadataMirrorFactory; import org.eclipse.persistence.internal.jpa.modelgen.objects.PersistenceUnit; import org.eclipse.persistence.internal.jpa.modelgen.visitors.ElementVisitor; import org.eclipse.persistence.logging.SessionLog; import org.eclipse.persistence.sessions.DatabaseLogin; import org.eclipse.persistence.sessions.Project; import org.eclipse.persistence.sessions.server.ServerSession; /** * This metadata factory employs java mirrors to create MetadataClass and is * used with the canonical model processor. * * @author Guy Pelletier * @since EclipseLink 1.2 */ public class MetadataMirrorFactory extends MetadataFactory { private ElementVisitor<MetadataClass, MetadataClass> elementVisitor; // Thing to note: persistence units can be reloaded. We do not however // reload their associated projects. Once the project is created, it remains // around for the lifecycle of the compiler. private Map<String, PersistenceUnit> persistenceUnits; private Map<String, MetadataProject> metadataProjects; // This is a map of element/metadata classes built per compile round. This // map is cleared for each compile round. private Map<Element, MetadataClass> roundElements; // This is a hash set of metadata classes per compile round. This set is // cleared for each compile round. private HashSet<MetadataClass> roundMetadataClasses; private ProcessingEnvironment processingEnv; /** * INTERNAL: * The factory is kept as a static object to the persistence unit. The first * time the factory is initialized, we will get a full list of root * elements. Build MetadataClass for them right away. We don't want to * rebuild the factory every time otherwise we lose already built metadata * classes and may not be able to rebuild till individual elements are * 'touched' or if the project is rebuilt as a whole. */ protected MetadataMirrorFactory(MetadataLogger logger, ClassLoader loader) { super(logger, loader); roundElements = new HashMap<Element, MetadataClass>(); roundMetadataClasses = new HashSet<MetadataClass>(); persistenceUnits = new HashMap<String, PersistenceUnit>(); metadataProjects = new HashMap<String, MetadataProject>(); } /** * INTERNAL: */ public void addPersistenceUnit(SEPersistenceUnitInfo puInfo, PersistenceUnit persistenceUnit) { persistenceUnits.put(puInfo.getPersistenceUnitName(), persistenceUnit); } /** * INTERNAL: * If the metadata class doesn't exist for the given element, we will build * one and add it to our map of MetadataClasses before returning it. We keep * our own map of metadata classes for each round element for a couple * reasons: * 1- Most importantly, we use this map to avoid an infinite loop. Once we * kick off the visiting of a class, it snow balls into visiting and * building other classes from the round (referenced from that class). * 2- For our own safety we cache metadata class keyed on the element we * built it for. This ensures we are always dealing with the correct * related metadata class. * 3- For each round we must update all metadata classes for each round * element. */ public MetadataClass getMetadataClass(Element element) { MetadataClass metadataClass = roundElements.get(element); if (metadataClass == null) { // Only log if logging on finest. boolean shouldLog = getLogger().getSession().getLogLevel() == SessionLog.FINEST; // As a performance gain, avoid visiting this class if it is not a // round element. We must re-visit round elements. if (isRoundElement(element)) { if (shouldLog) { processingEnv.getMessager().printMessage(Kind.NOTE, "Building metadata class for round element: " + element); } metadataClass = new MetadataClass(MetadataMirrorFactory.this, ""); roundElements.put(element, metadataClass); roundMetadataClasses.add(metadataClass); // Kick off the visiting of elements. element.accept(elementVisitor, metadataClass); // The name of the metadata class is a qualified name from a // type element. Set this name on the MetadataFactory map. We // can't call addMetadataClass till we have visited the class. addMetadataClass(metadataClass); } else { String name = element.toString(); if (metadataClassExists(name)) { return getMetadataClass(name); } // So we are not a round element, the outcome is as follows: // - TypeElement or TypeParameterElement in existing class map, // return it. // - TypeElement, and not in the existing class map, return // simple non-visited MetadataClass with only a name/type. // - TypeParameterElement, and not in the existing class map, // visit it to ensure we get the correct generic type set // and return it. // - Everything else, return simple non-visited MetadataClass // with only a name/type from the toString value. if (element instanceof TypeElement || element instanceof TypeParameterElement) { if (element instanceof TypeElement) { if (shouldLog) { processingEnv.getMessager().printMessage(Kind.NOTE, "Building metadata class for type element: " + name); } metadataClass = new MetadataClass(MetadataMirrorFactory.this, name); addMetadataClass(metadataClass); element.accept(elementVisitor, metadataClass); addMetadataClass(metadataClass); } else { // Only thing going to get through at this point are // TypeParameterElements (presumably generic ones). Look // at those further since they 'should' be simple visits. if (shouldLog) { processingEnv.getMessager().printMessage(Kind.NOTE, "Building type parameter element: " + name); } metadataClass = new MetadataClass(MetadataMirrorFactory.this, name); addMetadataClass(metadataClass); element.accept(elementVisitor, metadataClass); addMetadataClass(metadataClass); } } else { // Array types etc ... metadataClass = getMetadataClass(element.toString()); } } } return metadataClass; } @Override public MetadataClass getMetadataClass(String className, boolean isLazy) { return getMetadataClass(className); } /** * INTERNAL: * This assumes that every class that is accessed in the pre-process * methods will have a class metadata built for them already. That is, * our visitor must visit every class that the pre-process will want to * look at. All return types and field types need a metadata class or * else kaboom, null pointer! */ @Override public MetadataClass getMetadataClass(String className) { if (className == null) { return null; } else { if (! metadataClassExists(className)) { // By the time this method is called we should have built a // MetadataClass for all the model elements (and then some) which // are the only classes we really care about. This is acting like a // catch all for any jdk classes we didn't visit and just returns a // MetadataClass with the same class name. addMetadataClass(new MetadataClass(this, className)); } return getMetadataClasses().get(className); } } /** * INTERNAL: * If the adds a new element will build it and add it to our list of * MetadataClasses. */ public MetadataClass getMetadataClass(TypeMirror typeMirror) { Element element = processingEnv.getTypeUtils().asElement(typeMirror); if (element == null) { // This case hits when we are passed a TypeMirror of <none>. Not // 100% on the whole story here, either way we create a metadata // class with that name and carry on. The case also hits when we // ask for a metadata class for array types. return getMetadataClass(typeMirror.toString()); } else { return getMetadataClass(element); } } /** * INTERNAL: * We preserve state from each processor run by holding static references * to projects. */ public MetadataProject getMetadataProject(SEPersistenceUnitInfo puInfo) { if (! metadataProjects.containsKey(puInfo.getPersistenceUnitName())) { MetadataProject project = new MetadataProject(puInfo, new ServerSession(new Project(new DatabaseLogin())), false, false, false, false, false); metadataProjects.put(puInfo.getPersistenceUnitName(), project); return project; } else { return metadataProjects.get(puInfo.getPersistenceUnitName()); } } /** * INTERNAL: */ public Collection<PersistenceUnit> getPersistenceUnits() { return persistenceUnits.values(); } /** * INTERNAL: */ public ProcessingEnvironment getProcessingEnvironment() { return processingEnv; } /** * INTERNAL: */ public Map<Element, MetadataClass> getRoundElements() { return roundElements; } /** * INTENAL: */ public boolean isRoundElement(Element element) { return roundElements.containsKey(element); } /** * INTENAL: */ public boolean isRoundElement(MetadataClass cls) { return roundMetadataClasses.contains(cls); } /** * INTERNAL: */ @Override public void resolveGenericTypes(MetadataClass child, List<String> genericTypes, MetadataClass parent, MetadataDescriptor descriptor) { // Our metadata factory does not and can not resolve the types since // we are writing static attributes on our generated class. This // factory will use types of "? extends Object". So don't need to // resolve anything here. No work is good work! } /** * INTERNAL: * Our processor will not visit generated elements, there is no need for * us to do this. */ public void setEnvironments(ProcessingEnvironment processingEnvironment, RoundEnvironment roundEnvironment) { processingEnv = processingEnvironment; roundElements.clear(); roundMetadataClasses.clear(); // Initialize the element visitor if it is null. if (elementVisitor == null) { elementVisitor = new ElementVisitor<MetadataClass, MetadataClass>(processingEnv); } // Go through the root elements and gather the round elements that // we care about. It is crucial to not call getMetadataClass(element) // before we gather our list of round elements. Calling this method will // trigger the visiting of elements which has a dependency on round // elements. for (Element element : roundEnvironment.getRootElements()) { // Look at only class elements. if (element.getKind().isClass()) { // Don't look at the generated classes. We must look at all the // classes and not only those decorated with @Entity, // @MappedSuperclass or @Embeddable, since it may be a class // defined solely in XML and we want to make sure we look at // the changes for those classes as well. if (element.getAnnotation(javax.annotation.Generated.class) == null) { roundElements.put(element, null); } } } // Visit all the round elements now. These may be new elements or // existing elements that were changed. We must build or re-build the // class metadata for that element to be re-used with new accessors // needing to pre-processed. for (Element element : roundElements.keySet()) { getMetadataClass(element); } } }