Skip to content

Commit 3ba8abe

Browse files
committed
Expose RestController all handlers iterator. (#11876)
* Expose RestController all handlers iterator. Signed-off-by: dblock <[email protected]> * Extract MethodHandlers interface. Signed-off-by: dblock <[email protected]> * Added CHANGELOG. Signed-off-by: dblock <[email protected]> --------- Signed-off-by: dblock <[email protected]> (cherry picked from commit 6ccb46b)
1 parent a2eb7f2 commit 3ba8abe

File tree

7 files changed

+225
-75
lines changed

7 files changed

+225
-75
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
4343
- Add copy ingest processor ([#11870](https://github.com/opensearch-project/OpenSearch/pull/11870))
4444
- Introduce new feature flag "WRITEABLE_REMOTE_INDEX" to gate the writeable remote index functionality ([#11717](https://github.com/opensearch-project/OpenSearch/pull/11170))
4545
- Bump OpenTelemetry from 1.32.0 to 1.34.1 ([#11891](https://github.com/opensearch-project/OpenSearch/pull/11891))
46+
- Add `org.opensearch.rest.MethodHandlers` and `RestController#getAllHandlers` ([11876](https://github.com/opensearch-project/OpenSearch/pull/11876))
4647

4748
### Dependencies
4849
- Bumps jetty version to 9.4.52.v20230823 to fix GMS-2023-1857 ([#9822](https://github.com/opensearch-project/OpenSearch/pull/9822))

server/src/main/java/org/opensearch/common/path/PathTrie.java

+42
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import java.util.Iterator;
3838
import java.util.Map;
3939
import java.util.NoSuchElementException;
40+
import java.util.Stack;
4041
import java.util.function.BiFunction;
4142
import java.util.function.Supplier;
4243

@@ -405,4 +406,45 @@ public T next() {
405406
}
406407
};
407408
}
409+
410+
public Iterator<T> retrieveAll() {
411+
Stack<TrieNode> stack = new Stack<>();
412+
stack.add(root);
413+
414+
return new Iterator<T>() {
415+
@Override
416+
public boolean hasNext() {
417+
while (!stack.empty()) {
418+
TrieNode node = stack.peek();
419+
420+
if (node.value != null) {
421+
return true;
422+
}
423+
424+
advance();
425+
}
426+
427+
return false;
428+
}
429+
430+
@Override
431+
public T next() {
432+
while (!stack.empty()) {
433+
TrieNode node = advance();
434+
435+
if (node.value != null) {
436+
return node.value;
437+
}
438+
}
439+
440+
throw new NoSuchElementException("called next() without validating hasNext()! no more nodes available");
441+
}
442+
443+
private TrieNode advance() {
444+
TrieNode node = stack.pop();
445+
stack.addAll(node.children.values());
446+
return node;
447+
}
448+
};
449+
}
408450
}

server/src/main/java/org/opensearch/rest/MethodHandlers.java

+8-66
Original file line numberDiff line numberDiff line change
@@ -6,82 +6,24 @@
66
* compatible open source license.
77
*/
88

9-
/*
10-
* Licensed to Elasticsearch under one or more contributor
11-
* license agreements. See the NOTICE file distributed with
12-
* this work for additional information regarding copyright
13-
* ownership. Elasticsearch licenses this file to you under
14-
* the Apache License, Version 2.0 (the "License"); you may
15-
* not use this file except in compliance with the License.
16-
* You may obtain a copy of the License at
17-
*
18-
* http://www.apache.org/licenses/LICENSE-2.0
19-
*
20-
* Unless required by applicable law or agreed to in writing,
21-
* software distributed under the License is distributed on an
22-
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
23-
* KIND, either express or implied. See the License for the
24-
* specific language governing permissions and limitations
25-
* under the License.
26-
*/
27-
28-
/*
29-
* Modifications Copyright OpenSearch Contributors. See
30-
* GitHub history for details.
31-
*/
32-
339
package org.opensearch.rest;
3410

35-
import org.opensearch.common.Nullable;
11+
import org.opensearch.common.annotation.PublicApi;
3612

37-
import java.util.HashMap;
38-
import java.util.Map;
3913
import java.util.Set;
4014

4115
/**
42-
* Encapsulate multiple handlers for the same path, allowing different handlers for different HTTP verbs.
43-
*
44-
* @opensearch.api
16+
* A collection of REST method handlers.
4517
*/
46-
final class MethodHandlers {
47-
48-
private final String path;
49-
private final Map<RestRequest.Method, RestHandler> methodHandlers;
50-
51-
MethodHandlers(String path, RestHandler handler, RestRequest.Method... methods) {
52-
this.path = path;
53-
this.methodHandlers = new HashMap<>(methods.length);
54-
for (RestRequest.Method method : methods) {
55-
methodHandlers.put(method, handler);
56-
}
57-
}
58-
59-
/**
60-
* Add a handler for an additional array of methods. Note that {@code MethodHandlers}
61-
* does not allow replacing the handler for an already existing method.
62-
*/
63-
MethodHandlers addMethods(RestHandler handler, RestRequest.Method... methods) {
64-
for (RestRequest.Method method : methods) {
65-
RestHandler existing = methodHandlers.putIfAbsent(method, handler);
66-
if (existing != null) {
67-
throw new IllegalArgumentException("Cannot replace existing handler for [" + path + "] for method: " + method);
68-
}
69-
}
70-
return this;
71-
}
72-
18+
@PublicApi(since = "2.12.0")
19+
public interface MethodHandlers {
7320
/**
74-
* Returns the handler for the given method or {@code null} if none exists.
21+
* Return a set of all valid HTTP methods for the particular path.
7522
*/
76-
@Nullable
77-
RestHandler getHandler(RestRequest.Method method) {
78-
return methodHandlers.get(method);
79-
}
23+
Set<RestRequest.Method> getValidMethods();
8024

8125
/**
82-
* Return a set of all valid HTTP methods for the particular path
26+
* Returns the relative HTTP path of the set of method handlers.
8327
*/
84-
Set<RestRequest.Method> getValidMethods() {
85-
return methodHandlers.keySet();
86-
}
28+
String getPath();
8729
}

server/src/main/java/org/opensearch/rest/RestController.java

+17-6
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
import java.io.IOException;
6666
import java.io.InputStream;
6767
import java.net.URI;
68+
import java.util.ArrayList;
6869
import java.util.HashMap;
6970
import java.util.HashSet;
7071
import java.util.Iterator;
@@ -107,7 +108,7 @@ public class RestController implements HttpServerTransport.Dispatcher {
107108
}
108109
}
109110

110-
private final PathTrie<MethodHandlers> handlers = new PathTrie<>(RestUtils.REST_DECODER);
111+
private final PathTrie<RestMethodHandlers> handlers = new PathTrie<>(RestUtils.REST_DECODER);
111112

112113
private final UnaryOperator<RestHandler> handlerWrapper;
113114

@@ -144,6 +145,16 @@ public RestController(
144145
);
145146
}
146147

148+
/**
149+
* Returns an iterator over registered REST method handlers.
150+
* @return {@link Iterator} of {@link MethodHandlers}
151+
*/
152+
public Iterator<MethodHandlers> getAllHandlers() {
153+
List<MethodHandlers> methodHandlers = new ArrayList<>();
154+
handlers.retrieveAll().forEachRemaining(methodHandlers::add);
155+
return methodHandlers.iterator();
156+
}
157+
147158
/**
148159
* Registers a REST handler to be executed when the provided {@code method} and {@code path} match the request.
149160
*
@@ -221,7 +232,7 @@ protected void registerHandler(RestRequest.Method method, String path, RestHandl
221232
private void registerHandlerNoWrap(RestRequest.Method method, String path, RestHandler maybeWrappedHandler) {
222233
handlers.insertOrUpdate(
223234
path,
224-
new MethodHandlers(path, maybeWrappedHandler, method),
235+
new RestMethodHandlers(path, maybeWrappedHandler, method),
225236
(mHandlers, newMHandler) -> mHandlers.addMethods(maybeWrappedHandler, method)
226237
);
227238
}
@@ -392,10 +403,10 @@ private void tryAllHandlers(final RestRequest request, final RestChannel channel
392403
// Resolves the HTTP method and fails if the method is invalid
393404
requestMethod = request.method();
394405
// Loop through all possible handlers, attempting to dispatch the request
395-
Iterator<MethodHandlers> allHandlers = getAllHandlers(request.params(), rawPath);
406+
Iterator<RestMethodHandlers> allHandlers = getAllRestMethodHandlers(request.params(), rawPath);
396407
while (allHandlers.hasNext()) {
397408
final RestHandler handler;
398-
final MethodHandlers handlers = allHandlers.next();
409+
final RestMethodHandlers handlers = allHandlers.next();
399410
if (handlers == null) {
400411
handler = null;
401412
} else {
@@ -423,7 +434,7 @@ private void tryAllHandlers(final RestRequest request, final RestChannel channel
423434
handleBadRequest(uri, requestMethod, channel);
424435
}
425436

426-
Iterator<MethodHandlers> getAllHandlers(@Nullable Map<String, String> requestParamsRef, String rawPath) {
437+
Iterator<RestMethodHandlers> getAllRestMethodHandlers(@Nullable Map<String, String> requestParamsRef, String rawPath) {
427438
final Supplier<Map<String, String>> paramsSupplier;
428439
if (requestParamsRef == null) {
429440
paramsSupplier = () -> null;
@@ -561,7 +572,7 @@ private boolean handleAuthenticateUser(final RestRequest request, final RestChan
561572
*/
562573
private Set<RestRequest.Method> getValidHandlerMethodSet(String rawPath) {
563574
Set<RestRequest.Method> validMethods = new HashSet<>();
564-
Iterator<MethodHandlers> allHandlers = getAllHandlers(null, rawPath);
575+
Iterator<RestMethodHandlers> allHandlers = getAllRestMethodHandlers(null, rawPath);
565576
while (allHandlers.hasNext()) {
566577
final MethodHandlers methodHandlers = allHandlers.next();
567578
if (methodHandlers != null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
/*
10+
* Licensed to Elasticsearch under one or more contributor
11+
* license agreements. See the NOTICE file distributed with
12+
* this work for additional information regarding copyright
13+
* ownership. Elasticsearch licenses this file to you under
14+
* the Apache License, Version 2.0 (the "License"); you may
15+
* not use this file except in compliance with the License.
16+
* You may obtain a copy of the License at
17+
*
18+
* http://www.apache.org/licenses/LICENSE-2.0
19+
*
20+
* Unless required by applicable law or agreed to in writing,
21+
* software distributed under the License is distributed on an
22+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
23+
* KIND, either express or implied. See the License for the
24+
* specific language governing permissions and limitations
25+
* under the License.
26+
*/
27+
28+
/*
29+
* Modifications Copyright OpenSearch Contributors. See
30+
* GitHub history for details.
31+
*/
32+
33+
package org.opensearch.rest;
34+
35+
import org.opensearch.common.Nullable;
36+
37+
import java.util.HashMap;
38+
import java.util.Map;
39+
import java.util.Set;
40+
41+
/**
42+
* Encapsulate multiple handlers for the same path, allowing different handlers for different HTTP verbs.
43+
*/
44+
final class RestMethodHandlers implements MethodHandlers {
45+
46+
private final String path;
47+
private final Map<RestRequest.Method, RestHandler> methodHandlers;
48+
49+
RestMethodHandlers(String path, RestHandler handler, RestRequest.Method... methods) {
50+
this.path = path;
51+
this.methodHandlers = new HashMap<>(methods.length);
52+
for (RestRequest.Method method : methods) {
53+
methodHandlers.put(method, handler);
54+
}
55+
}
56+
57+
/**
58+
* Add a handler for an additional array of methods. Note that {@code MethodHandlers}
59+
* does not allow replacing the handler for an already existing method.
60+
*/
61+
public RestMethodHandlers addMethods(RestHandler handler, RestRequest.Method... methods) {
62+
for (RestRequest.Method method : methods) {
63+
RestHandler existing = methodHandlers.putIfAbsent(method, handler);
64+
if (existing != null) {
65+
throw new IllegalArgumentException("Cannot replace existing handler for [" + path + "] for method: " + method);
66+
}
67+
}
68+
return this;
69+
}
70+
71+
/**
72+
* Returns the handler for the given method or {@code null} if none exists.
73+
*/
74+
@Nullable
75+
public RestHandler getHandler(RestRequest.Method method) {
76+
return methodHandlers.get(method);
77+
}
78+
79+
/**
80+
* Return a set of all valid HTTP methods for the particular path.
81+
*/
82+
public Set<RestRequest.Method> getValidMethods() {
83+
return methodHandlers.keySet();
84+
}
85+
86+
/**
87+
* Returns the relative HTTP path of the set of method handlers.
88+
*/
89+
public String getPath() {
90+
return path;
91+
}
92+
}

server/src/test/java/org/opensearch/common/path/PathTrieTests.java

+31
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@
3636
import org.opensearch.rest.RestUtils;
3737
import org.opensearch.test.OpenSearchTestCase;
3838

39+
import java.util.ArrayList;
3940
import java.util.HashMap;
4041
import java.util.Iterator;
42+
import java.util.List;
4143
import java.util.Map;
4244

4345
import static org.hamcrest.Matchers.equalTo;
@@ -286,4 +288,33 @@ public void testEscapedSlashWithinUrl() {
286288
assertThat(params.get("type"), equalTo("type"));
287289
assertThat(params.get("id"), equalTo("id"));
288290
}
291+
292+
public void testRetrieveAllEmpty() {
293+
PathTrie<String> trie = new PathTrie<>(NO_DECODER);
294+
Iterator<String> allPaths = trie.retrieveAll();
295+
assertFalse(allPaths.hasNext());
296+
}
297+
298+
public void testRetrieveAll() {
299+
PathTrie<String> trie = new PathTrie<>(NO_DECODER);
300+
trie.insert("{testA}", "test1");
301+
trie.insert("{testA}/{testB}", "test2");
302+
trie.insert("a/{testB}", "test3");
303+
trie.insert("{testA}/b", "test4");
304+
trie.insert("{testA}/b/c", "test5");
305+
306+
Iterator<String> iterator = trie.retrieveAll();
307+
assertTrue(iterator.hasNext());
308+
List<String> paths = new ArrayList<>();
309+
iterator.forEachRemaining(paths::add);
310+
assertEquals(paths, List.of("test1", "test4", "test5", "test2", "test3"));
311+
assertFalse(iterator.hasNext());
312+
}
313+
314+
public void testRetrieveAllWithNllValue() {
315+
PathTrie<String> trie = new PathTrie<>(NO_DECODER);
316+
trie.insert("{testA}", null);
317+
Iterator<String> iterator = trie.retrieveAll();
318+
assertFalse(iterator.hasNext());
319+
}
289320
}

0 commit comments

Comments
 (0)