HHH-16633 fix an issue with the lifecycle of annotation processing

we could not see typesafe references to static strings we generate
This commit is contained in:
Gavin King 2023-07-09 01:24:54 +02:00
parent a4d8580606
commit 9512077462
5 changed files with 150 additions and 96 deletions

View File

@ -13,6 +13,7 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
@ -36,7 +37,7 @@ public final class Context {
private static final String DEFAULT_PERSISTENCE_XML_LOCATION = "/META-INF/persistence.xml"; private static final String DEFAULT_PERSISTENCE_XML_LOCATION = "/META-INF/persistence.xml";
/** /**
* Used for keeping track of parsed entities and mapped super classes (xml + annotations). * Used for keeping track of parsed entities and mapped super classes (XML + annotations).
*/ */
private final Map<String, Metamodel> metaEntities = new HashMap<>(); private final Map<String, Metamodel> metaEntities = new HashMap<>();
@ -50,7 +51,9 @@ public final class Context {
private final Map<String, AccessTypeInformation> accessTypeInformation = new HashMap<>(); private final Map<String, AccessTypeInformation> accessTypeInformation = new HashMap<>();
private final ProcessingEnvironment pe; private final Set<CharSequence> elementsToRedo = new HashSet<>();
private final ProcessingEnvironment processingEnvironment;
private final boolean logDebug; private final boolean logDebug;
private final boolean lazyXmlParsing; private final boolean lazyXmlParsing;
private final String persistenceXmlLocation; private final String persistenceXmlLocation;
@ -68,12 +71,12 @@ public final class Context {
private AccessType persistenceUnitDefaultAccessType; private AccessType persistenceUnitDefaultAccessType;
// keep track of all classes for which model have been generated // keep track of all classes for which model have been generated
private final Collection<String> generatedModelClasses = new HashSet<String>(); private final Collection<String> generatedModelClasses = new HashSet<>();
public Context(ProcessingEnvironment pe) { public Context(ProcessingEnvironment processingEnvironment) {
this.pe = pe; this.processingEnvironment = processingEnvironment;
String persistenceXmlOption = pe.getOptions().get( JPAMetaModelEntityProcessor.PERSISTENCE_XML_OPTION ); String persistenceXmlOption = processingEnvironment.getOptions().get( JPAMetaModelEntityProcessor.PERSISTENCE_XML_OPTION );
if ( persistenceXmlOption != null ) { if ( persistenceXmlOption != null ) {
if ( !persistenceXmlOption.startsWith("/") ) { if ( !persistenceXmlOption.startsWith("/") ) {
persistenceXmlOption = "/" + persistenceXmlOption; persistenceXmlOption = "/" + persistenceXmlOption;
@ -84,7 +87,7 @@ public final class Context {
persistenceXmlLocation = DEFAULT_PERSISTENCE_XML_LOCATION; persistenceXmlLocation = DEFAULT_PERSISTENCE_XML_LOCATION;
} }
String ormXmlOption = pe.getOptions().get( JPAMetaModelEntityProcessor.ORM_XML_OPTION ); String ormXmlOption = processingEnvironment.getOptions().get( JPAMetaModelEntityProcessor.ORM_XML_OPTION );
if ( ormXmlOption != null ) { if ( ormXmlOption != null ) {
ormXmlFiles = new ArrayList<>(); ormXmlFiles = new ArrayList<>();
for ( String ormFile : ormXmlOption.split( "," ) ) { for ( String ormFile : ormXmlOption.split( "," ) ) {
@ -98,12 +101,12 @@ public final class Context {
ormXmlFiles = Collections.emptyList(); ormXmlFiles = Collections.emptyList();
} }
lazyXmlParsing = Boolean.parseBoolean( pe.getOptions().get( JPAMetaModelEntityProcessor.LAZY_XML_PARSING ) ); lazyXmlParsing = Boolean.parseBoolean( processingEnvironment.getOptions().get( JPAMetaModelEntityProcessor.LAZY_XML_PARSING ) );
logDebug = Boolean.parseBoolean( pe.getOptions().get( JPAMetaModelEntityProcessor.DEBUG_OPTION ) ); logDebug = Boolean.parseBoolean( processingEnvironment.getOptions().get( JPAMetaModelEntityProcessor.DEBUG_OPTION ) );
} }
public ProcessingEnvironment getProcessingEnvironment() { public ProcessingEnvironment getProcessingEnvironment() {
return pe; return processingEnvironment;
} }
public boolean addInjectAnnotation() { public boolean addInjectAnnotation() {
@ -139,11 +142,11 @@ public final class Context {
} }
public Elements getElementUtils() { public Elements getElementUtils() {
return pe.getElementUtils(); return processingEnvironment.getElementUtils();
} }
public Types getTypeUtils() { public Types getTypeUtils() {
return pe.getTypeUtils(); return processingEnvironment.getTypeUtils();
} }
public String getPersistenceXmlLocation() { public String getPersistenceXmlLocation() {
@ -207,7 +210,7 @@ public final class Context {
} }
public TypeElement getTypeElementForFullyQualifiedName(String fqcn) { public TypeElement getTypeElementForFullyQualifiedName(String fqcn) {
Elements elementUtils = pe.getElementUtils(); Elements elementUtils = processingEnvironment.getElementUtils();
return elementUtils.getTypeElement( fqcn ); return elementUtils.getTypeElement( fqcn );
} }
@ -219,9 +222,21 @@ public final class Context {
return generatedModelClasses.contains( name ); return generatedModelClasses.contains( name );
} }
public Set<CharSequence> getElementsToRedo() {
return elementsToRedo;
}
public void addElementToRedo(CharSequence qualifiedName) {
elementsToRedo.add( qualifiedName );
}
public void removeElementToRedo(CharSequence qualifiedName) {
elementsToRedo.remove( qualifiedName );
}
public void logMessage(Diagnostic.Kind type, String message) { public void logMessage(Diagnostic.Kind type, String message) {
if ( logDebug || type != Diagnostic.Kind.OTHER ) { if ( logDebug || type != Diagnostic.Kind.OTHER ) {
pe.getMessager().printMessage( type, message ); processingEnvironment.getMessager().printMessage( type, message );
} }
} }

View File

@ -8,6 +8,7 @@ package org.hibernate.jpamodelgen;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.ProcessingEnvironment;
@ -18,6 +19,7 @@ import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind; import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement; import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType; import javax.lang.model.type.DeclaredType;
@ -29,6 +31,7 @@ import javax.tools.Diagnostic;
import org.hibernate.jpamodelgen.annotation.AnnotationMetaEntity; import org.hibernate.jpamodelgen.annotation.AnnotationMetaEntity;
import org.hibernate.jpamodelgen.annotation.AnnotationMetaPackage; import org.hibernate.jpamodelgen.annotation.AnnotationMetaPackage;
import org.hibernate.jpamodelgen.annotation.ProcessLaterException;
import org.hibernate.jpamodelgen.model.Metamodel; import org.hibernate.jpamodelgen.model.Metamodel;
import org.hibernate.jpamodelgen.util.Constants; import org.hibernate.jpamodelgen.util.Constants;
import org.hibernate.jpamodelgen.util.StringUtil; import org.hibernate.jpamodelgen.util.StringUtil;
@ -37,8 +40,10 @@ import org.hibernate.jpamodelgen.xml.JpaDescriptorParser;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import static java.lang.Boolean.parseBoolean;
import static javax.lang.model.util.ElementFilter.fieldsIn; import static javax.lang.model.util.ElementFilter.fieldsIn;
import static javax.lang.model.util.ElementFilter.methodsIn; import static javax.lang.model.util.ElementFilter.methodsIn;
import static org.hibernate.jpamodelgen.util.Constants.FIND;
import static org.hibernate.jpamodelgen.util.Constants.HQL; import static org.hibernate.jpamodelgen.util.Constants.HQL;
import static org.hibernate.jpamodelgen.util.Constants.SQL; import static org.hibernate.jpamodelgen.util.Constants.SQL;
import static org.hibernate.jpamodelgen.util.TypeUtils.containsAnnotation; import static org.hibernate.jpamodelgen.util.TypeUtils.containsAnnotation;
@ -87,46 +92,20 @@ public class JPAMetaModelEntityProcessor extends AbstractProcessor {
public static final String ADD_GENERATED_ANNOTATION = "addGeneratedAnnotation"; public static final String ADD_GENERATED_ANNOTATION = "addGeneratedAnnotation";
public static final String ADD_SUPPRESS_WARNINGS_ANNOTATION = "addSuppressWarningsAnnotation"; public static final String ADD_SUPPRESS_WARNINGS_ANNOTATION = "addSuppressWarningsAnnotation";
private static final Boolean ALLOW_OTHER_PROCESSORS_TO_CLAIM_ANNOTATIONS = Boolean.FALSE; private static final boolean ALLOW_OTHER_PROCESSORS_TO_CLAIM_ANNOTATIONS = false;
private Context context; private Context context;
@Override @Override
public void init(ProcessingEnvironment env) { public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init( env ); super.init( processingEnvironment );
context = new Context( env ); context = new Context( processingEnvironment );
context.logMessage( context.logMessage(
Diagnostic.Kind.NOTE, "Hibernate JPA 2 Static-Metamodel Generator " + Version.getVersionString() Diagnostic.Kind.NOTE,
"Hibernate/JPA static Metamodel Generator " + Version.getVersionString()
); );
PackageElement jakartaInjectPackage = boolean fullyAnnotationConfigured = handleSettings( processingEnvironment );
context.getProcessingEnvironment().getElementUtils()
.getPackageElement( "jakarta.inject" );
context.setAddInjectAnnotation( jakartaInjectPackage != null );
String tmp = env.getOptions().get( JPAMetaModelEntityProcessor.ADD_GENERATED_ANNOTATION );
if ( tmp != null ) {
boolean addGeneratedAnnotation = Boolean.parseBoolean( tmp );
context.setAddGeneratedAnnotation( addGeneratedAnnotation );
}
else {
PackageElement jakartaAnnotationPackage =
context.getProcessingEnvironment().getElementUtils()
.getPackageElement( "jakarta.annotation" );
context.setAddGeneratedAnnotation( jakartaAnnotationPackage != null );
}
tmp = env.getOptions().get( JPAMetaModelEntityProcessor.ADD_GENERATION_DATE );
boolean addGenerationDate = Boolean.parseBoolean( tmp );
context.setAddGenerationDate( addGenerationDate );
tmp = env.getOptions().get( JPAMetaModelEntityProcessor.ADD_SUPPRESS_WARNINGS_ANNOTATION );
boolean addSuppressWarningsAnnotation = Boolean.parseBoolean( tmp );
context.setAddSuppressWarningsAnnotation( addSuppressWarningsAnnotation );
tmp = env.getOptions().get( JPAMetaModelEntityProcessor.FULLY_ANNOTATION_CONFIGURED_OPTION );
boolean fullyAnnotationConfigured = Boolean.parseBoolean( tmp );
if ( !fullyAnnotationConfigured ) { if ( !fullyAnnotationConfigured ) {
JpaDescriptorParser parser = new JpaDescriptorParser( context ); JpaDescriptorParser parser = new JpaDescriptorParser( context );
parser.parseXml(); parser.parseXml();
@ -136,6 +115,32 @@ public class JPAMetaModelEntityProcessor extends AbstractProcessor {
} }
} }
private boolean handleSettings(ProcessingEnvironment environment) {
final PackageElement jakartaInjectPackage =
context.getProcessingEnvironment().getElementUtils()
.getPackageElement( "jakarta.inject" );
context.setAddInjectAnnotation( jakartaInjectPackage != null );
final Map<String, String> options = environment.getOptions();
String setting = options.get( ADD_GENERATED_ANNOTATION );
if ( setting != null ) {
context.setAddGeneratedAnnotation( parseBoolean( setting ) );
}
else {
PackageElement jakartaAnnotationPackage =
context.getProcessingEnvironment().getElementUtils()
.getPackageElement( "jakarta.annotation" );
context.setAddGeneratedAnnotation( jakartaAnnotationPackage != null );
}
context.setAddGenerationDate(parseBoolean( options.get( ADD_GENERATION_DATE ) ) );
context.setAddSuppressWarningsAnnotation( parseBoolean( options.get( ADD_SUPPRESS_WARNINGS_ANNOTATION ) ) );
return parseBoolean( options.get( FULLY_ANNOTATION_CONFIGURED_OPTION ) );
}
@Override @Override
public SourceVersion getSupportedSourceVersion() { public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported(); return SourceVersion.latestSupported();
@ -143,50 +148,80 @@ public class JPAMetaModelEntityProcessor extends AbstractProcessor {
@Override @Override
public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnvironment) { public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnvironment) {
// see also METAGEN-45
if ( roundEnvironment.processingOver() || annotations.size() == 0 ) {
return ALLOW_OTHER_PROCESSORS_TO_CLAIM_ANNOTATIONS;
}
if ( context.isFullyXmlConfigured() ) { // https://hibernate.atlassian.net/browse/METAGEN-45 claims that we need
// if ( roundEnvironment.processingOver() || annotations.size() == 0)
// but that was back on JDK 6 and I don't see why it should be necessary
// - in fact we want to use the last round to run the 'elementsToRedo'
if ( roundEnvironment.processingOver() ) {
final Set<CharSequence> elementsToRedo = context.getElementsToRedo();
if ( !elementsToRedo.isEmpty() ) {
context.logMessage( Diagnostic.Kind.ERROR, "Failed to generate code for " + elementsToRedo );
}
}
else if ( context.isFullyXmlConfigured() ) {
context.logMessage( context.logMessage(
Diagnostic.Kind.OTHER, Diagnostic.Kind.OTHER,
"Skipping the processing of annotations since persistence unit is purely xml configured." "Skipping the processing of annotations since persistence unit is purely XML configured."
); );
return ALLOW_OTHER_PROCESSORS_TO_CLAIM_ANNOTATIONS;
} }
else {
try { try {
processClasses( roundEnvironment ); processClasses( roundEnvironment );
createMetaModelClasses(); createMetaModelClasses();
}
catch (Exception e) {
context.logMessage( Diagnostic.Kind.ERROR, "Error generating JPA metamodel:" + e.getMessage() );
}
} }
catch (Exception e) {
context.logMessage( Diagnostic.Kind.ERROR, "Error generating JPA metamodel:" + e.getMessage() );
}
return ALLOW_OTHER_PROCESSORS_TO_CLAIM_ANNOTATIONS; return ALLOW_OTHER_PROCESSORS_TO_CLAIM_ANNOTATIONS;
} }
private void processClasses(RoundEnvironment roundEnvironment) { private void processClasses(RoundEnvironment roundEnvironment) {
for ( CharSequence elementName : new HashSet<>( context.getElementsToRedo() ) ) {
context.logMessage( Diagnostic.Kind.OTHER, "Redoing element: " + elementName );
final TypeElement typeElement = context.getElementUtils().getTypeElement( elementName );
try {
final AnnotationMetaEntity metaEntity =
AnnotationMetaEntity.create( typeElement, context, false );
context.addMetaAuxiliary( metaEntity.getQualifiedName(), metaEntity );
context.removeElementToRedo( elementName );
}
catch (ProcessLaterException processLaterException) {
// leave it there for next time
}
}
for ( Element element : roundEnvironment.getRootElements() ) { for ( Element element : roundEnvironment.getRootElements() ) {
if ( isJPAEntity( element ) ) { if ( isJPAEntity( element ) ) {
context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated class " + element.toString() ); context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated class " + element );
handleRootElementAnnotationMirrors( element ); handleRootElementAnnotationMirrors( element );
} }
else if ( hasAuxiliaryAnnotations( element ) ) { else if ( hasAuxiliaryAnnotations( element ) ) {
context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated class " + element.toString() ); context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated class " + element );
handleRootElementAuxiliaryAnnotationMirrors( element ); handleRootElementAuxiliaryAnnotationMirrors( element );
} }
else if ( element instanceof TypeElement ) { else if ( element instanceof TypeElement ) {
for ( Element enclosedElement : element.getEnclosedElements() ) { final TypeElement typeElement = (TypeElement) element;
if ( containsAnnotation( enclosedElement, HQL, SQL ) ) { try {
final AnnotationMetaEntity metaEntity = for ( Element member : typeElement.getEnclosedElements() ) {
AnnotationMetaEntity.create( (TypeElement) element, context, false ); if ( containsAnnotation( member, HQL, SQL, FIND ) ) {
context.addMetaAuxiliary( metaEntity.getQualifiedName(), metaEntity ); final AnnotationMetaEntity metaEntity =
break; AnnotationMetaEntity.create( typeElement, context, false );
context.addMetaAuxiliary( metaEntity.getQualifiedName(), metaEntity );
break;
}
}
}
catch (ProcessLaterException processLaterException) {
final Name qualifiedName = typeElement.getQualifiedName();
context.logMessage(
Diagnostic.Kind.OTHER,
"Could not process: " + qualifiedName + " (will redo in next round)"
);
context.addElementToRedo( qualifiedName );
} }
} }
}
} }
} }
@ -370,8 +405,8 @@ public class JPAMetaModelEntityProcessor extends AbstractProcessor {
static class ContainsAttributeTypeVisitor extends SimpleTypeVisitor8<Boolean, Element> { static class ContainsAttributeTypeVisitor extends SimpleTypeVisitor8<Boolean, Element> {
private Context context; private final Context context;
private TypeElement type; private final TypeElement type;
ContainsAttributeTypeVisitor(TypeElement elem, Context context) { ContainsAttributeTypeVisitor(TypeElement elem, Context context) {
this.context = context; this.context = context;

View File

@ -18,7 +18,6 @@ import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier; import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name; import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement; import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType; import javax.lang.model.type.DeclaredType;
@ -144,8 +143,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
} }
private static String getPackageName(Context context, TypeElement element) { private static String getPackageName(Context context, TypeElement element) {
PackageElement packageOf = context.getElementUtils().getPackageOf( element ); return context.getElementUtils().getPackageOf( element ).getQualifiedName().toString();
return context.getElementUtils().getName( packageOf.getQualifiedName() ).toString();
} }
@Override @Override
@ -485,7 +483,11 @@ public class AnnotationMetaEntity extends AnnotationMeta {
else { else {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final List<AnnotationValue> annotationValues = (List<AnnotationValue>) enabledFetchProfiles; final List<AnnotationValue> annotationValues = (List<AnnotationValue>) enabledFetchProfiles;
return annotationValues.stream().map(AnnotationValue::toString).collect(toList()); List<String> result = annotationValues.stream().map(AnnotationValue::toString).collect(toList());
if ( result.stream().anyMatch("<error>"::equals) ) {
throw new ProcessLaterException();
}
return result;
} }
} }

View File

@ -0,0 +1,10 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpamodelgen.annotation;
public class ProcessLaterException extends RuntimeException {
}

View File

@ -58,7 +58,7 @@ public class JpaDescriptorParser {
public JpaDescriptorParser(Context context) { public JpaDescriptorParser(Context context) {
this.context = context; this.context = context;
this.entityMappings = new ArrayList<EntityMappings>(); this.entityMappings = new ArrayList<>();
this.xmlParserHelper = new XmlParserHelper( context ); this.xmlParserHelper = new XmlParserHelper( context );
} }
@ -87,7 +87,7 @@ public class JpaDescriptorParser {
} }
private Collection<String> determineMappingFileNames() { private Collection<String> determineMappingFileNames() {
Collection<String> mappingFileNames = new ArrayList<String>(); Collection<String> mappingFileNames = new ArrayList<>();
Persistence persistence = getPersistence(); Persistence persistence = getPersistence();
if ( persistence != null ) { if ( persistence != null ) {
@ -227,10 +227,7 @@ public class JpaDescriptorParser {
} }
} }
} }
catch (IOException e) { catch (IOException|ClassNotFoundException e) {
//handled in the outer scope
}
catch (ClassNotFoundException e) {
//handled in the outer scope //handled in the outer scope
} }
} }
@ -436,12 +433,7 @@ public class JpaDescriptorParser {
for ( EntityMappings mappings : entityMappings ) { for ( EntityMappings mappings : entityMappings ) {
PersistenceUnitMetadata meta = mappings.getPersistenceUnitMetadata(); PersistenceUnitMetadata meta = mappings.getPersistenceUnitMetadata();
if ( meta != null ) { if ( meta != null ) {
if ( meta.getXmlMappingMetadataComplete() != null ) { context.mappingDocumentFullyXmlConfigured( meta.getXmlMappingMetadataComplete() != null );
context.mappingDocumentFullyXmlConfigured( true );
}
else {
context.mappingDocumentFullyXmlConfigured( false );
}
PersistenceUnitDefaults persistenceUnitDefaults = meta.getPersistenceUnitDefaults(); PersistenceUnitDefaults persistenceUnitDefaults = meta.getPersistenceUnitDefaults();
if ( persistenceUnitDefaults != null ) { if ( persistenceUnitDefaults != null ) {