Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions spring-grpc-docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Spring gRPC Docs

## Configuration Properties
The Spring gRPC configuration properties are automatically documented as follows:

1. This module contains a Java class (`org.springframework.grpc.internal.ConfigurationPropertiesAsciidocGenerator`) that is compiled when the module is built.
1. This class is then used during the Maven `package` phase to generate an asciidoc page containing each of the configuration properties.
1. The asciidoc is then included in the Antora reference documentation.

## Antora Site

To build the Antora site locally run the following command from the project root directory:
```
./mvnw -pl spring-grpc-docs antora
```
You can then view the output by opening `spring-grpc-docs/target/antora/site/index.html`.
46 changes: 45 additions & 1 deletion spring-grpc-docs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,27 @@
<name>Spring gRPC Docs</name>
<description>Spring gRPC documentation</description>

<properties>
<exec-maven-plugin.version>3.4.1</exec-maven-plugin.version>
<configprops.path>${project.basedir}/src/main/antora/modules/ROOT/partials/_configprops.adoc</configprops.path>
<configprops.inclusionPattern>spring.grpc.*</configprops.inclusionPattern>
</properties>
<!-- Dependencies used to build the config props doc generator -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.grpc</groupId>
<artifactId>spring-grpc-spring-boot-autoconfigure</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
Expand Down Expand Up @@ -63,7 +84,30 @@
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>${exec-maven-plugin.version}</version>
<executions>
<execution>
<id>generate-configprops</id>
<phase>package</phase>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<includeProjectDependencies>true</includeProjectDependencies>
<includePluginDependencies>false</includePluginDependencies>
<mainClass>org.springframework.grpc.internal.ConfigurationPropertiesAsciidocGenerator</mainClass>
<arguments>
<argument>${configprops.path}</argument>
<argument>${configprops.inclusionPattern}</argument>
</arguments>
</configuration>
</plugin>
</plugins>
</build>

</project>
</project>
3 changes: 1 addition & 2 deletions spring-grpc-docs/src/main/antora/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
* xref:index.adoc[Overview]
* xref:concepts.adoc[GRPC Concepts]
* xref:getting-started.adoc[Getting Started]

* xref:contribution-guidelines.adoc[Contribution Guidelines]

* xref:appendix.adoc[]
11 changes: 11 additions & 0 deletions spring-grpc-docs/src/main/antora/modules/ROOT/pages/appendix.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
:numbered!:

[appendix]
[[common-application-properties]]
= Common application properties
:page-section-summary-toc: 1

Various properties can be specified inside your `application.properties` file, inside your `application.yml` file, or as command line switches.
This appendix provides a list of common `Spring gRPC` properties.

include::partial$_configprops.adoc[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
|===
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is automatically generated. Should we even check it in? I notice other spring-cloud repos are checking it in. I believe having it checked in avoids annoying red warnings coming from the consuming adocs that are referencing it.

|Name | Default | Description

|spring.grpc.client.channels | |
|spring.grpc.server.address | `+++*+++` | Server address to bind to. The default is any IP address ('*').
|spring.grpc.server.keep-alive.max-age | | Maximum time a connection may exist before being gracefully terminated (default infinite).
|spring.grpc.server.keep-alive.max-age-grace | | Maximum time for graceful connection termination (default infinite).
|spring.grpc.server.keep-alive.max-idle | | Maximum time a connection can remain idle before being gracefully terminated (default infinite).
|spring.grpc.server.keep-alive.permit-time | `+++5m+++` | Maximum keep-alive time clients are permitted to configure (default 5m).
|spring.grpc.server.keep-alive.permit-without-calls | `+++false+++` | Whether clients are permitted to send keep alive pings when there are no outstanding RPCs on the connection (default false).
|spring.grpc.server.keep-alive.time | `+++2h+++` | Duration without read activity before sending a keep alive ping (default 2h).
|spring.grpc.server.keep-alive.timeout | `+++20s+++` | Maximum time to wait for read activity after sending a keep alive ping. If sender does not receive an acknowledgment within this time, it will close the connection (default 20s).
|spring.grpc.server.max-inbound-message-size | `+++4194304B+++` | Maximum message size allowed to be received by the server (default 4MiB).
|spring.grpc.server.max-inbound-metadata-size | `+++8192B+++` | Maximum metadata size allowed to be received by the server (default 8KiB).
|spring.grpc.server.port | `+++9090+++` | Server port to listen on. When the value is 0, a random available port is selected. The default is 9090.
|spring.grpc.server.shutdown-grace-period | `+++30s+++` | Maximum time to wait for the server to gracefully shutdown. When the value is negative, the server waits forever. When the value is 0, the server will force shutdown immediately. The default is 30 seconds.

|===
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* Copyright 2012-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.grpc.internal;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.ObjectMapper;

import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;

/**
* Generate Asciidoc for configuration properties.
* <p>
* Copied from 'spring-cloud-build' to avoid direct dependency on Spring Cloud.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not want to put a direct dependency on spring-cloud-build just for this. I therefore copy/pasta shift/lift etc..

I will take a pass through this code in a subsequent PR as it can be simplified greatly and get a little 2024 makeover.

*
* @author Marcin Grzejszczak
* @author Chris Bono
*/
public class ConfigurationPropertiesAsciidocGenerator {

public static void main(String... args) {
String outputFile = args[0];
String inclusionPattern = args.length > 1 ? args[1] : ".*";
File parent = new File(outputFile).getParentFile();
if (!parent.exists()) {
System.out.println("No parent directory [" + parent.toString()
+ "] found. Will not generate the configuration properties file");
return;
}
new Generator().generate(outputFile, inclusionPattern);
}

static class Generator {

void generate(String outputFile, String inclusionPattern) {
try {
System.out.println("Parsing all configuration metadata");
Resource[] resources = getResources();
System.out.println("Found [" + resources.length + "] configuration metadata jsons");
TreeSet<String> names = new TreeSet<>();
Map<String, ConfigValue> descriptions = new HashMap<>();
final AtomicInteger count = new AtomicInteger();
final AtomicInteger matchingPropertyCount = new AtomicInteger();
final AtomicInteger propertyCount = new AtomicInteger();
Pattern pattern = Pattern.compile(inclusionPattern);
for (Resource resource : resources) {
if (resourceNameContainsPattern(resource)) {
count.incrementAndGet();
byte[] bytes = StreamUtils.copyToByteArray(resource.getInputStream());
Map<String, Object> response = new ObjectMapper().readValue(bytes, HashMap.class);
List<Map<String, Object>> properties = (List<Map<String, Object>>) response.get("properties");
properties.forEach(val -> {
propertyCount.incrementAndGet();
String name = String.valueOf(val.get("name"));
if (!pattern.matcher(name).matches()) {
return;
}
Object description = val.get("description");
Object defaultValue = val.get("defaultValue");
matchingPropertyCount.incrementAndGet();
names.add(name);
descriptions.put(name, new ConfigValue(name, description, defaultValue));
});
}
}
System.out.println(
"Found [" + count + "] Spring projects configuration metadata jsons. [" + matchingPropertyCount
+ "/" + propertyCount + "] were matching the pattern [" + inclusionPattern + "]");
System.out.println("Successfully built the description table");
if (names.isEmpty()) {
System.out.println("Will not update the table, since no configuration properties were found!");
return;
}
Files.write(new File(outputFile).toPath(), ("|===\n" + "|Name | Default | Description\n\n"
+ names.stream().map(it -> descriptions.get(it).toString()).collect(Collectors.joining("\n"))
+ "\n\n" + "|===")
.getBytes());
System.out.println("Successfully stored the output file");
}
catch (IOException e) {
throw new IllegalStateException(e);
}
}

protected boolean resourceNameContainsPattern(Resource resource) {
try {
return resource.getURL().toString().contains("spring");
}
catch (Exception e) {
System.out.println("Exception [" + e + "] for resource [" + resource
+ "] occurred while trying to retrieve its URL");
return false;
}
}

protected Resource[] getResources() throws IOException {
return new PathMatchingResourcePatternResolver()
.getResources("classpath*:/META-INF/spring-configuration-metadata.json");
}

}

static class ConfigValue {

public String name;

public String description;

public String defaultValue;

ConfigValue() {
}

ConfigValue(String name, Object description, Object defaultValue) {
this.name = name;
this.description = escapedValue(description);
this.defaultValue = escapedValue(defaultValue);
}

private String escapedValue(Object value) {
return value != null ? value.toString().replaceAll("\\|", "\\\\|") : "";
}

public String toString() {
return "|" + name + " | " + (StringUtils.hasText(defaultValue) ? ("`+++" + defaultValue + "+++`") : "")
+ " | " + description;
}

}

}