-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
…rictions Signed-off-by: Andriy Redko <andriy.redko@aiven.io>
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,353 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
*/ | ||
|
||
package org.opensearch.common.annotation.processor; | ||
|
||
import org.opensearch.common.Nullable; | ||
import org.opensearch.common.annotation.DeprecatedApi; | ||
import org.opensearch.common.annotation.ExperimentalApi; | ||
import org.opensearch.common.annotation.InternalApi; | ||
import org.opensearch.common.annotation.PublicApi; | ||
|
||
import javax.annotation.processing.AbstractProcessor; | ||
import javax.annotation.processing.RoundEnvironment; | ||
import javax.annotation.processing.SupportedAnnotationTypes; | ||
import javax.lang.model.AnnotatedConstruct; | ||
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.ExecutableElement; | ||
import javax.lang.model.element.Modifier; | ||
import javax.lang.model.element.PackageElement; | ||
import javax.lang.model.element.TypeElement; | ||
import javax.lang.model.element.TypeParameterElement; | ||
import javax.lang.model.element.VariableElement; | ||
import javax.lang.model.type.ArrayType; | ||
import javax.lang.model.type.DeclaredType; | ||
import javax.lang.model.type.ReferenceType; | ||
import javax.lang.model.type.TypeMirror; | ||
import javax.lang.model.type.TypeVariable; | ||
import javax.lang.model.type.WildcardType; | ||
import javax.tools.Diagnostic.Kind; | ||
|
||
import java.util.HashSet; | ||
import java.util.Set; | ||
|
||
/** | ||
* The annotation processor for API related annotations: {@link DeprecatedApi}, {@link ExperimentalApi}, | ||
* {@link InternalApi} and {@link PublicApi}. | ||
* <p> | ||
* The checks are built on top of the following rules: | ||
* <ul> | ||
* <li>introspect each type annotated with {@link PublicApi}, {@link DeprecatedApi} or {@link ExperimentalApi}, | ||
* filtering out package-private declarations</li> | ||
* <li>make sure those leak only {@link PublicApi}, {@link DeprecatedApi} or {@link ExperimentalApi} types as well (exceptions, | ||
* method return values, method arguments, method generic type arguments, class generic type arguments, annotations)</li> | ||
* <li>recursively follow the type introspection chains to enforce the rules down the line</li> | ||
* </ul> | ||
*/ | ||
@InternalApi | ||
@SupportedAnnotationTypes("org.opensearch.common.annotation.*") | ||
public class ApiAnnotationProcessor extends AbstractProcessor { | ||
private static final String OPTION_CONTINUE_ON_FAILING_CHECKS = "continueOnFailingChecks"; | ||
private static final String OPENSEARCH_PACKAGE = "org.opensearch"; | ||
|
||
private final Set<Element> reported = new HashSet<>(); | ||
private final Set<AnnotatedConstruct> processed = new HashSet<>(); | ||
private Kind reportFailureAs = Kind.ERROR; | ||
|
||
@Override | ||
public SourceVersion getSupportedSourceVersion() { | ||
return SourceVersion.latest(); | ||
} | ||
|
||
@Override | ||
public Set<String> getSupportedOptions() { | ||
return Set.of(OPTION_CONTINUE_ON_FAILING_CHECKS); | ||
} | ||
|
||
@Override | ||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment round) { | ||
processingEnv.getMessager().printMessage(Kind.NOTE, "Processing OpenSearch Api annotations"); | ||
|
||
if (processingEnv.getOptions().containsKey(OPTION_CONTINUE_ON_FAILING_CHECKS) == true) { | ||
reportFailureAs = Kind.NOTE; | ||
Check warning on line 80 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L80
|
||
} | ||
|
||
final Set<? extends Element> elements = round.getElementsAnnotatedWithAny( | ||
Set.of(PublicApi.class, ExperimentalApi.class, DeprecatedApi.class) | ||
); | ||
|
||
for (var element : elements) { | ||
if (!checkPackage(element)) { | ||
continue; | ||
} | ||
|
||
// Skip all not-public elements | ||
if (!element.getModifiers().contains(Modifier.PUBLIC)) { | ||
continue; | ||
Check warning on line 94 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L94
|
||
} | ||
|
||
if (element instanceof TypeElement) { | ||
process((TypeElement) element); | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Check top level executable element | ||
* @param executable top level executable element | ||
* @param enclosing enclosing element | ||
*/ | ||
private void process(ExecutableElement executable, Element enclosing) { | ||
if (!inspectable(executable)) { | ||
return; | ||
} | ||
|
||
// The executable element should not be internal (unless constructor for injectable core component) | ||
checkNotInternal(enclosing, executable); | ||
|
||
// Process method return types | ||
final TypeMirror returnType = executable.getReturnType(); | ||
if (returnType instanceof ReferenceType) { | ||
process(executable, (ReferenceType) returnType); | ||
Check warning on line 121 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L121
|
||
} | ||
|
||
// Process method thrown types | ||
for (final TypeMirror thrownType : executable.getThrownTypes()) { | ||
if (thrownType instanceof ReferenceType) { | ||
process(executable, (ReferenceType) thrownType); | ||
} | ||
} | ||
|
||
// Process method type parameters | ||
for (final TypeParameterElement typeParameter : executable.getTypeParameters()) { | ||
for (final TypeMirror boundType : typeParameter.getBounds()) { | ||
if (boundType instanceof ReferenceType) { | ||
process(executable, (ReferenceType) boundType); | ||
Check warning on line 135 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L135
|
||
} | ||
} | ||
} | ||
Check warning on line 138 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L137-L138
|
||
|
||
// Process method arguments | ||
for (final VariableElement parameter : executable.getParameters()) { | ||
final TypeMirror parameterType = parameter.asType(); | ||
if (parameterType instanceof ReferenceType) { | ||
process(executable, (ReferenceType) parameterType); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Check wildcard type bounds referred by an element | ||
* @param executable element | ||
* @param type wildcard type | ||
*/ | ||
private void process(ExecutableElement executable, WildcardType type) { | ||
if (type.getExtendsBound() instanceof ReferenceType) { | ||
process(executable, (ReferenceType) type.getExtendsBound()); | ||
Check warning on line 156 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L156
|
||
} | ||
|
||
if (type.getSuperBound() instanceof ReferenceType) { | ||
process(executable, (ReferenceType) type.getSuperBound()); | ||
Check warning on line 160 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L160
|
||
} | ||
} | ||
Check warning on line 162 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L162
|
||
|
||
/** | ||
* Check reference type bounds referred by an executable element | ||
* @param executable executable element | ||
* @param ref reference type | ||
*/ | ||
private void process(ExecutableElement executable, ReferenceType ref) { | ||
// The element has been processed already | ||
if (processed.add(ref) == false) { | ||
return; | ||
Check warning on line 172 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L172
|
||
} | ||
|
||
if (ref instanceof DeclaredType) { | ||
final DeclaredType declaredType = (DeclaredType) ref; | ||
|
||
final Element element = declaredType.asElement(); | ||
if (inspectable(element)) { | ||
checkNotInternal(executable.getEnclosingElement(), element); | ||
checkPublic(executable.getEnclosingElement(), element); | ||
} | ||
|
||
for (final TypeMirror type : declaredType.getTypeArguments()) { | ||
if (type instanceof ReferenceType) { | ||
process(executable, (ReferenceType) type); | ||
Check warning on line 186 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L186
|
||
} else if (type instanceof WildcardType) { | ||
process(executable, (WildcardType) type); | ||
Check warning on line 188 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L188
|
||
} | ||
} | ||
Check warning on line 190 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L190
|
||
} else if (ref instanceof ArrayType) { | ||
final TypeMirror componentType = ((ArrayType) ref).getComponentType(); | ||
Check warning on line 192 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L192
|
||
if (componentType instanceof ReferenceType) { | ||
process(executable, (ReferenceType) componentType); | ||
Check warning on line 194 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L194
|
||
} | ||
} else if (ref instanceof TypeVariable) { | ||
final TypeVariable typeVariable = (TypeVariable) ref; | ||
Check warning on line 197 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L197
|
||
if (typeVariable.getUpperBound() instanceof ReferenceType) { | ||
process(executable, (ReferenceType) typeVariable.getUpperBound()); | ||
Check warning on line 199 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L199
|
||
} | ||
if (typeVariable.getLowerBound() instanceof ReferenceType) { | ||
process(executable, (ReferenceType) typeVariable.getLowerBound()); | ||
Check warning on line 202 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L202
|
||
} | ||
} | ||
|
||
// Check this elements annotations | ||
for (final AnnotationMirror annotation : ref.getAnnotationMirrors()) { | ||
final Element element = annotation.getAnnotationType().asElement(); | ||
Check warning on line 208 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L208
|
||
if (inspectable(element)) { | ||
checkNotInternal(executable.getEnclosingElement(), element); | ||
checkPublic(executable.getEnclosingElement(), element); | ||
Check warning on line 211 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L210-L211
|
||
} | ||
} | ||
Check warning on line 213 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L213
|
||
} | ||
|
||
/** | ||
* Check if a particular executable element should be inspected or not | ||
* @param executable executable element to inspect | ||
* @return {@code true} if a particular executable element should be inspected, {@code false} otherwise | ||
*/ | ||
private boolean inspectable(ExecutableElement executable) { | ||
// The constructors for public APIs could use non-public APIs when those are supposed to be only | ||
// consumed (not instantiated) by external consumers. | ||
return executable.getKind() != ElementKind.CONSTRUCTOR && executable.getModifiers().contains(Modifier.PUBLIC); | ||
} | ||
|
||
/** | ||
* Check if a particular element should be inspected or not | ||
* @param element element to inspect | ||
* @return {@code true} if a particular element should be inspected, {@code false} otherwise | ||
*/ | ||
private boolean inspectable(Element element) { | ||
final PackageElement pckg = processingEnv.getElementUtils().getPackageOf(element); | ||
return pckg.getQualifiedName().toString().startsWith(OPENSEARCH_PACKAGE); | ||
} | ||
|
||
/** | ||
* Check if a particular element belongs to OpenSeach managed packages | ||
* @param element element to inspect | ||
* @return {@code true} if a particular element belongs to OpenSeach managed packages, {@code false} otherwise | ||
*/ | ||
private boolean checkPackage(Element element) { | ||
// The element was reported already | ||
if (reported.contains(element)) { | ||
return false; | ||
} | ||
|
||
final PackageElement pckg = processingEnv.getElementUtils().getPackageOf(element); | ||
final boolean belongsToOpenSearch = pckg.getQualifiedName().toString().startsWith(OPENSEARCH_PACKAGE); | ||
|
||
if (!belongsToOpenSearch) { | ||
reported.add(element); | ||
Check warning on line 252 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L252
|
||
|
||
processingEnv.getMessager() | ||
.printMessage( | ||
Check warning on line 255 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L254-L255
|
||
reportFailureAs, | ||
"The type " | ||
+ element | ||
+ " is not residing in " | ||
+ OPENSEARCH_PACKAGE | ||
+ ".* package " | ||
+ "and should not be annotated as OpenSearch APIs." | ||
); | ||
} | ||
|
||
return belongsToOpenSearch; | ||
} | ||
|
||
/** | ||
* Check the fields, methods, constructors, and member types that are directly | ||
* declared in this class or interface. | ||
* @param element class or interface | ||
*/ | ||
private void process(Element element) { | ||
// Check the fields, methods, constructors, and member types that are directly | ||
// declared in this class or interface. | ||
for (final Element enclosed : element.getEnclosedElements()) { | ||
// Skip all not-public elements | ||
if (!enclosed.getModifiers().contains(Modifier.PUBLIC)) { | ||
continue; | ||
Check warning on line 280 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L280
|
||
} | ||
|
||
if (enclosed instanceof ExecutableElement) { | ||
process((ExecutableElement) enclosed, element); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Check if element is public and annotated with {@link PublicApi}, {@link DeprecatedApi} or {@link ExperimentalApi} | ||
* @param referencedBy the referrer for the element | ||
* @param element element to check | ||
*/ | ||
private void checkPublic(@Nullable Element referencedBy, final Element element) { | ||
// The element was reported already | ||
if (reported.contains(element)) { | ||
return; | ||
Check warning on line 297 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L297
|
||
} | ||
|
||
if (!element.getModifiers().contains(Modifier.PUBLIC)) { | ||
reported.add(element); | ||
|
||
processingEnv.getMessager() | ||
.printMessage( | ||
reportFailureAs, | ||
"The element " | ||
+ element | ||
+ " is part of the public APIs but does not have public visibility" | ||
+ ((referencedBy != null) ? " (referenced by " + referencedBy + ") " : "") | ||
); | ||
} | ||
|
||
if (element.getAnnotation(PublicApi.class) == null | ||
&& element.getAnnotation(ExperimentalApi.class) == null | ||
&& element.getAnnotation(DeprecatedApi.class) == null) { | ||
reported.add(element); | ||
|
||
processingEnv.getMessager() | ||
.printMessage( | ||
reportFailureAs, | ||
"The element " | ||
+ element | ||
+ " is part of the public APIs but is not maked as @PublicApi, @ExperimentalApi or @DeprecatedApi" | ||
+ ((referencedBy != null) ? " (referenced by " + referencedBy + ") " : "") | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* Check if element is not annotated with {@link InternalApi} | ||
* @param referencedBy the referrer for the element | ||
* @param element element to check | ||
*/ | ||
private void checkNotInternal(@Nullable Element referencedBy, final Element element) { | ||
// The element was reported already | ||
if (reported.contains(element)) { | ||
return; | ||
Check warning on line 337 in libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java Codecov / codecov/patchlibs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java#L337
|
||
} | ||
|
||
if (element.getAnnotation(InternalApi.class) != null) { | ||
reported.add(element); | ||
|
||
processingEnv.getMessager() | ||
.printMessage( | ||
reportFailureAs, | ||
"The element " | ||
+ element | ||
+ " is part of the public APIs but is marked as @InternalApi" | ||
+ ((referencedBy != null) ? " (referenced by " + referencedBy + ") " : "") | ||
); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
*/ | ||
|
||
/** | ||
* Classes related yo OpenSearch API annotation processing | ||
* | ||
* @opensearch.internal | ||
*/ | ||
@org.opensearch.common.annotation.InternalApi | ||
package org.opensearch.common.annotation.processor; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# | ||
# SPDX-License-Identifier: Apache-2.0 | ||
# | ||
# The OpenSearch Contributors require contributions made to | ||
# this file be licensed under the Apache-2.0 license or a | ||
# compatible open source license. | ||
# | ||
# Modifications Copyright OpenSearch Contributors. See | ||
# GitHub history for details. | ||
# | ||
|
||
org.opensearch.common.annotation.processor.ApiAnnotationProcessor |