diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5fc7b5a6c9d08..a63eb6695cc26 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -31,6 +31,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Allowing pipeline processors to access index mapping info by passing ingest service ref as part of the processor factory parameters ([#10307](https://github.com/opensearch-project/OpenSearch/pull/10307))
- Make number of segment metadata files in remote segment store configurable ([#11329](https://github.com/opensearch-project/OpenSearch/pull/11329))
- Allow changing number of replicas of searchable snapshot index ([#11317](https://github.com/opensearch-project/OpenSearch/pull/11317))
+- [BWC and API enforcement] Introduce checks for enforcing the API restrictions ([#11175](https://github.com/opensearch-project/OpenSearch/pull/11175))
### Dependencies
- Bumps jetty version to 9.4.52.v20230823 to fix GMS-2023-1857 ([#9822](https://github.com/opensearch-project/OpenSearch/pull/9822))
diff --git a/libs/common/src/main/java/org/opensearch/common/Nullable.java b/libs/common/src/main/java/org/opensearch/common/Nullable.java
index c663ef863ed48..70db2a3755eba 100644
--- a/libs/common/src/main/java/org/opensearch/common/Nullable.java
+++ b/libs/common/src/main/java/org/opensearch/common/Nullable.java
@@ -32,6 +32,8 @@
package org.opensearch.common;
+import org.opensearch.common.annotation.PublicApi;
+
import javax.annotation.CheckForNull;
import javax.annotation.meta.TypeQualifierNickname;
@@ -53,5 +55,6 @@
@CheckForNull
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD })
+@PublicApi(since = "1.0.0")
public @interface Nullable {
}
diff --git a/libs/common/src/main/java/org/opensearch/common/SuppressForbidden.java b/libs/common/src/main/java/org/opensearch/common/SuppressForbidden.java
index 1f1b28bcf6759..c479d7bd98e8a 100644
--- a/libs/common/src/main/java/org/opensearch/common/SuppressForbidden.java
+++ b/libs/common/src/main/java/org/opensearch/common/SuppressForbidden.java
@@ -31,6 +31,8 @@
package org.opensearch.common;
+import org.opensearch.common.annotation.PublicApi;
+
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -43,6 +45,7 @@
*/
@Retention(RetentionPolicy.CLASS)
@Target({ ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE })
+@PublicApi(since = "1.0.0")
public @interface SuppressForbidden {
String reason();
}
diff --git a/libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java b/libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java
new file mode 100644
index 0000000000000..1864aec4aa951
--- /dev/null
+++ b/libs/common/src/main/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessor.java
@@ -0,0 +1,369 @@
+/*
+ * 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}.
+ *
+ * The checks are built on top of the following rules:
+ *
+ * - introspect each type annotated with {@link PublicApi}, {@link DeprecatedApi} or {@link ExperimentalApi},
+ * filtering out package-private declarations
+ * - 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)
+ * - recursively follow the type introspection chains to enforce the rules down the line
+ *
+ */
+@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 reported = new HashSet<>();
+ private final Set processed = new HashSet<>();
+ private Kind reportFailureAs = Kind.ERROR;
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latest();
+ }
+
+ @Override
+ public Set 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;
+ }
+
+ 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
+ checkPublicVisibility(null, element);
+
+ 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);
+
+ // Check this elements annotations
+ for (final AnnotationMirror annotation : executable.getAnnotationMirrors()) {
+ final Element element = annotation.getAnnotationType().asElement();
+ if (inspectable(element)) {
+ checkNotInternal(executable.getEnclosingElement(), element);
+ checkPublic(executable.getEnclosingElement(), element);
+ }
+ }
+
+ // Process method return types
+ final TypeMirror returnType = executable.getReturnType();
+ if (returnType instanceof ReferenceType) {
+ process(executable, (ReferenceType) returnType);
+ }
+
+ // 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);
+ }
+ }
+ }
+
+ // 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());
+ }
+
+ if (type.getSuperBound() instanceof ReferenceType) {
+ process(executable, (ReferenceType) type.getSuperBound());
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ 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);
+ } else if (type instanceof WildcardType) {
+ process(executable, (WildcardType) type);
+ }
+ }
+ } else if (ref instanceof ArrayType) {
+ final TypeMirror componentType = ((ArrayType) ref).getComponentType();
+ if (componentType instanceof ReferenceType) {
+ process(executable, (ReferenceType) componentType);
+ }
+ } else if (ref instanceof TypeVariable) {
+ final TypeVariable typeVariable = (TypeVariable) ref;
+ if (typeVariable.getUpperBound() instanceof ReferenceType) {
+ process(executable, (ReferenceType) typeVariable.getUpperBound());
+ }
+ if (typeVariable.getLowerBound() instanceof ReferenceType) {
+ process(executable, (ReferenceType) typeVariable.getLowerBound());
+ }
+ }
+
+ // Check this elements annotations
+ for (final AnnotationMirror annotation : ref.getAnnotationMirrors()) {
+ final Element element = annotation.getAnnotationType().asElement();
+ if (inspectable(element)) {
+ checkNotInternal(executable.getEnclosingElement(), element);
+ checkPublic(executable.getEnclosingElement(), element);
+ }
+ }
+ }
+
+ /**
+ * 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);
+
+ processingEnv.getMessager()
+ .printMessage(
+ 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;
+ }
+
+ 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;
+ }
+
+ checkPublicVisibility(referencedBy, element);
+
+ 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 has public visibility (following Java visibility rules)
+ * @param referencedBy the referrer for the element
+ * @param element element to check
+ */
+ private void checkPublicVisibility(Element referencedBy, final Element element) {
+ if (!element.getModifiers().contains(Modifier.PUBLIC) && !element.getModifiers().contains(Modifier.PROTECTED)) {
+ reported.add(element);
+
+ processingEnv.getMessager()
+ .printMessage(
+ reportFailureAs,
+ "The element "
+ + element
+ + " is part of the public APIs but does not have public or protected visibility"
+ + ((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;
+ }
+
+ 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 + ") " : "")
+ );
+ }
+ }
+}
diff --git a/libs/common/src/main/java/org/opensearch/common/annotation/processor/package-info.java b/libs/common/src/main/java/org/opensearch/common/annotation/processor/package-info.java
new file mode 100644
index 0000000000000..fa23e4a7addce
--- /dev/null
+++ b/libs/common/src/main/java/org/opensearch/common/annotation/processor/package-info.java
@@ -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;
diff --git a/libs/common/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/libs/common/src/main/resources/META-INF/services/javax.annotation.processing.Processor
new file mode 100644
index 0000000000000..c4e4dfed864f2
--- /dev/null
+++ b/libs/common/src/main/resources/META-INF/services/javax.annotation.processing.Processor
@@ -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
\ No newline at end of file
diff --git a/libs/common/src/test/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessorTests.java b/libs/common/src/test/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessorTests.java
new file mode 100644
index 0000000000000..df04709458b29
--- /dev/null
+++ b/libs/common/src/test/java/org/opensearch/common/annotation/processor/ApiAnnotationProcessorTests.java
@@ -0,0 +1,476 @@
+/*
+ * 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.annotation.InternalApi;
+import org.opensearch.test.OpenSearchTestCase;
+
+import javax.tools.Diagnostic;
+
+import static org.opensearch.common.annotation.processor.CompilerSupport.HasDiagnostic.matching;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.instanceOf;
+
+@SuppressWarnings("deprecation")
+public class ApiAnnotationProcessorTests extends OpenSearchTestCase implements CompilerSupport {
+ public void testPublicApiMethodArgumentNotAnnotated() {
+ final CompilerResult result = compile("PublicApiMethodArgumentNotAnnotated.java", "NotAnnotated.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(3));
+
+ assertThat(
+ failure.diagnotics(),
+ hasItem(
+ matching(
+ Diagnostic.Kind.ERROR,
+ containsString(
+ "The element org.opensearch.common.annotation.processor.NotAnnotated is part of the public APIs but is not maked as @PublicApi, @ExperimentalApi or @DeprecatedApi "
+ + "(referenced by org.opensearch.common.annotation.processor.PublicApiMethodArgumentNotAnnotated)"
+ )
+ )
+ )
+ );
+ }
+
+ public void testPublicApiMethodArgumentNotAnnotatedGenerics() {
+ final CompilerResult result = compile("PublicApiMethodArgumentNotAnnotatedGenerics.java", "NotAnnotated.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(3));
+
+ assertThat(
+ failure.diagnotics(),
+ hasItem(
+ matching(
+ Diagnostic.Kind.ERROR,
+ containsString(
+ "The element org.opensearch.common.annotation.processor.NotAnnotated is part of the public APIs but is not maked as @PublicApi, @ExperimentalApi or @DeprecatedApi "
+ + "(referenced by org.opensearch.common.annotation.processor.PublicApiMethodArgumentNotAnnotatedGenerics)"
+ )
+ )
+ )
+ );
+ }
+
+ public void testPublicApiMethodThrowsNotAnnotated() {
+ final CompilerResult result = compile("PublicApiMethodThrowsNotAnnotated.java", "PublicApiAnnotated.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(3));
+
+ assertThat(
+ failure.diagnotics(),
+ hasItem(
+ matching(
+ Diagnostic.Kind.ERROR,
+ containsString(
+ "The element org.opensearch.common.annotation.processor.NotAnnotatedException is part of the public APIs but is not maked as @PublicApi, @ExperimentalApi or @DeprecatedApi "
+ + "(referenced by org.opensearch.common.annotation.processor.PublicApiMethodThrowsNotAnnotated)"
+ )
+ )
+ )
+ );
+ }
+
+ public void testPublicApiMethodArgumentNotAnnotatedPackagePrivate() {
+ final CompilerResult result = compile("PublicApiMethodArgumentNotAnnotatedPackagePrivate.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(4));
+
+ assertThat(
+ failure.diagnotics(),
+ hasItem(
+ matching(
+ Diagnostic.Kind.ERROR,
+ containsString(
+ "The element org.opensearch.common.annotation.processor.NotAnnotatedPackagePrivate is part of the public APIs but does not have public or protected visibility "
+ + "(referenced by org.opensearch.common.annotation.processor.PublicApiMethodArgumentNotAnnotatedPackagePrivate)"
+ )
+ )
+ )
+ );
+
+ assertThat(
+ failure.diagnotics(),
+ hasItem(
+ matching(
+ Diagnostic.Kind.ERROR,
+ containsString(
+ "The element org.opensearch.common.annotation.processor.NotAnnotatedPackagePrivate is part of the public APIs but is not maked as @PublicApi, @ExperimentalApi or @DeprecatedApi "
+ + "(referenced by org.opensearch.common.annotation.processor.PublicApiMethodArgumentNotAnnotatedPackagePrivate)"
+ )
+ )
+ )
+ );
+ }
+
+ public void testPublicApiMethodArgumentAnnotatedPackagePrivate() {
+ final CompilerResult result = compile("PublicApiMethodArgumentAnnotatedPackagePrivate.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(3));
+
+ assertThat(
+ failure.diagnotics(),
+ hasItem(
+ matching(
+ Diagnostic.Kind.ERROR,
+ containsString(
+ "The element org.opensearch.common.annotation.processor.AnnotatedPackagePrivate is part of the public APIs but does not have public or protected visibility "
+ + "(referenced by org.opensearch.common.annotation.processor.PublicApiMethodArgumentAnnotatedPackagePrivate)"
+ )
+ )
+ )
+ );
+ }
+
+ public void testPublicApiWithInternalApiMethod() {
+ final CompilerResult result = compile("PublicApiWithInternalApiMethod.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(3));
+
+ assertThat(
+ failure.diagnotics(),
+ hasItem(
+ matching(
+ Diagnostic.Kind.ERROR,
+ containsString(
+ "The element method() is part of the public APIs but is marked as @InternalApi (referenced by org.opensearch.common.annotation.processor.PublicApiWithInternalApiMethod)"
+ )
+ )
+ )
+ );
+ }
+
+ /**
+ * The constructor arguments have relaxed semantics at the moment: those could be not annotated or be annotated as {@link InternalApi}
+ */
+ public void testPublicApiConstructorArgumentNotAnnotated() {
+ final CompilerResult result = compile("PublicApiConstructorArgumentNotAnnotated.java", "NotAnnotated.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(2));
+
+ assertThat(failure.diagnotics(), not(hasItem(matching(Diagnostic.Kind.ERROR))));
+ }
+
+ /**
+ * The constructor arguments have relaxed semantics at the moment: those could be not annotated or be annotated as {@link InternalApi}
+ */
+ public void testPublicApiConstructorArgumentAnnotatedInternalApi() {
+ final CompilerResult result = compile("PublicApiConstructorArgumentAnnotatedInternalApi.java", "InternalApiAnnotated.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(2));
+
+ assertThat(failure.diagnotics(), not(hasItem(matching(Diagnostic.Kind.ERROR))));
+ }
+
+ public void testPublicApiWithExperimentalApiMethod() {
+ final CompilerResult result = compile("PublicApiWithExperimentalApiMethod.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(2));
+
+ assertThat(failure.diagnotics(), not(hasItem(matching(Diagnostic.Kind.ERROR))));
+ }
+
+ public void testPublicApiMethodReturnNotAnnotated() {
+ final CompilerResult result = compile("PublicApiMethodReturnNotAnnotated.java", "NotAnnotated.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(3));
+
+ assertThat(
+ failure.diagnotics(),
+ hasItem(
+ matching(
+ Diagnostic.Kind.ERROR,
+ containsString(
+ "The element org.opensearch.common.annotation.processor.NotAnnotated is part of the public APIs but is not maked as @PublicApi, @ExperimentalApi or @DeprecatedApi "
+ + "(referenced by org.opensearch.common.annotation.processor.PublicApiMethodReturnNotAnnotated)"
+ )
+ )
+ )
+ );
+ }
+
+ public void testPublicApiMethodReturnNotAnnotatedGenerics() {
+ final CompilerResult result = compile("PublicApiMethodReturnNotAnnotatedGenerics.java", "NotAnnotated.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(3));
+
+ assertThat(
+ failure.diagnotics(),
+ hasItem(
+ matching(
+ Diagnostic.Kind.ERROR,
+ containsString(
+ "The element org.opensearch.common.annotation.processor.NotAnnotated is part of the public APIs but is not maked as @PublicApi, @ExperimentalApi or @DeprecatedApi "
+ + "(referenced by org.opensearch.common.annotation.processor.PublicApiMethodReturnNotAnnotatedGenerics)"
+ )
+ )
+ )
+ );
+ }
+
+ public void testPublicApiMethodReturnNotAnnotatedArray() {
+ final CompilerResult result = compile("PublicApiMethodReturnNotAnnotatedArray.java", "NotAnnotated.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(3));
+
+ assertThat(
+ failure.diagnotics(),
+ hasItem(
+ matching(
+ Diagnostic.Kind.ERROR,
+ containsString(
+ "The element org.opensearch.common.annotation.processor.NotAnnotated is part of the public APIs but is not maked as @PublicApi, @ExperimentalApi or @DeprecatedApi "
+ + "(referenced by org.opensearch.common.annotation.processor.PublicApiMethodReturnNotAnnotatedArray)"
+ )
+ )
+ )
+ );
+ }
+
+ public void testPublicApiMethodReturnNotAnnotatedBoundedGenerics() {
+ final CompilerResult result = compile("PublicApiMethodReturnNotAnnotatedBoundedGenerics.java", "NotAnnotated.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(3));
+
+ assertThat(
+ failure.diagnotics(),
+ hasItem(
+ matching(
+ Diagnostic.Kind.ERROR,
+ containsString(
+ "The element org.opensearch.common.annotation.processor.NotAnnotated is part of the public APIs but is not maked as @PublicApi, @ExperimentalApi or @DeprecatedApi "
+ + "(referenced by org.opensearch.common.annotation.processor.PublicApiMethodReturnNotAnnotatedBoundedGenerics)"
+ )
+ )
+ )
+ );
+ }
+
+ public void testPublicApiMethodReturnNotAnnotatedAnnotation() {
+ final CompilerResult result = compile(
+ "PublicApiMethodReturnNotAnnotatedAnnotation.java",
+ "PublicApiAnnotated.java",
+ "NotAnnotatedAnnotation.java"
+ );
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(3));
+
+ assertThat(
+ failure.diagnotics(),
+ hasItem(
+ matching(
+ Diagnostic.Kind.ERROR,
+ containsString(
+ "The element org.opensearch.common.annotation.processor.NotAnnotatedAnnotation is part of the public APIs but is not maked as @PublicApi, @ExperimentalApi or @DeprecatedApi "
+ + "(referenced by org.opensearch.common.annotation.processor.PublicApiMethodReturnNotAnnotatedAnnotation)"
+ )
+ )
+ )
+ );
+ }
+
+ public void testPublicApiMethodReturnNotAnnotatedWildcardGenerics() {
+ final CompilerResult result = compile("PublicApiMethodReturnNotAnnotatedWildcardGenerics.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(2));
+
+ assertThat(failure.diagnotics(), not(hasItem(matching(Diagnostic.Kind.ERROR))));
+ }
+
+ public void testPublicApiWithPackagePrivateMethod() {
+ final CompilerResult result = compile("PublicApiWithPackagePrivateMethod.java", "NotAnnotated.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(2));
+
+ assertThat(failure.diagnotics(), not(hasItem(matching(Diagnostic.Kind.ERROR))));
+ }
+
+ public void testPublicApiMethodReturnSelf() {
+ final CompilerResult result = compile("PublicApiMethodReturnSelf.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(2));
+
+ assertThat(failure.diagnotics(), not(hasItem(matching(Diagnostic.Kind.ERROR))));
+ }
+
+ public void testExperimentalApiMethodReturnSelf() {
+ final CompilerResult result = compile("ExperimentalApiMethodReturnSelf.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(2));
+
+ assertThat(failure.diagnotics(), not(hasItem(matching(Diagnostic.Kind.ERROR))));
+ }
+
+ public void testDeprecatedApiMethodReturnSelf() {
+ final CompilerResult result = compile("DeprecatedApiMethodReturnSelf.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(2));
+
+ assertThat(failure.diagnotics(), not(hasItem(matching(Diagnostic.Kind.ERROR))));
+ }
+
+ public void testPublicApiPackagePrivate() {
+ final CompilerResult result = compile("PublicApiPackagePrivate.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(3));
+
+ assertThat(
+ failure.diagnotics(),
+ hasItem(
+ matching(
+ Diagnostic.Kind.ERROR,
+ containsString(
+ "The element org.opensearch.common.annotation.processor.PublicApiPackagePrivate is part of the public APIs but does not have public or protected visibility"
+ )
+ )
+ )
+ );
+ }
+
+ public void testPublicApiMethodGenericsArgumentNotAnnotated() {
+ final CompilerResult result = compile("PublicApiMethodGenericsArgumentNotAnnotated.java", "NotAnnotated.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(3));
+
+ assertThat(
+ failure.diagnotics(),
+ hasItem(
+ matching(
+ Diagnostic.Kind.ERROR,
+ containsString(
+ "The element org.opensearch.common.annotation.processor.NotAnnotated is part of the public APIs but is not maked as @PublicApi, @ExperimentalApi or @DeprecatedApi "
+ + "(referenced by org.opensearch.common.annotation.processor.PublicApiMethodGenericsArgumentNotAnnotated)"
+ )
+ )
+ )
+ );
+ }
+
+ public void testPublicApiMethodReturnAnnotatedArray() {
+ final CompilerResult result = compile("PublicApiMethodReturnAnnotatedArray.java", "PublicApiAnnotated.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(2));
+
+ assertThat(failure.diagnotics(), not(hasItem(matching(Diagnostic.Kind.ERROR))));
+ }
+
+ public void testPublicApiMethodGenericsArgumentAnnotated() {
+ final CompilerResult result = compile("PublicApiMethodGenericsArgumentAnnotated.java", "PublicApiAnnotated.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(2));
+
+ assertThat(failure.diagnotics(), not(hasItem(matching(Diagnostic.Kind.ERROR))));
+ }
+
+ public void testPublicApiAnnotatedNotOpensearch() {
+ final CompilerResult result = compileWithPackage("org.acme", "PublicApiAnnotated.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(3));
+
+ assertThat(
+ failure.diagnotics(),
+ hasItem(
+ matching(
+ Diagnostic.Kind.ERROR,
+ containsString(
+ "The type org.acme.PublicApiAnnotated is not residing in org.opensearch.* package and should not be annotated as OpenSearch APIs."
+ )
+ )
+ )
+ );
+ }
+
+ public void testPublicApiMethodReturnAnnotatedGenerics() {
+ final CompilerResult result = compile(
+ "PublicApiMethodReturnAnnotatedGenerics.java",
+ "PublicApiAnnotated.java",
+ "NotAnnotatedAnnotation.java"
+ );
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(3));
+
+ assertThat(
+ failure.diagnotics(),
+ hasItem(
+ matching(
+ Diagnostic.Kind.ERROR,
+ containsString(
+ "The element org.opensearch.common.annotation.processor.NotAnnotatedAnnotation is part of the public APIs but is not maked as @PublicApi, @ExperimentalApi or @DeprecatedApi "
+ + "(referenced by org.opensearch.common.annotation.processor.PublicApiMethodReturnAnnotatedGenerics)"
+ )
+ )
+ )
+ );
+ }
+
+ /**
+ * The type could expose protected inner types which are still considered to be a public API when used
+ */
+ public void testPublicApiWithProtectedInterface() {
+ final CompilerResult result = compile("PublicApiWithProtectedInterface.java");
+ assertThat(result, instanceOf(Failure.class));
+
+ final Failure failure = (Failure) result;
+ assertThat(failure.diagnotics(), hasSize(2));
+
+ assertThat(failure.diagnotics(), not(hasItem(matching(Diagnostic.Kind.ERROR))));
+ }
+}
diff --git a/libs/common/src/test/java/org/opensearch/common/annotation/processor/CompilerSupport.java b/libs/common/src/test/java/org/opensearch/common/annotation/processor/CompilerSupport.java
new file mode 100644
index 0000000000000..dcf8dd7945012
--- /dev/null
+++ b/libs/common/src/test/java/org/opensearch/common/annotation/processor/CompilerSupport.java
@@ -0,0 +1,139 @@
+/*
+ * 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.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaCompiler.CompilationTask;
+import javax.tools.JavaFileObject;
+import javax.tools.JavaFileObject.Kind;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+interface CompilerSupport {
+ default CompilerResult compile(String name, String... names) {
+ return compileWithPackage(ApiAnnotationProcessorTests.class.getPackageName(), name, names);
+ }
+
+ default CompilerResult compileWithPackage(String pck, String name, String... names) {
+ final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+ final DiagnosticCollector collector = new DiagnosticCollector<>();
+
+ try (StringWriter out = new StringWriter()) {
+ final StandardJavaFileManager fileManager = compiler.getStandardFileManager(collector, null, null);
+ final List files = Stream.concat(Stream.of(name), Arrays.stream(names))
+ .map(f -> asSource(pck, f))
+ .collect(Collectors.toList());
+
+ final CompilationTask task = compiler.getTask(out, fileManager, collector, null, null, files);
+ task.setProcessors(Collections.singleton(new ApiAnnotationProcessor()));
+
+ if (AccessController.doPrivileged((PrivilegedAction) () -> task.call())) {
+ return new Success();
+ } else {
+ return new Failure(collector.getDiagnostics());
+ }
+ } catch (final IOException ex) {
+ throw new UncheckedIOException(ex);
+ }
+ }
+
+ private static JavaFileObject asSource(String pkg, String name) {
+ final String resource = "/" + pkg.replaceAll("[.]", "/") + "/" + name;
+ final URL source = ApiAnnotationProcessorTests.class.getResource(resource);
+
+ return new SimpleJavaFileObject(URI.create(source.toExternalForm()), Kind.SOURCE) {
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+ try (final InputStream in = ApiAnnotationProcessorTests.class.getResourceAsStream(resource)) {
+ return new String(in.readAllBytes(), StandardCharsets.UTF_8);
+ }
+ }
+ };
+ }
+
+ class CompilerResult {}
+
+ class Success extends CompilerResult {
+
+ }
+
+ class Failure extends CompilerResult {
+ private final List> diagnotics;
+
+ Failure(List> diagnotics) {
+ this.diagnotics = diagnotics;
+ }
+
+ List> diagnotics() {
+ return diagnotics;
+ }
+ }
+
+ class HasDiagnostic extends TypeSafeMatcher> {
+ private final Diagnostic.Kind kind;
+ private final Matcher matcher;
+
+ HasDiagnostic(final Diagnostic.Kind kind, final Matcher matcher) {
+ this.kind = kind;
+ this.matcher = matcher;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("diagnostic with kind ").appendValue(kind).appendText(" ");
+
+ if (matcher != null) {
+ description.appendText(" and message ");
+ matcher.describeTo(description);
+ }
+ }
+
+ @Override
+ protected boolean matchesSafely(Diagnostic extends JavaFileObject> item) {
+ if (!kind.equals(item.getKind())) {
+ return false;
+ } else if (matcher != null) {
+ return matcher.matches(item.getMessage(Locale.ROOT));
+ } else {
+ return true;
+ }
+ }
+
+ public static HasDiagnostic matching(final Diagnostic.Kind kind, final Matcher matcher) {
+ return new HasDiagnostic(kind, matcher);
+ }
+
+ public static HasDiagnostic matching(final Diagnostic.Kind kind) {
+ return new HasDiagnostic(kind, null);
+ }
+ }
+}
diff --git a/libs/common/src/test/resources/org/acme/PublicApiAnnotated.java b/libs/common/src/test/resources/org/acme/PublicApiAnnotated.java
new file mode 100644
index 0000000000000..bc16fd996e69d
--- /dev/null
+++ b/libs/common/src/test/resources/org/acme/PublicApiAnnotated.java
@@ -0,0 +1,16 @@
+/*
+ * 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.acme;
+
+import org.opensearch.common.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiAnnotated {
+
+}
diff --git a/libs/common/src/test/resources/org/opensearch/bootstrap/test.policy b/libs/common/src/test/resources/org/opensearch/bootstrap/test.policy
new file mode 100644
index 0000000000000..e0a183b7eac88
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/bootstrap/test.policy
@@ -0,0 +1,16 @@
+/*
+ * 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.
+ */
+
+grant {
+ // allow to use JVM tooling (Java Compiler) in tests for annotation processing
+ permission java.io.FilePermission "${java.home}/lib/*", "read";
+ permission java.io.FilePermission "${java.home}/lib/modules/*", "read";
+ permission java.lang.RuntimePermission "accessSystemModules";
+ permission java.lang.RuntimePermission "accessDeclaredMembers";
+ permission java.lang.RuntimePermission "accessClassInPackage.*";
+};
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/DeprecatedApiMethodReturnSelf.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/DeprecatedApiMethodReturnSelf.java
new file mode 100644
index 0000000000000..7c5b6f6ea2f51
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/DeprecatedApiMethodReturnSelf.java
@@ -0,0 +1,18 @@
+/*
+ * 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.annotation.DeprecatedApi;
+
+@DeprecatedApi(since = "1.0.0")
+public class DeprecatedApiMethodReturnSelf {
+ public DeprecatedApiMethodReturnSelf method() {
+ return new DeprecatedApiMethodReturnSelf();
+ }
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/ExperimentalApiAnnotated.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/ExperimentalApiAnnotated.java
new file mode 100644
index 0000000000000..5be07e22c811f
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/ExperimentalApiAnnotated.java
@@ -0,0 +1,16 @@
+/*
+ * 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.annotation.ExperimentalApi;
+
+@ExperimentalApi
+public class ExperimentalApiAnnotated {
+
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/ExperimentalApiMethodReturnSelf.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/ExperimentalApiMethodReturnSelf.java
new file mode 100644
index 0000000000000..cde8f4f254faf
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/ExperimentalApiMethodReturnSelf.java
@@ -0,0 +1,18 @@
+/*
+ * 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.annotation.ExperimentalApi;
+
+@ExperimentalApi
+public class ExperimentalApiMethodReturnSelf {
+ public ExperimentalApiMethodReturnSelf method() {
+ return new ExperimentalApiMethodReturnSelf();
+ }
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/InternalApiAnnotated.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/InternalApiAnnotated.java
new file mode 100644
index 0000000000000..9996ba8b736aa
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/InternalApiAnnotated.java
@@ -0,0 +1,16 @@
+/*
+ * 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.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class InternalApiAnnotated {
+
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/NotAnnotated.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/NotAnnotated.java
new file mode 100644
index 0000000000000..ec16ce926ea86
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/NotAnnotated.java
@@ -0,0 +1,13 @@
+/*
+ * 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;
+
+public class NotAnnotated {
+
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/NotAnnotatedAnnotation.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/NotAnnotatedAnnotation.java
new file mode 100644
index 0000000000000..a3e9c4f576d92
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/NotAnnotatedAnnotation.java
@@ -0,0 +1,27 @@
+/*
+ * 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({
+ ElementType.TYPE,
+ ElementType.TYPE_PARAMETER,
+ ElementType.TYPE_USE,
+ ElementType.PACKAGE,
+ ElementType.METHOD,
+ ElementType.CONSTRUCTOR,
+ ElementType.PARAMETER,
+ ElementType.FIELD,
+ ElementType.ANNOTATION_TYPE,
+ ElementType.MODULE })
+public @interface NotAnnotatedAnnotation {
+
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/NotAnnotatedException.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/NotAnnotatedException.java
new file mode 100644
index 0000000000000..0aadaf8f9bf31
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/NotAnnotatedException.java
@@ -0,0 +1,13 @@
+/*
+ * 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;
+
+public class NotAnnotatedException extends Exception {
+ private static final long serialVersionUID = 1L;
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiAnnotated.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiAnnotated.java
new file mode 100644
index 0000000000000..b2a7f03cb2d31
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiAnnotated.java
@@ -0,0 +1,16 @@
+/*
+ * 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.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiAnnotated {
+
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiConstructorArgumentAnnotatedInternalApi.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiConstructorArgumentAnnotatedInternalApi.java
new file mode 100644
index 0000000000000..6bea2961a14e6
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiConstructorArgumentAnnotatedInternalApi.java
@@ -0,0 +1,20 @@
+/*
+ * 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.annotation.InternalApi;
+import org.opensearch.common.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiConstructorArgumentAnnotatedInternalApi {
+ /**
+ * The constructor arguments have relaxed semantics at the moment: those could be not annotated or be annotated as {@link InternalApi}
+ */
+ public PublicApiConstructorArgumentAnnotatedInternalApi(InternalApiAnnotated arg) {}
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiConstructorArgumentNotAnnotated.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiConstructorArgumentNotAnnotated.java
new file mode 100644
index 0000000000000..6c7481d9978cd
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiConstructorArgumentNotAnnotated.java
@@ -0,0 +1,20 @@
+/*
+ * 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.annotation.InternalApi;
+import org.opensearch.common.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiConstructorArgumentNotAnnotated {
+ /**
+ * The constructor arguments have relaxed semantics at the moment: those could be not annotated or be annotated as {@link InternalApi}
+ */
+ public PublicApiConstructorArgumentNotAnnotated(NotAnnotated arg) {}
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodArgumentAnnotatedPackagePrivate.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodArgumentAnnotatedPackagePrivate.java
new file mode 100644
index 0000000000000..5dae56a7cd7d3
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodArgumentAnnotatedPackagePrivate.java
@@ -0,0 +1,20 @@
+/*
+ * 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.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiMethodArgumentAnnotatedPackagePrivate {
+ public void method(AnnotatedPackagePrivate arg) {}
+}
+
+// The public API exposes this class through public method argument, it should be public
+@PublicApi(since = "1.0.0")
+class AnnotatedPackagePrivate {}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodArgumentNotAnnotated.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodArgumentNotAnnotated.java
new file mode 100644
index 0000000000000..ddfec939f79e8
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodArgumentNotAnnotated.java
@@ -0,0 +1,16 @@
+/*
+ * 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.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiMethodArgumentNotAnnotated {
+ public void method(NotAnnotated arg) {}
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodArgumentNotAnnotatedGenerics.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodArgumentNotAnnotatedGenerics.java
new file mode 100644
index 0000000000000..d32502831d299
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodArgumentNotAnnotatedGenerics.java
@@ -0,0 +1,18 @@
+/*
+ * 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.annotation.PublicApi;
+
+import java.util.Collection;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiMethodArgumentNotAnnotatedGenerics {
+ public void method(Collection super NotAnnotated> arg) {}
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodArgumentNotAnnotatedPackagePrivate.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodArgumentNotAnnotatedPackagePrivate.java
new file mode 100644
index 0000000000000..d4fb31b172ef2
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodArgumentNotAnnotatedPackagePrivate.java
@@ -0,0 +1,19 @@
+/*
+ * 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.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiMethodArgumentNotAnnotatedPackagePrivate {
+ public void method(NotAnnotatedPackagePrivate arg) {}
+}
+
+// The public API exposes this class through public method argument, it should be annotated and be public
+class NotAnnotatedPackagePrivate {}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodGenericsArgumentAnnotated.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodGenericsArgumentAnnotated.java
new file mode 100644
index 0000000000000..9715748cfa659
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodGenericsArgumentAnnotated.java
@@ -0,0 +1,16 @@
+/*
+ * 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.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiMethodGenericsArgumentAnnotated {
+ public void method(T arg) {}
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodGenericsArgumentNotAnnotated.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodGenericsArgumentNotAnnotated.java
new file mode 100644
index 0000000000000..f149c1f34b067
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodGenericsArgumentNotAnnotated.java
@@ -0,0 +1,16 @@
+/*
+ * 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.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiMethodGenericsArgumentNotAnnotated {
+ public void method(T arg) {}
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnAnnotatedArray.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnAnnotatedArray.java
new file mode 100644
index 0000000000000..39b7e146fe1e7
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnAnnotatedArray.java
@@ -0,0 +1,18 @@
+/*
+ * 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.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiMethodReturnAnnotatedArray {
+ public PublicApiAnnotated[] method() {
+ return new PublicApiAnnotated[0];
+ }
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnAnnotatedGenerics.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnAnnotatedGenerics.java
new file mode 100644
index 0000000000000..2171eccee2f31
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnAnnotatedGenerics.java
@@ -0,0 +1,23 @@
+/*
+ * 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.annotation.PublicApi;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.acme.PublicApiAnnotated;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiMethodReturnAnnotatedGenerics {
+ public Collection<@NotAnnotatedAnnotation PublicApiAnnotated> method() {
+ return Collections.emptyList();
+ }
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnNotAnnotated.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnNotAnnotated.java
new file mode 100644
index 0000000000000..725d06072d0ea
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnNotAnnotated.java
@@ -0,0 +1,18 @@
+/*
+ * 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.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiMethodReturnNotAnnotated {
+ public NotAnnotated method() {
+ return new NotAnnotated();
+ }
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnNotAnnotatedAnnotation.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnNotAnnotatedAnnotation.java
new file mode 100644
index 0000000000000..b684e36a53da1
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnNotAnnotatedAnnotation.java
@@ -0,0 +1,18 @@
+/*
+ * 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.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiMethodReturnNotAnnotatedAnnotation {
+ public @NotAnnotatedAnnotation PublicApiAnnotated method() {
+ return new PublicApiAnnotated();
+ }
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnNotAnnotatedArray.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnNotAnnotatedArray.java
new file mode 100644
index 0000000000000..e4c541dcea57f
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnNotAnnotatedArray.java
@@ -0,0 +1,18 @@
+/*
+ * 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.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiMethodReturnNotAnnotatedArray {
+ public NotAnnotated[] method() {
+ return new NotAnnotated[0];
+ }
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnNotAnnotatedBoundedGenerics.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnNotAnnotatedBoundedGenerics.java
new file mode 100644
index 0000000000000..0646faf152610
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnNotAnnotatedBoundedGenerics.java
@@ -0,0 +1,21 @@
+/*
+ * 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.annotation.PublicApi;
+
+import java.util.Collection;
+import java.util.Collections;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiMethodReturnNotAnnotatedBoundedGenerics {
+ public Collection extends NotAnnotated> method() {
+ return Collections.emptyList();
+ }
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnNotAnnotatedGenerics.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnNotAnnotatedGenerics.java
new file mode 100644
index 0000000000000..2227883c707d0
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnNotAnnotatedGenerics.java
@@ -0,0 +1,21 @@
+/*
+ * 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.annotation.PublicApi;
+
+import java.util.Collection;
+import java.util.Collections;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiMethodReturnNotAnnotatedGenerics {
+ public Collection method() {
+ return Collections.emptyList();
+ }
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnNotAnnotatedWildcardGenerics.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnNotAnnotatedWildcardGenerics.java
new file mode 100644
index 0000000000000..f2818ebb23c4a
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnNotAnnotatedWildcardGenerics.java
@@ -0,0 +1,21 @@
+/*
+ * 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.annotation.PublicApi;
+
+import java.util.Collection;
+import java.util.Collections;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiMethodReturnNotAnnotatedWildcardGenerics {
+ public Collection> method() {
+ return Collections.emptyList();
+ }
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnSelf.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnSelf.java
new file mode 100644
index 0000000000000..883471b23ae0f
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodReturnSelf.java
@@ -0,0 +1,18 @@
+/*
+ * 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.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiMethodReturnSelf {
+ public PublicApiMethodReturnSelf method() {
+ return new PublicApiMethodReturnSelf();
+ }
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodThrowsNotAnnotated.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodThrowsNotAnnotated.java
new file mode 100644
index 0000000000000..496b243276565
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiMethodThrowsNotAnnotated.java
@@ -0,0 +1,16 @@
+/*
+ * 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.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiMethodThrowsNotAnnotated {
+ public void method(PublicApiAnnotated arg) throws NotAnnotatedException {}
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiPackagePrivate.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiPackagePrivate.java
new file mode 100644
index 0000000000000..88c20e7f4c8f1
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiPackagePrivate.java
@@ -0,0 +1,16 @@
+/*
+ * 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.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+class PublicApiPackagePrivate {
+ void method() {}
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiWithExperimentalApiMethod.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiWithExperimentalApiMethod.java
new file mode 100644
index 0000000000000..faaaa1d9f4051
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiWithExperimentalApiMethod.java
@@ -0,0 +1,18 @@
+/*
+ * 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.annotation.ExperimentalApi;
+import org.opensearch.common.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiWithExperimentalApiMethod {
+ @ExperimentalApi
+ public void method() {}
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiWithInternalApiMethod.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiWithInternalApiMethod.java
new file mode 100644
index 0000000000000..5bfa3c9f3e008
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiWithInternalApiMethod.java
@@ -0,0 +1,19 @@
+/*
+ * 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.annotation.InternalApi;
+import org.opensearch.common.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiWithInternalApiMethod {
+ // The public API exposes internal API method, it should be public API
+ @InternalApi
+ public void method() {}
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiWithPackagePrivateMethod.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiWithPackagePrivateMethod.java
new file mode 100644
index 0000000000000..1345467423530
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiWithPackagePrivateMethod.java
@@ -0,0 +1,16 @@
+/*
+ * 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.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiWithPackagePrivateMethod {
+ void method(NotAnnotated arg) {}
+}
diff --git a/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiWithProtectedInterface.java b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiWithProtectedInterface.java
new file mode 100644
index 0000000000000..222ae01fd15e6
--- /dev/null
+++ b/libs/common/src/test/resources/org/opensearch/common/annotation/processor/PublicApiWithProtectedInterface.java
@@ -0,0 +1,22 @@
+/*
+ * 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.annotation.PublicApi;
+
+@PublicApi(since = "1.0.0")
+public class PublicApiWithProtectedInterface {
+ public void method(ProtectedInterface iface) {}
+
+ /**
+ * The type could expose protected inner types which are still considered to be a public API when used
+ */
+ @PublicApi(since = "1.0.0")
+ protected interface ProtectedInterface {}
+}
diff --git a/server/build.gradle b/server/build.gradle
index f8e31421c8a2e..0355a45a4a77b 100644
--- a/server/build.gradle
+++ b/server/build.gradle
@@ -147,6 +147,7 @@ dependencies {
api "org.apache.logging.log4j:log4j-jul:${versions.log4j}"
api "org.apache.logging.log4j:log4j-core:${versions.log4j}", optional
annotationProcessor "org.apache.logging.log4j:log4j-core:${versions.log4j}"
+ annotationProcessor project(':libs:opensearch-common')
// jna
api "net.java.dev.jna:jna:${versions.jna}"
@@ -178,7 +179,8 @@ tasks.withType(JavaCompile).configureEach {
}
compileJava {
- options.compilerArgs += ['-processor', 'org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor']
+ options.compilerArgs += ['-processor', ['org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor',
+ 'org.opensearch.common.annotation.processor.ApiAnnotationProcessor'].join(','), '-AcontinueOnFailingChecks']
}
tasks.named("internalClusterTest").configure {
diff --git a/server/src/main/java/org/opensearch/index/engine/Engine.java b/server/src/main/java/org/opensearch/index/engine/Engine.java
index 66e7b30ab0b4d..37f46245783ae 100644
--- a/server/src/main/java/org/opensearch/index/engine/Engine.java
+++ b/server/src/main/java/org/opensearch/index/engine/Engine.java
@@ -2178,8 +2178,9 @@ public long getMaxSeenAutoIdTimestamp() {
/**
* The runner for translog recovery
*
- * @opensearch.internal
+ * @opensearch.api
*/
+ @PublicApi(since = "1.0.0")
@FunctionalInterface
public interface TranslogRecoveryRunner {
int run(Engine engine, Translog.Snapshot snapshot) throws IOException;
diff --git a/server/src/main/java/org/opensearch/index/mapper/ParametrizedFieldMapper.java b/server/src/main/java/org/opensearch/index/mapper/ParametrizedFieldMapper.java
index ee0b50024ab38..f4723b6178137 100644
--- a/server/src/main/java/org/opensearch/index/mapper/ParametrizedFieldMapper.java
+++ b/server/src/main/java/org/opensearch/index/mapper/ParametrizedFieldMapper.java
@@ -36,7 +36,6 @@
import org.opensearch.Version;
import org.opensearch.common.Explicit;
import org.opensearch.common.TriFunction;
-import org.opensearch.common.annotation.InternalApi;
import org.opensearch.common.annotation.PublicApi;
import org.opensearch.common.logging.DeprecationLogger;
import org.opensearch.common.settings.Settings;
@@ -154,16 +153,20 @@ protected void doXContentBody(XContentBuilder builder, boolean includeDefaults,
/**
* Serializes a parameter
+ *
+ * @opensearch.api
*/
- @InternalApi
+ @PublicApi(since = "1.0.0")
protected interface Serializer {
void serialize(XContentBuilder builder, String name, T value) throws IOException;
}
/**
* Check on whether or not a parameter should be serialized
+ *
+ * @opensearch.api
*/
- @InternalApi
+ @PublicApi(since = "1.0.0")
protected interface SerializerCheck {
/**
* Check on whether or not a parameter should be serialized
diff --git a/server/src/main/java/org/opensearch/indices/recovery/RecoveryFailedException.java b/server/src/main/java/org/opensearch/indices/recovery/RecoveryFailedException.java
index 73916d0238419..1878d37c38e4d 100644
--- a/server/src/main/java/org/opensearch/indices/recovery/RecoveryFailedException.java
+++ b/server/src/main/java/org/opensearch/indices/recovery/RecoveryFailedException.java
@@ -34,6 +34,7 @@
import org.opensearch.cluster.node.DiscoveryNode;
import org.opensearch.common.Nullable;
+import org.opensearch.common.annotation.PublicApi;
import org.opensearch.core.common.io.stream.StreamInput;
import org.opensearch.core.index.shard.ShardId;
import org.opensearch.indices.replication.common.ReplicationFailedException;
@@ -43,8 +44,9 @@
/**
* Exception thrown if recovery fails
*
- * @opensearch.internal
+ * @opensearch.api
*/
+@PublicApi(since = "1.0.0")
public class RecoveryFailedException extends ReplicationFailedException {
public RecoveryFailedException(StartRecoveryRequest request, Throwable cause) {
diff --git a/server/src/main/java/org/opensearch/search/aggregations/pipeline/SiblingPipelineAggregator.java b/server/src/main/java/org/opensearch/search/aggregations/pipeline/SiblingPipelineAggregator.java
index cf8fa3504710b..427902143429e 100644
--- a/server/src/main/java/org/opensearch/search/aggregations/pipeline/SiblingPipelineAggregator.java
+++ b/server/src/main/java/org/opensearch/search/aggregations/pipeline/SiblingPipelineAggregator.java
@@ -32,6 +32,7 @@
package org.opensearch.search.aggregations.pipeline;
+import org.opensearch.common.annotation.PublicApi;
import org.opensearch.core.common.io.stream.StreamInput;
import org.opensearch.search.aggregations.Aggregations;
import org.opensearch.search.aggregations.InternalAggregation;
@@ -45,8 +46,9 @@
/**
* Aggregate all docs into a sibling bucket
*
- * @opensearch.internal
+ * @opensearch.api
*/
+@PublicApi(since = "1.0.0")
public abstract class SiblingPipelineAggregator extends PipelineAggregator {
protected SiblingPipelineAggregator(String name, String[] bucketsPaths, Map metadata) {
super(name, bucketsPaths, metadata);
diff --git a/test/framework/src/main/java/org/opensearch/bootstrap/BootstrapForTesting.java b/test/framework/src/main/java/org/opensearch/bootstrap/BootstrapForTesting.java
index 8f065de35aa8b..43881d0660e04 100644
--- a/test/framework/src/main/java/org/opensearch/bootstrap/BootstrapForTesting.java
+++ b/test/framework/src/main/java/org/opensearch/bootstrap/BootstrapForTesting.java
@@ -69,6 +69,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
@@ -161,12 +162,17 @@ public class BootstrapForTesting {
addClassCodebase(codebases, "opensearch-rest-client", "org.opensearch.client.RestClient");
}
final Policy testFramework = Security.readPolicy(Bootstrap.class.getResource("test-framework.policy"), codebases);
+ // Allow modules to define own test policy in ad-hoc fashion (if needed) that is not really applicable to other modules
+ final Optional testPolicy = Optional.ofNullable(Bootstrap.class.getResource("test.policy"))
+ .map(policy -> Security.readPolicy(policy, codebases));
final Policy opensearchPolicy = new OpenSearchPolicy(codebases, perms, getPluginPermissions(), true, new Permissions());
Policy.setPolicy(new Policy() {
@Override
public boolean implies(ProtectionDomain domain, Permission permission) {
// implements union
- return opensearchPolicy.implies(domain, permission) || testFramework.implies(domain, permission);
+ return opensearchPolicy.implies(domain, permission)
+ || testFramework.implies(domain, permission)
+ || testPolicy.map(policy -> policy.implies(domain, permission)).orElse(false /* no policy */);
}
});
// Create access control context for mocking