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.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
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";
/**
* 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<>();
@ -50,7 +51,9 @@ public final class Context {
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 lazyXmlParsing;
private final String persistenceXmlLocation;
@ -68,12 +71,12 @@ public final class Context {
private AccessType persistenceUnitDefaultAccessType;
// 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) {
this.pe = pe;
public Context(ProcessingEnvironment processingEnvironment) {
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.startsWith("/") ) {
persistenceXmlOption = "/" + persistenceXmlOption;
@ -84,7 +87,7 @@ public final class Context {
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 ) {
ormXmlFiles = new ArrayList<>();
for ( String ormFile : ormXmlOption.split( "," ) ) {
@ -98,12 +101,12 @@ public final class Context {
ormXmlFiles = Collections.emptyList();
}
lazyXmlParsing = Boolean.parseBoolean( pe.getOptions().get( JPAMetaModelEntityProcessor.LAZY_XML_PARSING ) );
logDebug = Boolean.parseBoolean( pe.getOptions().get( JPAMetaModelEntityProcessor.DEBUG_OPTION ) );
lazyXmlParsing = Boolean.parseBoolean( processingEnvironment.getOptions().get( JPAMetaModelEntityProcessor.LAZY_XML_PARSING ) );
logDebug = Boolean.parseBoolean( processingEnvironment.getOptions().get( JPAMetaModelEntityProcessor.DEBUG_OPTION ) );
}
public ProcessingEnvironment getProcessingEnvironment() {
return pe;
return processingEnvironment;
}
public boolean addInjectAnnotation() {
@ -139,11 +142,11 @@ public final class Context {
}
public Elements getElementUtils() {
return pe.getElementUtils();
return processingEnvironment.getElementUtils();
}
public Types getTypeUtils() {
return pe.getTypeUtils();
return processingEnvironment.getTypeUtils();
}
public String getPersistenceXmlLocation() {
@ -207,7 +210,7 @@ public final class Context {
}
public TypeElement getTypeElementForFullyQualifiedName(String fqcn) {
Elements elementUtils = pe.getElementUtils();
Elements elementUtils = processingEnvironment.getElementUtils();
return elementUtils.getTypeElement( fqcn );
}
@ -219,9 +222,21 @@ public final class Context {
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) {
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.HashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
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.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
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.AnnotationMetaPackage;
import org.hibernate.jpamodelgen.annotation.ProcessLaterException;
import org.hibernate.jpamodelgen.model.Metamodel;
import org.hibernate.jpamodelgen.util.Constants;
import org.hibernate.jpamodelgen.util.StringUtil;
@ -37,8 +40,10 @@ import org.hibernate.jpamodelgen.xml.JpaDescriptorParser;
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.methodsIn;
import static org.hibernate.jpamodelgen.util.Constants.FIND;
import static org.hibernate.jpamodelgen.util.Constants.HQL;
import static org.hibernate.jpamodelgen.util.Constants.SQL;
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_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;
@Override
public void init(ProcessingEnvironment env) {
super.init( env );
context = new Context( env );
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init( processingEnvironment );
context = new Context( processingEnvironment );
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 =
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 );
boolean fullyAnnotationConfigured = handleSettings( processingEnvironment );
if ( !fullyAnnotationConfigured ) {
JpaDescriptorParser parser = new JpaDescriptorParser( context );
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
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
@ -143,50 +148,80 @@ public class JPAMetaModelEntityProcessor extends AbstractProcessor {
@Override
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(
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;
}
try {
processClasses( roundEnvironment );
createMetaModelClasses();
else {
try {
processClasses( roundEnvironment );
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;
}
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() ) {
if ( isJPAEntity( element ) ) {
context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated class " + element.toString() );
handleRootElementAnnotationMirrors( element );
}
else if ( hasAuxiliaryAnnotations( element ) ) {
context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated class " + element.toString() );
handleRootElementAuxiliaryAnnotationMirrors( element );
}
else if ( element instanceof TypeElement ) {
for ( Element enclosedElement : element.getEnclosedElements() ) {
if ( containsAnnotation( enclosedElement, HQL, SQL ) ) {
final AnnotationMetaEntity metaEntity =
AnnotationMetaEntity.create( (TypeElement) element, context, false );
context.addMetaAuxiliary( metaEntity.getQualifiedName(), metaEntity );
break;
if ( isJPAEntity( element ) ) {
context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated class " + element );
handleRootElementAnnotationMirrors( element );
}
else if ( hasAuxiliaryAnnotations( element ) ) {
context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated class " + element );
handleRootElementAuxiliaryAnnotationMirrors( element );
}
else if ( element instanceof TypeElement ) {
final TypeElement typeElement = (TypeElement) element;
try {
for ( Element member : typeElement.getEnclosedElements() ) {
if ( containsAnnotation( member, HQL, SQL, FIND ) ) {
final AnnotationMetaEntity metaEntity =
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> {
private Context context;
private TypeElement type;
private final Context context;
private final TypeElement type;
ContainsAttributeTypeVisitor(TypeElement elem, 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.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
@ -144,8 +143,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
}
private static String getPackageName(Context context, TypeElement element) {
PackageElement packageOf = context.getElementUtils().getPackageOf( element );
return context.getElementUtils().getName( packageOf.getQualifiedName() ).toString();
return context.getElementUtils().getPackageOf( element ).getQualifiedName().toString();
}
@Override
@ -485,7 +483,11 @@ public class AnnotationMetaEntity extends AnnotationMeta {
else {
@SuppressWarnings("unchecked")
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) {
this.context = context;
this.entityMappings = new ArrayList<EntityMappings>();
this.entityMappings = new ArrayList<>();
this.xmlParserHelper = new XmlParserHelper( context );
}
@ -87,7 +87,7 @@ public class JpaDescriptorParser {
}
private Collection<String> determineMappingFileNames() {
Collection<String> mappingFileNames = new ArrayList<String>();
Collection<String> mappingFileNames = new ArrayList<>();
Persistence persistence = getPersistence();
if ( persistence != null ) {
@ -227,10 +227,7 @@ public class JpaDescriptorParser {
}
}
}
catch (IOException e) {
//handled in the outer scope
}
catch (ClassNotFoundException e) {
catch (IOException|ClassNotFoundException e) {
//handled in the outer scope
}
}
@ -436,12 +433,7 @@ public class JpaDescriptorParser {
for ( EntityMappings mappings : entityMappings ) {
PersistenceUnitMetadata meta = mappings.getPersistenceUnitMetadata();
if ( meta != null ) {
if ( meta.getXmlMappingMetadataComplete() != null ) {
context.mappingDocumentFullyXmlConfigured( true );
}
else {
context.mappingDocumentFullyXmlConfigured( false );
}
context.mappingDocumentFullyXmlConfigured( meta.getXmlMappingMetadataComplete() != null );
PersistenceUnitDefaults persistenceUnitDefaults = meta.getPersistenceUnitDefaults();
if ( persistenceUnitDefaults != null ) {