diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c8dab3649ef2a..fd45f96e6770a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,6 +53,7 @@ If you've thought of a way that OpenSearch could be better, we want to hear abou ### Documentation Changes If you would like to contribute to the documentation, please see [OpenSearch Docs repo](https://github.com/opensearch-project/documentation-website/blob/main/README.md). +To contribute javadocs, please see [this open issue on github](https://github.com/opensearch-project/OpenSearch/issues/221). ### Contributing Code diff --git a/build.gradle b/build.gradle index 62eb01df837f3..954c7adcb5d81 100644 --- a/build.gradle +++ b/build.gradle @@ -58,6 +58,7 @@ apply from: 'gradle/formatting.gradle' apply from: 'gradle/local-distribution.gradle' apply from: 'gradle/fips.gradle' apply from: 'gradle/run.gradle' +apply from: 'gradle/missing-javadoc.gradle' // common maven publishing configuration allprojects { diff --git a/buildSrc/reaper/src/main/java/org/opensearch/gradle/reaper/package-info.java b/buildSrc/reaper/src/main/java/org/opensearch/gradle/reaper/package-info.java new file mode 100644 index 0000000000000..2462fe8d39b02 --- /dev/null +++ b/buildSrc/reaper/src/main/java/org/opensearch/gradle/reaper/package-info.java @@ -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. + */ + +/** + * A standalone process that will reap external services after a build dies. + */ +package org.opensearch.gradle.reaper; diff --git a/client/rest/src/main/java/org/opensearch/client/HasAttributeNodeSelector.java b/client/rest/src/main/java/org/opensearch/client/HasAttributeNodeSelector.java index 3977cee34138a..a2b9bc758a36c 100644 --- a/client/rest/src/main/java/org/opensearch/client/HasAttributeNodeSelector.java +++ b/client/rest/src/main/java/org/opensearch/client/HasAttributeNodeSelector.java @@ -45,6 +45,13 @@ public final class HasAttributeNodeSelector implements NodeSelector { private final String key; private final String value; + /** + * Create a {link HasAttributeNodeSelector} instance using the provided + * attribute key value pair. + * + * @param key The attribute name. + * @param value The attribute value. + */ public HasAttributeNodeSelector(String key, String value) { this.key = key; this.value = value; diff --git a/client/rest/src/main/java/org/opensearch/client/HeapBufferedAsyncResponseConsumer.java b/client/rest/src/main/java/org/opensearch/client/HeapBufferedAsyncResponseConsumer.java index 47b7cc539516e..618f00b29e7f4 100644 --- a/client/rest/src/main/java/org/opensearch/client/HeapBufferedAsyncResponseConsumer.java +++ b/client/rest/src/main/java/org/opensearch/client/HeapBufferedAsyncResponseConsumer.java @@ -61,7 +61,10 @@ public class HeapBufferedAsyncResponseConsumer extends AbstractAsyncResponseCons private volatile SimpleInputBuffer buf; /** - * Creates a new instance of this consumer with the provided buffer limit + * Creates a new instance of this consumer with the provided buffer limit. + * + * @param bufferLimit the buffer limit. Must be greater than 0. + * @throws IllegalArgumentException if {@code bufferLimit} is less than or equal to 0. */ public HeapBufferedAsyncResponseConsumer(int bufferLimit) { if (bufferLimit <= 0) { diff --git a/client/rest/src/main/java/org/opensearch/client/HttpAsyncResponseConsumerFactory.java b/client/rest/src/main/java/org/opensearch/client/HttpAsyncResponseConsumerFactory.java index e30ee60257599..df575f5cbd21e 100644 --- a/client/rest/src/main/java/org/opensearch/client/HttpAsyncResponseConsumerFactory.java +++ b/client/rest/src/main/java/org/opensearch/client/HttpAsyncResponseConsumerFactory.java @@ -66,6 +66,11 @@ class HeapBufferedResponseConsumerFactory implements HttpAsyncResponseConsumerFa private final int bufferLimit; + /** + * Creates a {@link HeapBufferedResponseConsumerFactory} instance with the given buffer limit. + * + * @param bufferLimitBytes the buffer limit to be applied to this instance + */ public HeapBufferedResponseConsumerFactory(int bufferLimitBytes) { this.bufferLimit = bufferLimitBytes; } diff --git a/client/rest/src/main/java/org/opensearch/client/Node.java b/client/rest/src/main/java/org/opensearch/client/Node.java index 33c8bc4d9c489..b7aabd564ac6b 100644 --- a/client/rest/src/main/java/org/opensearch/client/Node.java +++ b/client/rest/src/main/java/org/opensearch/client/Node.java @@ -77,6 +77,13 @@ public class Node { * Create a {@linkplain Node} with metadata. All parameters except * {@code host} are nullable and implementations of {@link NodeSelector} * need to decide what to do in their absence. + * + * @param host primary host address + * @param boundHosts addresses on which the host is listening + * @param name name of the node + * @param version version of OpenSearch + * @param roles roles that the OpenSearch process has on the host + * @param attributes attributes declared on the node */ public Node(HttpHost host, Set boundHosts, String name, String version, Roles roles, Map> attributes) { @@ -93,6 +100,8 @@ public Node(HttpHost host, Set boundHosts, String name, String version /** * Create a {@linkplain Node} without any metadata. + * + * @param host primary host address */ public Node(HttpHost host) { this(host, null, null, null, null, null); @@ -192,6 +201,11 @@ public static final class Roles { private final Set roles; + /** + * Create a {@link Roles} instance of the given string set. + * + * @param roles set of role names. + */ public Roles(final Set roles) { this.roles = new TreeSet<>(roles); } diff --git a/client/rest/src/main/java/org/opensearch/client/NodeSelector.java b/client/rest/src/main/java/org/opensearch/client/NodeSelector.java index fb3493947caa0..0e11c113be31c 100644 --- a/client/rest/src/main/java/org/opensearch/client/NodeSelector.java +++ b/client/rest/src/main/java/org/opensearch/client/NodeSelector.java @@ -53,6 +53,8 @@ public interface NodeSelector { * {@link RestClient} will call this method with a list of "dead" nodes. *

* Implementers should not rely on the ordering of the nodes. + * + * @param nodes the {@link Node}s targeted for the sending requests */ void select(Iterable nodes); /* diff --git a/client/rest/src/main/java/org/opensearch/client/PreferHasAttributeNodeSelector.java b/client/rest/src/main/java/org/opensearch/client/PreferHasAttributeNodeSelector.java index 382a42c552972..1a8145e8d3970 100644 --- a/client/rest/src/main/java/org/opensearch/client/PreferHasAttributeNodeSelector.java +++ b/client/rest/src/main/java/org/opensearch/client/PreferHasAttributeNodeSelector.java @@ -47,6 +47,12 @@ public final class PreferHasAttributeNodeSelector implements NodeSelector { private final String key; private final String value; + /** + * Creates a {@link PreferHasAttributeNodeSelector} instance with the given key value pair. + * + * @param key attribute key + * @param value attribute value + */ public PreferHasAttributeNodeSelector(String key, String value) { this.key = key; this.value = value; diff --git a/client/rest/src/main/java/org/opensearch/client/Request.java b/client/rest/src/main/java/org/opensearch/client/Request.java index c56328587152d..8116265ea9b21 100644 --- a/client/rest/src/main/java/org/opensearch/client/Request.java +++ b/client/rest/src/main/java/org/opensearch/client/Request.java @@ -94,6 +94,12 @@ public void addParameter(String name, String value) { } } + /** + * Add query parameters using the provided map of key value pairs. + * + * @param paramSource a map of key value pairs where the key is the url parameter. + * @throws IllegalArgumentException if a parameter with that name has already been set. + */ public void addParameters(Map paramSource){ paramSource.forEach(this::addParameter); } @@ -110,6 +116,8 @@ public Map getParameters() { /** * Set the body of the request. If not set or set to {@code null} then no * body is sent with the request. + * + * @param entity the {@link HttpEntity} to be set as the body of the request. */ public void setEntity(HttpEntity entity) { this.entity = entity; @@ -121,6 +129,8 @@ public void setEntity(HttpEntity entity) { * {@code Content-Type} will be sent as {@code application/json}. * If you need a different content type then use * {@link #setEntity(HttpEntity)}. + * + * @param entity JSON string to be set as the entity body of the request. */ public void setJsonEntity(String entity) { setEntity(entity == null ? null : new NStringEntity(entity, ContentType.APPLICATION_JSON)); @@ -137,6 +147,9 @@ public HttpEntity getEntity() { /** * Set the portion of an HTTP request to OpenSearch that can be * manipulated without changing OpenSearch's behavior. + * + * @param options the options to be set. + * @throws NullPointerException if {@code options} is null. */ public void setOptions(RequestOptions options) { Objects.requireNonNull(options, "options cannot be null"); @@ -146,6 +159,9 @@ public void setOptions(RequestOptions options) { /** * Set the portion of an HTTP request to OpenSearch that can be * manipulated without changing OpenSearch's behavior. + * + * @param options the options to be set. + * @throws NullPointerException if {@code options} is null. */ public void setOptions(RequestOptions.Builder options) { Objects.requireNonNull(options, "options cannot be null"); diff --git a/client/rest/src/main/java/org/opensearch/client/RequestOptions.java b/client/rest/src/main/java/org/opensearch/client/RequestOptions.java index 5112150f4d560..0f351b06140a1 100644 --- a/client/rest/src/main/java/org/opensearch/client/RequestOptions.java +++ b/client/rest/src/main/java/org/opensearch/client/RequestOptions.java @@ -196,6 +196,10 @@ public RequestOptions build() { /** * Add the provided header to the request. + * + * @param name the header name + * @param value the header value + * @throws NullPointerException if {@code name} or {@code value} is null. */ public Builder addHeader(String name, String value) { Objects.requireNonNull(name, "header name cannot be null"); @@ -209,6 +213,9 @@ public Builder addHeader(String name, String value) { * {@link HttpAsyncResponseConsumer} callback per retry. Controls how the * response body gets streamed from a non-blocking HTTP connection on the * client side. + * + * @param httpAsyncResponseConsumerFactory factory for creating {@link HttpAsyncResponseConsumer}. + * @throws NullPointerException if {@code httpAsyncResponseConsumerFactory} is null. */ public void setHttpAsyncResponseConsumerFactory(HttpAsyncResponseConsumerFactory httpAsyncResponseConsumerFactory) { this.httpAsyncResponseConsumerFactory = @@ -231,6 +238,8 @@ public void setHttpAsyncResponseConsumerFactory(HttpAsyncResponseConsumerFactory * {@linkplain WarningsHandler} to permit only certain warnings or to * fail the request if the warnings returned don't * exactly match some set. + * + * @param warningsHandler the {@link WarningsHandler} to be used */ public void setWarningsHandler(WarningsHandler warningsHandler) { this.warningsHandler = warningsHandler; diff --git a/client/rest/src/main/java/org/opensearch/client/Response.java b/client/rest/src/main/java/org/opensearch/client/Response.java index e2dae6189b642..6cc0aad9c4a6a 100644 --- a/client/rest/src/main/java/org/opensearch/client/Response.java +++ b/client/rest/src/main/java/org/opensearch/client/Response.java @@ -96,6 +96,8 @@ public Header[] getHeaders() { * Returns the value of the first header with a specified name of this message. * If there is more than one matching header in the message the first element is returned. * If there is no matching header in the message null is returned. + * + * @param name header name */ public String getHeader(String name) { Header header = response.getFirstHeader(name); diff --git a/client/rest/src/main/java/org/opensearch/client/ResponseException.java b/client/rest/src/main/java/org/opensearch/client/ResponseException.java index 8e0ba6144426c..5892304241f0e 100644 --- a/client/rest/src/main/java/org/opensearch/client/ResponseException.java +++ b/client/rest/src/main/java/org/opensearch/client/ResponseException.java @@ -47,6 +47,11 @@ public final class ResponseException extends IOException { private final Response response; + /** + * Creates a ResponseException containing the given {@code Response}. + * + * @param response The error response. + */ public ResponseException(Response response) throws IOException { super(buildMessage(response)); this.response = response; diff --git a/client/rest/src/main/java/org/opensearch/client/ResponseListener.java b/client/rest/src/main/java/org/opensearch/client/ResponseListener.java index ad10deb5e680c..95ece150569e5 100644 --- a/client/rest/src/main/java/org/opensearch/client/ResponseListener.java +++ b/client/rest/src/main/java/org/opensearch/client/ResponseListener.java @@ -44,7 +44,9 @@ public interface ResponseListener { /** - * Method invoked if the request yielded a successful response + * Method invoked if the request yielded a successful response. + * + * @param response success response */ void onSuccess(Response response); @@ -52,6 +54,8 @@ public interface ResponseListener { * Method invoked if the request failed. There are two main categories of failures: connection failures (usually * {@link java.io.IOException}s, or responses that were treated as errors based on their error response code * ({@link ResponseException}s). + * + * @param exception the failure exception */ void onFailure(Exception exception); } diff --git a/client/rest/src/main/java/org/opensearch/client/RestClient.java b/client/rest/src/main/java/org/opensearch/client/RestClient.java index d86ccd606ec29..9c254f697f0ec 100644 --- a/client/rest/src/main/java/org/opensearch/client/RestClient.java +++ b/client/rest/src/main/java/org/opensearch/client/RestClient.java @@ -195,6 +195,8 @@ public static RestClientBuilder builder(String cloudId) { *

* Prefer this to {@link #builder(HttpHost...)} if you have metadata up front about the nodes. * If you don't either one is fine. + * + * @param nodes The nodes that the client will send requests to. */ public static RestClientBuilder builder(Node... nodes) { return new RestClientBuilder(nodes == null ? null : Arrays.asList(nodes)); @@ -207,6 +209,8 @@ public static RestClientBuilder builder(Node... nodes) { * You can use this if you do not have metadata up front about the nodes. If you do, prefer * {@link #builder(Node...)}. * @see Node#Node(HttpHost) + * + * @param hosts The hosts that the client will send requests to. */ public static RestClientBuilder builder(HttpHost... hosts) { if (hosts == null || hosts.length == 0) { @@ -218,6 +222,8 @@ public static RestClientBuilder builder(HttpHost... hosts) { /** * Replaces the nodes with which the client communicates. + * + * @param nodes the new nodes to communicate with. */ public synchronized void setNodes(Collection nodes) { if (nodes == null || nodes.isEmpty()) { @@ -672,7 +678,14 @@ void trackFailure(Exception exception) { */ public static class FailureListener { /** - * Notifies that the node provided as argument has just failed + * Create a {@link FailureListener} instance. + */ + public FailureListener() {} + + /** + * Notifies that the node provided as argument has just failed. + * + * @param node The node which has failed. */ public void onFailure(Node node) {} } @@ -907,6 +920,11 @@ private static Exception extractAndWrapCause(Exception exception) { */ public static class ContentCompressingEntity extends GzipCompressingEntity { + /** + * Creates a {@link ContentCompressingEntity} instance with the provided HTTP entity. + * + * @param entity the HTTP entity. + */ public ContentCompressingEntity(HttpEntity entity) { super(entity); } diff --git a/client/rest/src/main/java/org/opensearch/client/RestClientBuilder.java b/client/rest/src/main/java/org/opensearch/client/RestClientBuilder.java index 7e3c01cae4e2c..cd6d9b9b75f1d 100644 --- a/client/rest/src/main/java/org/opensearch/client/RestClientBuilder.java +++ b/client/rest/src/main/java/org/opensearch/client/RestClientBuilder.java @@ -53,9 +53,24 @@ * {@link org.apache.http.nio.client.HttpAsyncClient} in case additional customization is needed. */ public final class RestClientBuilder { + /** + * The default connection timout in milliseconds. + */ public static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 1000; + + /** + * The default socket timeout in milliseconds. + */ public static final int DEFAULT_SOCKET_TIMEOUT_MILLIS = 30000; + + /** + * The default maximum of connections per route. + */ public static final int DEFAULT_MAX_CONN_PER_ROUTE = 10; + + /** + * The default maximum total connections. + */ public static final int DEFAULT_MAX_CONN_TOTAL = 30; private static final Header[] EMPTY_HEADERS = new Header[0]; @@ -92,6 +107,7 @@ public final class RestClientBuilder { *

* Request-time headers will always overwrite any default headers. * + * @param defaultHeaders array of default header * @throws NullPointerException if {@code defaultHeaders} or any header is {@code null}. */ public RestClientBuilder setDefaultHeaders(Header[] defaultHeaders) { @@ -106,6 +122,7 @@ public RestClientBuilder setDefaultHeaders(Header[] defaultHeaders) { /** * Sets the {@link RestClient.FailureListener} to be notified for each request failure * + * @param failureListener the {@link RestClient.FailureListener} for each failure * @throws NullPointerException if {@code failureListener} is {@code null}. */ public RestClientBuilder setFailureListener(RestClient.FailureListener failureListener) { @@ -117,6 +134,7 @@ public RestClientBuilder setFailureListener(RestClient.FailureListener failureLi /** * Sets the {@link HttpClientConfigCallback} to be used to customize http client configuration * + * @param httpClientConfigCallback the {@link HttpClientConfigCallback} to be used * @throws NullPointerException if {@code httpClientConfigCallback} is {@code null}. */ public RestClientBuilder setHttpClientConfigCallback(HttpClientConfigCallback httpClientConfigCallback) { @@ -128,6 +146,7 @@ public RestClientBuilder setHttpClientConfigCallback(HttpClientConfigCallback ht /** * Sets the {@link RequestConfigCallback} to be used to customize http client configuration * + * @param requestConfigCallback the {@link RequestConfigCallback} to be used * @throws NullPointerException if {@code requestConfigCallback} is {@code null}. */ public RestClientBuilder setRequestConfigCallback(RequestConfigCallback requestConfigCallback) { @@ -145,6 +164,7 @@ public RestClientBuilder setRequestConfigCallback(RequestConfigCallback requestC * OpenSearch is behind a proxy that provides a base path or a proxy that requires all paths to start with '/'; * it is not intended for other purposes and it should not be supplied in other scenarios. * + * @param pathPrefix the path prefix for every request. * @throws NullPointerException if {@code pathPrefix} is {@code null}. * @throws IllegalArgumentException if {@code pathPrefix} is empty, or ends with more than one '/'. */ @@ -153,6 +173,14 @@ public RestClientBuilder setPathPrefix(String pathPrefix) { return this; } + /** + * Cleans up the given path prefix to ensure that looks like "/base/path". + * + * @param pathPrefix the path prefix to be cleaned up. + * @return the cleaned up path prefix. + * @throws NullPointerException if {@code pathPrefix} is {@code null}. + * @throws IllegalArgumentException if {@code pathPrefix} is empty, or ends with more than one '/'. + */ public static String cleanPathPrefix(String pathPrefix) { Objects.requireNonNull(pathPrefix, "pathPrefix must not be null"); @@ -178,6 +206,8 @@ public static String cleanPathPrefix(String pathPrefix) { /** * Sets the {@link NodeSelector} to be used for all requests. + * + * @param nodeSelector the {@link NodeSelector} to be used * @throws NullPointerException if the provided nodeSelector is null */ public RestClientBuilder setNodeSelector(NodeSelector nodeSelector) { @@ -189,6 +219,8 @@ public RestClientBuilder setNodeSelector(NodeSelector nodeSelector) { /** * Whether the REST client should return any response containing at least * one warning header as a failure. + * + * @param strictDeprecationMode flag for enabling strict deprecation mode */ public RestClientBuilder setStrictDeprecationMode(boolean strictDeprecationMode) { this.strictDeprecationMode = strictDeprecationMode; @@ -198,6 +230,8 @@ public RestClientBuilder setStrictDeprecationMode(boolean strictDeprecationMode) /** * Whether the REST client should compress requests using gzip content encoding and add the "Accept-Encoding: gzip" * header to receive compressed responses. + * + * @param compressionEnabled flag for enabling compression */ public RestClientBuilder setCompressionEnabled(boolean compressionEnabled) { this.compressionEnabled = compressionEnabled; @@ -254,6 +288,8 @@ public interface RequestConfigCallback { * Allows to customize the {@link RequestConfig} that will be used with each request. * It is common to customize the different timeout values through this method without losing any other useful default * value that the {@link RestClientBuilder} internally sets. + * + * @param requestConfigBuilder the {@link RestClientBuilder} for customizing the request configuration. */ RequestConfig.Builder customizeRequestConfig(RequestConfig.Builder requestConfigBuilder); } @@ -269,6 +305,8 @@ public interface HttpClientConfigCallback { * Commonly used to customize the default {@link org.apache.http.client.CredentialsProvider} for authentication * or the {@link SchemeIOSessionStrategy} for communication through ssl without losing any other useful default * value that the {@link RestClientBuilder} internally sets, like connection pooling. + * + * @param httpClientBuilder the {@link HttpClientBuilder} for customizing the client instance. */ HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder); } diff --git a/client/rest/src/main/java/org/opensearch/client/WarningFailureException.java b/client/rest/src/main/java/org/opensearch/client/WarningFailureException.java index 910d342580624..5415126d53aba 100644 --- a/client/rest/src/main/java/org/opensearch/client/WarningFailureException.java +++ b/client/rest/src/main/java/org/opensearch/client/WarningFailureException.java @@ -47,6 +47,12 @@ public final class WarningFailureException extends RuntimeException { private final Response response; + /** + * Creates a {@link WarningFailureException} instance. + * + * @param response the response that contains warnings. + * @throws IOException if there is a problem building the exception message. + */ public WarningFailureException(Response response) throws IOException { super(buildMessage(response)); this.response = response; @@ -56,6 +62,8 @@ public WarningFailureException(Response response) throws IOException { * Wrap a {@linkplain WarningFailureException} with another one with the current * stack trace. This is used during synchronous calls so that the caller * ends up in the stack trace of the exception thrown. + * + * @param e the exception to be wrapped. */ WarningFailureException(WarningFailureException e) { super(e.getMessage(), e); diff --git a/client/rest/src/main/java/org/opensearch/client/WarningsHandler.java b/client/rest/src/main/java/org/opensearch/client/WarningsHandler.java index d60b1aef25f65..10f00b5e5e734 100644 --- a/client/rest/src/main/java/org/opensearch/client/WarningsHandler.java +++ b/client/rest/src/main/java/org/opensearch/client/WarningsHandler.java @@ -39,8 +39,18 @@ * request. */ public interface WarningsHandler { + + /** + * Determines whether the given list of warnings should fail the request. + * + * @param warnings a list of warnings. + * @return boolean indicating if the request should fail. + */ boolean warningsShouldFailRequest(List warnings); + /** + * The permissive warnings handler. Warnings will not fail the request. + */ WarningsHandler PERMISSIVE = new WarningsHandler() { @Override public boolean warningsShouldFailRequest(List warnings) { @@ -52,6 +62,10 @@ public String toString() { return "permissive"; } }; + + /** + * The strict warnings handler. Warnings will fail the request. + */ WarningsHandler STRICT = new WarningsHandler() { @Override public boolean warningsShouldFailRequest(List warnings) { diff --git a/client/rest/src/main/java/org/opensearch/client/package-info.java b/client/rest/src/main/java/org/opensearch/client/package-info.java new file mode 100644 index 0000000000000..c323c022d0094 --- /dev/null +++ b/client/rest/src/main/java/org/opensearch/client/package-info.java @@ -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. + */ + +/** + * REST client that connects to an OpenSearch cluster. + */ +package org.opensearch.client; diff --git a/distribution/tools/launchers/build.gradle b/distribution/tools/launchers/build.gradle index 7ebe5c7e64416..52100296ac7e6 100644 --- a/distribution/tools/launchers/build.gradle +++ b/distribution/tools/launchers/build.gradle @@ -54,5 +54,6 @@ testingConventions { } javadoc.enabled = false +missingJavadoc.enabled = false loggerUsageCheck.enabled = false jarHell.enabled = false diff --git a/doc-tools/build.gradle b/doc-tools/build.gradle new file mode 100644 index 0000000000000..98b2149cb59a9 --- /dev/null +++ b/doc-tools/build.gradle @@ -0,0 +1,11 @@ +plugins { + id 'java' +} + +group 'org.opensearch' +version '1.0.0-SNAPSHOT' + +repositories { + mavenCentral() +} + diff --git a/doc-tools/missing-doclet/build.gradle b/doc-tools/missing-doclet/build.gradle new file mode 100644 index 0000000000000..e16900afce876 --- /dev/null +++ b/doc-tools/missing-doclet/build.gradle @@ -0,0 +1,15 @@ +plugins { + id 'java-library' +} + +group 'org.opensearch' +version '1.0.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +tasks.withType(JavaCompile) { + options.compilerArgs += ["--release", targetCompatibility.toString()] + options.encoding = "UTF-8" +} diff --git a/doc-tools/missing-doclet/src/main/java/org/opensearch/missingdoclet/MissingDoclet.java b/doc-tools/missing-doclet/src/main/java/org/opensearch/missingdoclet/MissingDoclet.java new file mode 100644 index 0000000000000..021fc04a42e86 --- /dev/null +++ b/doc-tools/missing-doclet/src/main/java/org/opensearch/missingdoclet/MissingDoclet.java @@ -0,0 +1,432 @@ +/* + * 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.missingdoclet; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.ModuleElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.Elements; +import javax.tools.Diagnostic; + +import com.sun.source.doctree.DocCommentTree; +import com.sun.source.doctree.ParamTree; +import com.sun.source.util.DocTrees; + +import jdk.javadoc.doclet.Doclet; +import jdk.javadoc.doclet.DocletEnvironment; +import jdk.javadoc.doclet.Reporter; +import jdk.javadoc.doclet.StandardDoclet; + +/** + * Checks for missing javadocs, where missing also means "only whitespace" or "license header". + * Has option --missing-level (package, class, method, parameter) so that we can improve over time. + * Has option --missing-ignore to ignore individual elements (such as split packages). + * It isn't recursive, just ignores exactly the elements you tell it. + * Has option --missing-method to apply "method" level to selected packages (fix one at a time). + * Matches package names exactly: so you'll need to list subpackages separately. + * + * Note: This by default ignores javadoc validation on overridden methods. + */ +// Original version of this class is ported from MissingDoclet code in Lucene, +// which is under the Apache Software Foundation under Apache 2.0 license +// See - https://github.com/apache/lucene-solr/tree/master/dev-tools/missing-doclet +public class MissingDoclet extends StandardDoclet { + // checks that modules and packages have documentation + private static final int PACKAGE = 0; + // checks that classes, interfaces, enums, and annotation types have documentation + private static final int CLASS = 1; + // checks that methods, constructors, fields, and enumerated constants have documentation + private static final int METHOD = 2; + // checks that @param tags are present for any method/constructor parameters + private static final int PARAMETER = 3; + int level = PARAMETER; + Reporter reporter; + DocletEnvironment docEnv; + DocTrees docTrees; + Elements elementUtils; + Set ignored = Collections.emptySet(); + Set methodPackages = Collections.emptySet(); + + @Override + public Set getSupportedOptions() { + Set options = new HashSet<>(); + options.addAll(super.getSupportedOptions()); + options.add(new Doclet.Option() { + @Override + public int getArgumentCount() { + return 1; + } + + @Override + public String getDescription() { + return "level to enforce for missing javadocs: [package, class, method, parameter]"; + } + + @Override + public Kind getKind() { + return Option.Kind.STANDARD; + } + + @Override + public List getNames() { + return Collections.singletonList("--missing-level"); + } + + @Override + public String getParameters() { + return "level"; + } + + @Override + public boolean process(String option, List arguments) { + switch (arguments.get(0)) { + case "package": + level = PACKAGE; + return true; + case "class": + level = CLASS; + return true; + case "method": + level = METHOD; + return true; + case "parameter": + level = PARAMETER; + return true; + default: + return false; + } + } + }); + options.add(new Doclet.Option() { + @Override + public int getArgumentCount() { + return 1; + } + + @Override + public String getDescription() { + return "comma separated list of element names to ignore (e.g. as a workaround for split packages)"; + } + + @Override + public Kind getKind() { + return Option.Kind.STANDARD; + } + + @Override + public List getNames() { + return Collections.singletonList("--missing-ignore"); + } + + @Override + public String getParameters() { + return "ignoredNames"; + } + + @Override + public boolean process(String option, List arguments) { + ignored = new HashSet<>(Arrays.asList(arguments.get(0).split(","))); + return true; + } + }); + options.add(new Doclet.Option() { + @Override + public int getArgumentCount() { + return 1; + } + + @Override + public String getDescription() { + return "comma separated list of packages to check at 'method' level"; + } + + @Override + public Kind getKind() { + return Option.Kind.STANDARD; + } + + @Override + public List getNames() { + return Collections.singletonList("--missing-method"); + } + + @Override + public String getParameters() { + return "packages"; + } + + @Override + public boolean process(String option, List arguments) { + methodPackages = new HashSet<>(Arrays.asList(arguments.get(0).split(","))); + return true; + } + }); + return options; + } + + @Override + public void init(Locale locale, Reporter reporter) { + this.reporter = reporter; + super.init(locale, reporter); + } + + @Override + public boolean run(DocletEnvironment docEnv) { + this.docEnv = docEnv; + this.docTrees = docEnv.getDocTrees(); + this.elementUtils = docEnv.getElementUtils(); + for (var element : docEnv.getIncludedElements()) { + check(element); + } + return true; + } + + /** + * Returns effective check level for this element + */ + private int level(Element element) { + String pkg = elementUtils.getPackageOf(element).getQualifiedName().toString(); + if (methodPackages.contains(pkg)) { + return METHOD; + } else { + return level; + } + } + + /** + * Check an individual element. + * This checks packages and types from the doctrees. + * It will recursively check methods/fields from encountered types when the level is "method" + */ + private void check(Element element) { + switch(element.getKind()) { + case MODULE: + // don't check the unnamed module, it won't have javadocs + if (!((ModuleElement)element).isUnnamed()) { + checkComment(element); + } + break; + case PACKAGE: + checkComment(element); + break; + // class-like elements, check them, then recursively check their children (fields and methods) + case CLASS: + case INTERFACE: + case ENUM: + case ANNOTATION_TYPE: + if (level(element) >= CLASS) { + checkComment(element); + for (var subElement : element.getEnclosedElements()) { + // don't recurse into enclosed types, otherwise we'll double-check since they are already in the included docTree + if (subElement.getKind() == ElementKind.METHOD || + subElement.getKind() == ElementKind.CONSTRUCTOR || + subElement.getKind() == ElementKind.FIELD || + subElement.getKind() == ElementKind.ENUM_CONSTANT) { + check(subElement); + } + } + } + break; + // method-like elements, check them if we are configured to do so + case METHOD: + case CONSTRUCTOR: + case FIELD: + case ENUM_CONSTANT: + if (level(element) >= METHOD && !isSyntheticEnumMethod(element)) { + checkComment(element); + } + break; + default: + error(element, "I don't know how to analyze " + element.getKind() + " yet."); + } + } + + /** + * Return true if the method is synthetic enum method (values/valueOf). + * According to the doctree documentation, the "included" set never includes synthetic elements. + * UweSays: It should not happen but it happens! + */ + private boolean isSyntheticEnumMethod(Element element) { + String simpleName = element.getSimpleName().toString(); + if (simpleName.equals("values") || simpleName.equals("valueOf")) { + if (element.getEnclosingElement().getKind() == ElementKind.ENUM) { + return true; + } + } + return false; + } + + /** + * Checks that an element doesn't have missing javadocs. + * In addition to truly "missing", check that comments aren't solely whitespace (generated by some IDEs) + */ + private void checkComment(Element element) { + // sanity check that the element is really "included", because we do some recursion into types + if (!docEnv.isIncluded(element)) { + return; + } + // check that this element isn't on our ignore list. This is only used as a workaround for "split packages". + // ignoring a package isn't recursive (on purpose), we still check all the classes, etc. inside it. + // we just need to cope with the fact package-info.java isn't there because it is split across multiple jars. + if (ignored.contains(element.toString())) { + return; + } + var tree = docTrees.getDocCommentTree(element); + if (tree == null || tree.getFirstSentence().isEmpty()) { + // Check for methods that override other stuff and perhaps inherit their Javadocs. + if (hasInheritedJavadocs(element)) { + return; + } else { + error(element, "javadocs are missing"); + } + } else { + var normalized = tree.getFirstSentence().get(0).toString() + .replace('\u00A0', ' ') + .trim() + .toLowerCase(Locale.ROOT); + if (normalized.isEmpty()) { + error(element, "blank javadoc comment"); + } + } + if (level >= PARAMETER) { + checkParameters(element, tree); + } + } + + private boolean hasInheritedJavadocs(Element element) { + boolean hasOverrides = element.getAnnotationMirrors().stream() + .anyMatch(ann -> ann.getAnnotationType().toString().equals(Override.class.getName())); + + if (hasOverrides) { + // If an element has explicit @Overrides annotation, assume it does + // have inherited javadocs somewhere. + reporter.print(Diagnostic.Kind.NOTE, element, "javadoc empty but @Override declared, skipping."); + return true; + } + + // Check for methods up the types tree. + if (element instanceof ExecutableElement) { + ExecutableElement thisMethod = (ExecutableElement) element; + Iterable superTypes = + () -> superTypeForInheritDoc(thisMethod.getEnclosingElement()).iterator(); + + for (Element sup : superTypes) { + for (ExecutableElement supMethod : ElementFilter.methodsIn(sup.getEnclosedElements())) { + TypeElement clazz = (TypeElement) thisMethod.getEnclosingElement(); + if (elementUtils.overrides(thisMethod, supMethod, clazz)) { + // We could check supMethod for non-empty javadoc here. Don't know if this makes + // sense though as all methods will be verified in the end so it'd fail on the + // top of the hierarchy (if empty) anyway. + reporter.print(Diagnostic.Kind.NOTE, element, "javadoc empty but method overrides another, skipping."); + return true; + } + } + } + } + + return false; + } + + + /* Find types from which methods in type may inherit javadoc, in the proper order.*/ + private Stream superTypeForInheritDoc(Element type) { + TypeElement clazz = (TypeElement) type; + List interfaces = clazz.getInterfaces() + .stream() + .filter(tm -> tm.getKind() == TypeKind.DECLARED) + .map(tm -> ((DeclaredType) tm).asElement()) + .collect(Collectors.toList()); + + Stream result = interfaces.stream(); + result = Stream.concat(result, interfaces.stream().flatMap(this::superTypeForInheritDoc)); + + if (clazz.getSuperclass().getKind() == TypeKind.DECLARED) { + Element superClass = ((DeclaredType) clazz.getSuperclass()).asElement(); + result = Stream.concat(result, Stream.of(superClass)); + result = Stream.concat(result, superTypeForInheritDoc(superClass)); + } + + return result; + } + + /** Checks there is a corresponding "param" tag for each method parameter */ + private void checkParameters(Element element, DocCommentTree tree) { + if (element instanceof ExecutableElement) { + // record each @param that we see + Set seenParameters = new HashSet<>(); + if (tree != null) { + for (var tag : tree.getBlockTags()) { + if (tag instanceof ParamTree) { + var name = ((ParamTree)tag).getName().getName().toString(); + seenParameters.add(name); + } + } + } + // now compare the method's formal parameter list against it + for (var param : ((ExecutableElement)element).getParameters()) { + var name = param.getSimpleName().toString(); + if (!seenParameters.contains(name)) { + error(element, "missing javadoc @param for parameter '" + name + "'"); + } + } + } + } + + /** logs a new error for the particular element */ + private void error(Element element, String message) { + var fullMessage = new StringBuilder(); + switch (element.getKind()) { + case MODULE: + case PACKAGE: + // for modules/packages, we don't have filename + line number, fully qualify + fullMessage.append(element.toString()); + break; + case METHOD: + case CONSTRUCTOR: + case FIELD: + case ENUM_CONSTANT: + // for method-like elements, include the enclosing type to make it easier + fullMessage.append(element.getEnclosingElement().getSimpleName()); + fullMessage.append("."); + fullMessage.append(element.getSimpleName()); + break; + default: + // for anything else, use a simple name + fullMessage.append(element.getSimpleName()); + break; + } + + fullMessage.append(" ("); + fullMessage.append(element.getKind().toString().toLowerCase(Locale.ROOT)); + fullMessage.append("): "); + fullMessage.append(message); + + if (Runtime.version().feature() == 11 && element.getKind() == ElementKind.PACKAGE) { + // Avoid JDK 11 bug: + // https://bugs.openjdk.java.net/browse/JDK-8224082 + reporter.print(Diagnostic.Kind.ERROR, fullMessage.toString()); + } else { + reporter.print(Diagnostic.Kind.ERROR, element, fullMessage.toString()); + } + } +} diff --git a/gradle/missing-javadoc.gradle b/gradle/missing-javadoc.gradle new file mode 100644 index 0000000000000..1ed04a9d94b46 --- /dev/null +++ b/gradle/missing-javadoc.gradle @@ -0,0 +1,242 @@ +/* + * 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. + */ + +import javax.annotation.Nullable + +/** + * Checks for missing javadocs. + */ +// Original version of this file is ported from the render-javadoc code in Lucene, +// original code under the Apache Software Foundation under Apache 2.0 license +// See - https://github.com/apache/lucene-solr/tree/master/gradle/documentation/render-javadoc +allprojects { + ext { + scriptResources = { buildscript -> + return file(buildscript.sourceFile.absolutePath.replaceAll('.gradle$', "")) + } + } +} + +def resources = scriptResources(buildscript) + +allprojects { + plugins.withType(JavaPlugin) { + configurations { + missingdoclet + } + + dependencies { + missingdoclet "org.opensearch:missing-doclet" + } + + ext { + relativeDocPath = project.path.replaceFirst(/:\w+:/, "").replace(':', '/') + docroot = file("${buildDir}/site") + } + + // TODO: Add missingJavadoc checks to precommit plugin + // See https://github.com/opensearch-project/OpenSearch/issues/221 + // Currently the missingJavadoc task fails due to missing documentation + // across multiple modules. Once javadocs are added, we can + // add this task to precommit plugin. + tasks.withType(MissingJavadocTask).configureEach { + enabled = true + title = "${project.rootProject.name} ${project.name} API" + + // Set up custom doclet. + dependsOn configurations.missingdoclet + docletpath = configurations.missingdoclet + } + + + tasks.register('missingJavadoc', MissingJavadocTask) { + description "This task validates and generates Javadoc API documentation for the main source code." + group "documentation" + + taskResources = resources + dependsOn sourceSets.main.compileClasspath + classpath = sourceSets.main.compileClasspath + srcDirSet = sourceSets.main.java + + outputDir = project.javadoc.destinationDir + } + } +} + +class MissingJavadocTask extends DefaultTask { + @InputFiles + @SkipWhenEmpty + SourceDirectorySet srcDirSet; + + @OutputDirectory + File outputDir + + @CompileClasspath + FileCollection classpath + + @CompileClasspath + FileCollection docletpath + + @Input + String title + + @Input + boolean linksource = false + + @Input + boolean relativeProjectLinks = false + + @Input + String javadocMissingLevel = "parameter" + + // anything in these packages is checked with level=method. This allows iteratively fixing one package at a time. + @Input + List javadocMissingMethod = [] + + // default is not to ignore any elements, should only be used to workaround split packages + @Input + List javadocMissingIgnore = [] + + @Nullable + @Optional + @Input + def executable + + @Input + def taskResources + + /** Utility method to recursively collect all tasks with same name like this one that we depend on */ + private Set findRenderTasksInDependencies() { + Set found = [] + def collectDeps + collectDeps = { task -> task.taskDependencies.getDependencies(task).findAll{ it.name == this.name && it.enabled && !found.contains(it) }.each{ + found << it + collectDeps(it) + }} + collectDeps(this) + return found + } + + @TaskAction + void render() { + def srcDirs = srcDirSet.srcDirs.findAll { dir -> dir.exists() } + def optionsFile = project.file("${getTemporaryDir()}/javadoc-options.txt") + + // create the directory, so relative link calculation knows that it's a directory: + outputDir.mkdirs(); + + def opts = [] + opts << [ '-overview', project.file("${srcDirs[0]}/overview.html") ] + opts << [ '-sourcepath', srcDirs.join(File.pathSeparator) ] + if (project.getGroup().toString().startsWith('org.opensearch')) { + opts << [ '-subpackages', 'org.opensearch'] + } else { + opts << [ '-subpackages', project.getGroup().toString()] + } + opts << [ '-d', outputDir ] + opts << '-protected' + opts << [ '-encoding', 'UTF-8' ] + opts << [ '-charset', 'UTF-8' ] + opts << [ '-docencoding', 'UTF-8' ] + opts << '-noindex' + opts << '-author' + opts << '-version' + if (linksource) { + opts << '-linksource' + } + opts << '-use' + opts << [ '-locale', 'en_US' ] + opts << [ '-windowtitle', title ] + opts << [ '-doctitle', title ] + if (!classpath.isEmpty()) { + opts << [ '-classpath', classpath.asPath ] + } + opts << [ '-doclet', "org.opensearch.missingdoclet.MissingDoclet" ] + opts << [ '-docletpath', docletpath.asPath ] + opts << [ '--missing-level', javadocMissingLevel ] + if (javadocMissingIgnore) { + opts << [ '--missing-ignore', String.join(',', javadocMissingIgnore) ] + } + if (javadocMissingMethod) { + opts << [ '--missing-method', String.join(',', javadocMissingMethod) ] + } + opts << [ '-quiet' ] + opts << [ '--release', 11 ] + opts << '-Xdoclint:all,-missing' + + // Temporary file that holds all javadoc options for the current task. + optionsFile.withWriter("UTF-8", { writer -> + // escapes an option with single quotes or whitespace to be passed in the options.txt file for + def escapeJavadocOption = { String s -> (s =~ /[ '"]/) ? ("'" + s.replaceAll(/[\\'"]/, /\\$0/) + "'") : s } + + opts.each { entry -> + if (entry instanceof List) { + writer.write(entry.collect { escapeJavadocOption(it as String) }.join(" ")) + } else { + writer.write(escapeJavadocOption(entry as String)) + } + writer.write('\n') + } + }) + + def javadocCmd = { + if (executable == null) { + JavaInstallationRegistry registry = project.extensions.getByType(JavaInstallationRegistry) + JavaInstallation currentJvm = registry.installationForCurrentVirtualMachine.get() + return currentJvm.jdk.get().javadocExecutable.asFile + } else { + return project.file(executable) + } + }() + + def outputFile = project.file("${getTemporaryDir()}/javadoc-output.txt") + def result + + outputFile.withOutputStream { output -> + result = project.exec { + executable javadocCmd + + // we want to capture both stdout and stderr to the same + // stream but gradle attempts to close these separately + // (it has two independent pumping threads) and it can happen + // that one still tries to write something when the other closed + // the underlying output stream. + def wrapped = new java.io.FilterOutputStream(output) { + public void close() { + // no-op. we close this stream manually. + } + } + standardOutput = wrapped + errorOutput = wrapped + + args += [ "@${optionsFile}" ] + + // -J flags can't be passed via options file... (an error "javadoc: error - invalid flag: -J-Xmx512m" occurs.) + args += [ "-J-Xmx512m" ] + // force locale to be "en_US" (fix for: https://bugs.openjdk.java.net/browse/JDK-8222793) + args += [ "-J-Duser.language=en", "-J-Duser.country=US" ] + + ignoreExitValue true + } + } + + if (result.getExitValue() != 0) { + // Pipe the output to console. Intentionally skips any encoding conversion + // and pumps raw bytes. + System.out.write(outputFile.bytes) + + def cause + try { + result.rethrowFailure() + } catch (ex) { + cause = ex + } + throw new GradleException("Javadoc generation failed for ${project.path},\n Options file at: ${optionsFile}\n Command output at: ${outputFile}", cause) + } + } +} diff --git a/settings.gradle b/settings.gradle index 06f8cb9e9a870..6599a84891db4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,6 +15,9 @@ plugins { rootProject.name = "OpenSearch" +include 'doc-tools' +includeBuild("doc-tools/missing-doclet") + List projects = [ 'build-tools', 'build-tools:reaper', @@ -128,3 +131,4 @@ if (extraProjects.exists()) { addSubProjects('', extraProjectDir) } } + diff --git a/test/fixtures/azure-fixture/build.gradle b/test/fixtures/azure-fixture/build.gradle index 25d64d6596105..63722c586c917 100644 --- a/test/fixtures/azure-fixture/build.gradle +++ b/test/fixtures/azure-fixture/build.gradle @@ -33,6 +33,7 @@ apply plugin: 'opensearch.test.fixtures' description = 'Fixture for Azure external service' test.enabled = false +group = 'fixture.azure' dependencies { api project(':server') } diff --git a/test/fixtures/gcs-fixture/build.gradle b/test/fixtures/gcs-fixture/build.gradle index 0ab4eb7d0ed1a..8a2f7f0675497 100644 --- a/test/fixtures/gcs-fixture/build.gradle +++ b/test/fixtures/gcs-fixture/build.gradle @@ -37,6 +37,7 @@ dependencies { api project(':server') } +group = 'fixture.gcs' preProcessFixture { dependsOn jar, configurations.runtimeClasspath doLast { diff --git a/test/fixtures/hdfs-fixture/build.gradle b/test/fixtures/hdfs-fixture/build.gradle index 4711f12ff849a..89d6ba66b2a22 100644 --- a/test/fixtures/hdfs-fixture/build.gradle +++ b/test/fixtures/hdfs-fixture/build.gradle @@ -30,6 +30,8 @@ apply plugin: 'opensearch.java' +group = 'hdfs' + dependencies { api "org.apache.hadoop:hadoop-minicluster:2.10.1" } diff --git a/test/fixtures/old-elasticsearch/build.gradle b/test/fixtures/old-elasticsearch/build.gradle index 9f01b0ee0f320..b54ef88b206ad 100644 --- a/test/fixtures/old-elasticsearch/build.gradle +++ b/test/fixtures/old-elasticsearch/build.gradle @@ -37,6 +37,8 @@ a "ports" file with the port on which Elasticsearch is running. apply plugin: 'opensearch.java' test.enabled = false +group = 'oldes' + dependencies { // Just for the constants.... api "org.apache.lucene:lucene-core:${versions.lucene}" diff --git a/test/fixtures/s3-fixture/build.gradle b/test/fixtures/s3-fixture/build.gradle index 5ae846a876617..d65020f8009a0 100644 --- a/test/fixtures/s3-fixture/build.gradle +++ b/test/fixtures/s3-fixture/build.gradle @@ -32,6 +32,7 @@ apply plugin: 'opensearch.test.fixtures' description = 'Fixture for S3 Storage service' test.enabled = false +group = 'fixture.s3' dependencies { api project(':server')