Skip to content

Commit 4dd4f37

Browse files
authored
Implement simple API listing (#509)
* Implement simple API listing * Suppress comparator serialization * Apply spotless formatting * Place verb before path in results Makes for easier/more natural reading for humans * Migrate to V2 API response format and include HTTP_API.md resource with link * Add unit test * Add simple itest * Do not output duplicate endpoints * Remove unused field * Rename ApiListingHandler -> ApiGetHandler Name follows existing handler naming convention
1 parent ccdb720 commit 4dd4f37

File tree

5 files changed

+692
-1
lines changed

5 files changed

+692
-1
lines changed

pom.xml

+25-1
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,30 @@
210210
</annotationProcessorPaths>
211211
</configuration>
212212
</plugin>
213+
<plugin>
214+
<artifactId>maven-resources-plugin</artifactId>
215+
<version>${org.apache.maven.plugins.resources.version}</version>
216+
<executions>
217+
<execution>
218+
<id>copy-apidoc-resource</id>
219+
<phase>prepare-package</phase>
220+
<goals>
221+
<goal>copy-resources</goal>
222+
</goals>
223+
<configuration>
224+
<resources>
225+
<resource>
226+
<directory>${project.basedir}</directory>
227+
<includes>
228+
<include>HTTP_API.md</include>
229+
</includes>
230+
</resource>
231+
</resources>
232+
<outputDirectory>${project.build.directory}/assets/app/resources/io/cryostat/net/web</outputDirectory>
233+
</configuration>
234+
</execution>
235+
</executions>
236+
</plugin>
213237
<plugin>
214238
<groupId>org.codehaus.mojo</groupId>
215239
<artifactId>exec-maven-plugin</artifactId>
@@ -653,7 +677,7 @@
653677
<resource>
654678
<directory>web-client/dist</directory>
655679
<excludes>
656-
<exclude>cache/**/*</exclude>
680+
<exclude>cache/**/*</exclude>
657681
</excludes>
658682
</resource>
659683
</resources>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* Copyright The Cryostat Authors
3+
*
4+
* The Universal Permissive License (UPL), Version 1.0
5+
*
6+
* Subject to the condition set forth below, permission is hereby granted to any
7+
* person obtaining a copy of this software, associated documentation and/or data
8+
* (collectively the "Software"), free of charge and under any and all copyright
9+
* rights in the Software, and any and all patent rights owned or freely
10+
* licensable by each licensor hereunder covering either (i) the unmodified
11+
* Software as contributed to or provided by such licensor, or (ii) the Larger
12+
* Works (as defined below), to deal in both
13+
*
14+
* (a) the Software, and
15+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
16+
* one is included with the Software (each a "Larger Work" to which the Software
17+
* is contributed by such licensors),
18+
*
19+
* without restriction, including without limitation the rights to copy, create
20+
* derivative works of, display, perform, and distribute the Software and make,
21+
* use, sell, offer for sale, import, export, have made, and have sold the
22+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
23+
* either these or other terms.
24+
*
25+
* This license is subject to the following condition:
26+
* The above copyright notice and either this complete permission notice or at
27+
* a minimum a reference to the UPL must be included in all copies or
28+
* substantial portions of the Software.
29+
*
30+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
36+
* SOFTWARE.
37+
*/
38+
package io.cryostat.net.web.http.api.v2;
39+
40+
import java.net.URI;
41+
import java.net.URL;
42+
import java.util.List;
43+
import java.util.Objects;
44+
import java.util.Set;
45+
import java.util.stream.Collectors;
46+
47+
import javax.inject.Inject;
48+
49+
import io.cryostat.net.AuthManager;
50+
import io.cryostat.net.web.WebServer;
51+
import io.cryostat.net.web.http.HttpMimeType;
52+
import io.cryostat.net.web.http.RequestHandler;
53+
import io.cryostat.net.web.http.api.ApiVersion;
54+
55+
import com.google.gson.Gson;
56+
import com.google.gson.annotations.SerializedName;
57+
import dagger.Lazy;
58+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
59+
import io.vertx.core.http.HttpMethod;
60+
import org.apache.commons.lang3.builder.HashCodeBuilder;
61+
62+
class ApiGetHandler extends AbstractV2RequestHandler<ApiGetHandler.ApiResponse> {
63+
64+
private final Lazy<WebServer> webServer;
65+
private final Lazy<Set<RequestHandler>> handlers;
66+
67+
@Inject
68+
ApiGetHandler(
69+
Lazy<WebServer> webServer,
70+
Lazy<Set<RequestHandler>> handlers,
71+
AuthManager auth,
72+
Gson gson) {
73+
super(auth, gson);
74+
this.webServer = webServer;
75+
this.handlers = handlers;
76+
}
77+
78+
@Override
79+
public HttpMethod httpMethod() {
80+
return HttpMethod.GET;
81+
}
82+
83+
@Override
84+
public ApiVersion apiVersion() {
85+
return ApiVersion.GENERIC;
86+
}
87+
88+
@Override
89+
public String path() {
90+
return basePath() + "api";
91+
}
92+
93+
@Override
94+
HttpMimeType mimeType() {
95+
return HttpMimeType.JSON;
96+
}
97+
98+
@Override
99+
public boolean requiresAuthentication() {
100+
return false;
101+
}
102+
103+
@Override
104+
IntermediateResponse<ApiResponse> handle(RequestParameters requestParams) throws Exception {
105+
List<SerializedHandler> serializedHandlers =
106+
handlers.get().stream()
107+
.filter(RequestHandler::isAvailable)
108+
.filter(handler -> !ApiVersion.GENERIC.equals(handler.apiVersion()))
109+
.sorted((h1, h2) -> h1.path().compareTo(h2.path()))
110+
.map(SerializedHandler::new)
111+
.distinct()
112+
.collect(Collectors.toList());
113+
114+
URL resourceFilePath = new URL(webServer.get().getHostUrl(), "HTTP_API.md");
115+
116+
return new IntermediateResponse<ApiResponse>()
117+
.body(new ApiResponse(resourceFilePath, serializedHandlers));
118+
}
119+
120+
@SuppressFBWarnings("URF_UNREAD_FIELD")
121+
static class ApiResponse {
122+
@SerializedName("overview")
123+
final URL resourceFilePath;
124+
125+
@SerializedName("endpoints")
126+
final List<SerializedHandler> handlers;
127+
128+
ApiResponse(URL resourceFilePath, List<SerializedHandler> handlers) {
129+
this.resourceFilePath = resourceFilePath;
130+
this.handlers = handlers;
131+
}
132+
}
133+
134+
@SuppressFBWarnings("URF_UNREAD_FIELD")
135+
static class SerializedHandler {
136+
@SerializedName("version")
137+
final ApiVersion apiVersion;
138+
139+
@SerializedName("verb")
140+
final HttpMethod httpMethod;
141+
142+
final String path;
143+
144+
SerializedHandler(RequestHandler handler) {
145+
this.apiVersion = handler.apiVersion();
146+
this.httpMethod = handler.httpMethod();
147+
this.path = URI.create(handler.path()).normalize().toString();
148+
}
149+
150+
@Override
151+
public boolean equals(Object o) {
152+
if (!(o instanceof SerializedHandler)) {
153+
return false;
154+
}
155+
SerializedHandler osh = (SerializedHandler) o;
156+
return Objects.equals(apiVersion, osh.apiVersion)
157+
&& Objects.equals(httpMethod, osh.httpMethod)
158+
&& Objects.equals(path, osh.path);
159+
}
160+
161+
@Override
162+
public int hashCode() {
163+
return new HashCodeBuilder().append(apiVersion).append(httpMethod).append(path).build();
164+
}
165+
}
166+
}

src/main/java/io/cryostat/net/web/http/api/v2/HttpApiV2Module.java

+4
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@
5656
@Module
5757
public abstract class HttpApiV2Module {
5858

59+
@Binds
60+
@IntoSet
61+
abstract RequestHandler bindApiGetHandler(ApiGetHandler handler);
62+
5963
@Binds
6064
@IntoSet
6165
abstract RequestHandler bindTargetsPostHandler(TargetsPostHandler handler);

0 commit comments

Comments
 (0)