blob: 58a2ad53c89defaad5673c31dcd8e40d4defc3bb [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 1998, 2018 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.
* 02/17/2018-2.7.2 Lukas Jungmann
* - 531305: Canonical model generator fails to run on JDK9
******************************************************************************/
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.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.Name;
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.MetadataClass;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataFactory;
import org.eclipse.persistence.internal.jpa.modelgen.objects.PersistenceUnit;
import org.eclipse.persistence.internal.jpa.modelgen.visitors.ElementVisitor;
import org.eclipse.persistence.logging.LogCategory;
import org.eclipse.persistence.logging.LogLevel;
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;
/** Current logger context from command line options. */
private final LoggerContext loggerContext;
/**
* 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(final MetadataLogger logger, final LoggerContext loggerContext, final ClassLoader loader) {
super(logger, loader);
this.loggerContext = loggerContext;
roundElements = new HashMap<>();
roundMetadataClasses = new HashSet<>();
persistenceUnits = new HashMap<>();
metadataProjects = new HashMap<>();
}
/**
* 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.
// 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 (m_logger.shouldLog(LogLevel.FINE, LogCategory.PROCESSOR)) {
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 (m_logger.shouldLog(LogLevel.FINE, LogCategory.PROCESSOR)) {
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 (m_logger.shouldLog(LogLevel.FINE, LogCategory.PROCESSOR)) {
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:
* Get current logger context from command line options.
*/
public LoggerContext getLoggerContext() {
return loggerContext;
}
/**
* 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.
boolean isGenerated = false;
List<? extends AnnotationMirror> annotationMirrors = element.getAnnotationMirrors();
for (AnnotationMirror am : annotationMirrors) {
Name qn = ((TypeElement) am.getAnnotationType().asElement()).getQualifiedName();
if ("javax.annotation.Generated".equals(qn) || "javax.annotation.processing.Generated".equals(qn)) {
isGenerated = true;
break;
}
}
if (!isGenerated) {
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);
}
}
}