Skip to content

Commit 4cc4c2c

Browse files
authored
[REST Compatible API] Route refactoring (#69573)
Related to #51816 Makes `Route`s `RestApiVersion` -aware (and `RestHandler`s `RestApiVersion` -agnostic). Refactors how `Route`s are constructed in the case of deprecation or replacement of routes.
1 parent 6eda84a commit 4cc4c2c

File tree

102 files changed

+841
-1023
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+841
-1023
lines changed

server/src/main/java/org/elasticsearch/rest/BaseRestHandler.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -192,16 +192,6 @@ public List<Route> routes() {
192192
return delegate.routes();
193193
}
194194

195-
@Override
196-
public List<DeprecatedRoute> deprecatedRoutes() {
197-
return delegate.deprecatedRoutes();
198-
}
199-
200-
@Override
201-
public List<ReplacedRoute> replacedRoutes() {
202-
return delegate.replacedRoutes();
203-
}
204-
205195
@Override
206196
protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
207197
return delegate.prepareRequest(request, client);

server/src/main/java/org/elasticsearch/rest/DeprecationRestHandler.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public class DeprecationRestHandler implements RestHandler {
2323
private final RestHandler handler;
2424
private final String deprecationMessage;
2525
private final DeprecationLogger deprecationLogger;
26+
private final boolean compatibleVersionWarning;
2627

2728
/**
2829
* Create a {@link DeprecationRestHandler} that encapsulates the {@code handler} using the {@code deprecationLogger} to log
@@ -31,13 +32,18 @@ public class DeprecationRestHandler implements RestHandler {
3132
* @param handler The rest handler to deprecate (it's possible that the handler is reused with a different name!)
3233
* @param deprecationMessage The message to warn users with when they use the {@code handler}
3334
* @param deprecationLogger The deprecation logger
35+
* @param compatibleVersionWarning set to false so that a deprecation warning will be issued for the handled request,
36+
* set to true to that a compatibility api warning will be issue for the handled request
37+
*
3438
* @throws NullPointerException if any parameter except {@code deprecationMessage} is {@code null}
3539
* @throws IllegalArgumentException if {@code deprecationMessage} is not a valid header
3640
*/
37-
public DeprecationRestHandler(RestHandler handler, String deprecationMessage, DeprecationLogger deprecationLogger) {
41+
public DeprecationRestHandler(RestHandler handler, String deprecationMessage, DeprecationLogger deprecationLogger,
42+
boolean compatibleVersionWarning) {
3843
this.handler = Objects.requireNonNull(handler);
3944
this.deprecationMessage = requireValidHeader(deprecationMessage);
4045
this.deprecationLogger = Objects.requireNonNull(deprecationLogger);
46+
this.compatibleVersionWarning = compatibleVersionWarning;
4147
}
4248

4349
/**
@@ -47,7 +53,11 @@ public DeprecationRestHandler(RestHandler handler, String deprecationMessage, De
4753
*/
4854
@Override
4955
public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception {
50-
deprecationLogger.deprecate(DeprecationCategory.API, "deprecated_route", deprecationMessage);
56+
if (compatibleVersionWarning == false) {
57+
deprecationLogger.deprecate(DeprecationCategory.API, "deprecated_route", deprecationMessage);
58+
} else {
59+
deprecationLogger.compatibleApiWarning("deprecated_route", deprecationMessage);
60+
}
5161

5262
handler.handleRequest(request, channel, client);
5363
}

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

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,28 @@ final class MethodHandlers {
2222
private final String path;
2323
private final Map<RestRequest.Method, Map<RestApiVersion, RestHandler>> methodHandlers;
2424

25-
MethodHandlers(String path, RestHandler handler, RestRequest.Method... methods) {
25+
MethodHandlers(String path) {
2626
this.path = path;
27-
this.methodHandlers = new HashMap<>(methods.length);
28-
for (RestRequest.Method method : methods) {
29-
methodHandlers.computeIfAbsent(method, k -> new HashMap<>())
30-
.put(handler.compatibleWithVersion(), handler);
31-
}
27+
28+
// by setting the loadFactor to 1, these maps are resized only when they *must* be, and the vast majority of these
29+
// maps contain only 1 or 2 entries anyway, so most of these maps are never resized at all and waste only 1 or 0
30+
// array references, while those few that contain 3 or 4 elements will have been resized just once and will still
31+
// waste only 1 or 0 array references
32+
this.methodHandlers = new HashMap<>(2, 1);
3233
}
3334

3435
/**
3536
* Add a handler for an additional array of methods. Note that {@code MethodHandlers}
3637
* does not allow replacing the handler for an already existing method.
3738
*/
38-
MethodHandlers addMethods(RestHandler handler, RestRequest.Method... methods) {
39-
for (RestRequest.Method method : methods) {
40-
RestHandler existing = methodHandlers.computeIfAbsent(method, k -> new HashMap<>())
41-
.putIfAbsent(handler.compatibleWithVersion(), handler);
42-
if (existing != null) {
43-
throw new IllegalArgumentException("Cannot replace existing handler for [" + path + "] for method: " + method);
44-
}
39+
MethodHandlers addMethod(RestRequest.Method method, RestApiVersion version, RestHandler handler) {
40+
RestHandler existing = methodHandlers
41+
// same sizing notes as 'methodHandlers' above, except that having a size here that's more than 1 is vanishingly
42+
// rare, so an initialCapacity of 1 with a loadFactor of 1 is perfect
43+
.computeIfAbsent(method, k -> new HashMap<>(1, 1))
44+
.putIfAbsent(version, handler);
45+
if (existing != null) {
46+
throw new IllegalArgumentException("Cannot replace existing handler for [" + path + "] for method: " + method);
4547
}
4648
return this;
4749
}
@@ -61,7 +63,6 @@ RestHandler getHandler(RestRequest.Method method, RestApiVersion version) {
6163
}
6264
final RestHandler handler = versionToHandlers.get(version);
6365
return handler == null ? versionToHandlers.get(RestApiVersion.current()) : handler;
64-
6566
}
6667

6768
/**

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

Lines changed: 68 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.elasticsearch.core.internal.io.Streams;
2929
import org.elasticsearch.http.HttpServerTransport;
3030
import org.elasticsearch.indices.breaker.CircuitBreakerService;
31+
import org.elasticsearch.rest.RestHandler.Route;
3132
import org.elasticsearch.usage.UsageService;
3233

3334
import java.io.ByteArrayOutputStream;
@@ -92,91 +93,117 @@ public RestController(Set<RestHeaderDefinition> headersToCopy, UnaryOperator<Res
9293
this.handlerWrapper = handlerWrapper;
9394
this.client = client;
9495
this.circuitBreakerService = circuitBreakerService;
95-
registerHandlerNoWrap(RestRequest.Method.GET, "/favicon.ico", (request, channel, clnt) ->
96+
registerHandlerNoWrap(RestRequest.Method.GET, "/favicon.ico", RestApiVersion.current(),
97+
(request, channel, clnt) ->
9698
channel.sendResponse(new BytesRestResponse(RestStatus.OK, "image/x-icon", FAVICON_RESPONSE)));
9799
}
98100

99101
/**
100102
* Registers a REST handler to be executed when the provided {@code method} and {@code path} match the request.
101103
*
102104
* @param method GET, POST, etc.
103-
* @param path Path to handle (e.g., "/{index}/{type}/_bulk")
105+
* @param path Path to handle (e.g. "/{index}/{type}/_bulk")
106+
* @param version API version to handle (e.g. RestApiVersion.V_8)
104107
* @param handler The handler to actually execute
105108
* @param deprecationMessage The message to log and send as a header in the response
106109
*/
107-
protected void registerAsDeprecatedHandler(RestRequest.Method method, String path, RestHandler handler, String deprecationMessage) {
110+
protected void registerAsDeprecatedHandler(RestRequest.Method method, String path, RestApiVersion version,
111+
RestHandler handler, String deprecationMessage) {
108112
assert (handler instanceof DeprecationRestHandler) == false;
109-
110-
registerHandler(method, path, new DeprecationRestHandler(handler, deprecationMessage, deprecationLogger));
113+
if (version == RestApiVersion.current()) {
114+
// e.g. it was marked as deprecated in 8.x, and we're currently running 8.x
115+
registerHandler(method, path, version, new DeprecationRestHandler(handler, deprecationMessage, deprecationLogger, false));
116+
} else if (version == RestApiVersion.minimumSupported()) {
117+
// e.g. it was marked as deprecated in 7.x, and we're currently running 8.x
118+
registerHandler(method, path, version, new DeprecationRestHandler(handler, deprecationMessage, deprecationLogger, true));
119+
} else {
120+
// e.g. it was marked as deprecated in 7.x, and we're currently running *9.x*
121+
logger.debug("Deprecated route [" + method + " " + path + "] for handler [" + handler.getClass() + "] " +
122+
"with version [" + version + "], which is less than the minimum supported version [" +
123+
RestApiVersion.minimumSupported() + "]");
124+
}
111125
}
112126

113127
/**
114128
* Registers a REST handler to be executed when the provided {@code method} and {@code path} match the request, or when provided
115-
* with {@code deprecatedMethod} and {@code deprecatedPath}. Expected usage:
129+
* with {@code replacedMethod} and {@code replacedPath}. Expected usage:
116130
* <pre><code>
117131
* // remove deprecation in next major release
118-
* controller.registerWithDeprecatedHandler(POST, "/_forcemerge", this,
119-
* POST, "/_optimize", deprecationLogger);
120-
* controller.registerWithDeprecatedHandler(POST, "/{index}/_forcemerge", this,
121-
* POST, "/{index}/_optimize", deprecationLogger);
132+
* controller.registerAsDeprecatedHandler(POST, "/_forcemerge", RestApiVersion.V_8, someHandler,
133+
* POST, "/_optimize", RestApiVersion.V_7);
134+
* controller.registerAsDeprecatedHandler(POST, "/{index}/_forcemerge", RestApiVersion.V_8, someHandler,
135+
* POST, "/{index}/_optimize", RestApiVersion.V_7);
122136
* </code></pre>
123137
* <p>
124138
* The registered REST handler ({@code method} with {@code path}) is a normal REST handler that is not deprecated and it is
125-
* replacing the deprecated REST handler ({@code deprecatedMethod} with {@code deprecatedPath}) that is using the <em>same</em>
139+
* replacing the deprecated REST handler ({@code replacedMethod} with {@code replacedPath}) that is using the <em>same</em>
126140
* {@code handler}.
127141
* <p>
128142
* Deprecated REST handlers without a direct replacement should be deprecated directly using {@link #registerAsDeprecatedHandler}
129143
* and a specific message.
130144
*
131145
* @param method GET, POST, etc.
132-
* @param path Path to handle (e.g., "/_forcemerge")
146+
* @param path Path to handle (e.g. "/_forcemerge")
147+
* @param version API version to handle (e.g. RestApiVersion.V_8)
133148
* @param handler The handler to actually execute
134-
* @param deprecatedMethod GET, POST, etc.
135-
* @param deprecatedPath <em>Deprecated</em> path to handle (e.g., "/_optimize")
149+
* @param replacedMethod GET, POST, etc.
150+
* @param replacedPath <em>Replaced</em> path to handle (e.g. "/_optimize")
151+
* @param replacedVersion <em>Replaced</em> API version to handle (e.g. RestApiVersion.V_7)
136152
*/
137-
protected void registerWithDeprecatedHandler(RestRequest.Method method, String path, RestHandler handler,
138-
RestRequest.Method deprecatedMethod, String deprecatedPath) {
139-
// e.g., [POST /_optimize] is deprecated! Use [POST /_forcemerge] instead.
140-
final String deprecationMessage =
141-
"[" + deprecatedMethod.name() + " " + deprecatedPath + "] is deprecated! Use [" + method.name() + " " + path + "] instead.";
142-
143-
registerHandler(method, path, handler);
144-
registerAsDeprecatedHandler(deprecatedMethod, deprecatedPath, handler, deprecationMessage);
153+
protected void registerAsReplacedHandler(RestRequest.Method method, String path, RestApiVersion version, RestHandler handler,
154+
RestRequest.Method replacedMethod, String replacedPath, RestApiVersion replacedVersion) {
155+
// e.g. [POST /_optimize] is deprecated! Use [POST /_forcemerge] instead.
156+
final String replacedMessage =
157+
"[" + replacedMethod.name() + " " + replacedPath + "] is deprecated! Use [" + method.name() + " " + path + "] instead.";
158+
159+
registerHandler(method, path, version, handler);
160+
registerAsDeprecatedHandler(replacedMethod, replacedPath, replacedVersion, handler, replacedMessage);
145161
}
146162

147163
/**
148164
* Registers a REST handler to be executed when one of the provided methods and path match the request.
149165
*
150-
* @param path Path to handle (e.g., "/{index}/{type}/_bulk")
151-
* @param handler The handler to actually execute
152166
* @param method GET, POST, etc.
167+
* @param path Path to handle (e.g. "/{index}/{type}/_bulk")
168+
* @param version API version to handle (e.g. RestApiVersion.V_8)
169+
* @param handler The handler to actually execute
153170
*/
154-
protected void registerHandler(RestRequest.Method method, String path, RestHandler handler) {
171+
protected void registerHandler(RestRequest.Method method, String path, RestApiVersion version, RestHandler handler) {
155172
if (handler instanceof BaseRestHandler) {
156173
usageService.addRestHandler((BaseRestHandler) handler);
157174
}
158-
registerHandlerNoWrap(method, path, handlerWrapper.apply(handler));
175+
registerHandlerNoWrap(method, path, version, handlerWrapper.apply(handler));
159176
}
160177

161-
private void registerHandlerNoWrap(RestRequest.Method method, String path, RestHandler maybeWrappedHandler) {
162-
final RestApiVersion version = maybeWrappedHandler.compatibleWithVersion();
178+
private void registerHandlerNoWrap(RestRequest.Method method, String path, RestApiVersion version, RestHandler handler) {
163179
assert RestApiVersion.minimumSupported() == version || RestApiVersion.current() == version
164180
: "REST API compatibility is only supported for version " + RestApiVersion.minimumSupported().major;
165181

166-
handlers.insertOrUpdate(path, new MethodHandlers(path, maybeWrappedHandler, method),
167-
(mHandlers, newMHandler) -> mHandlers.addMethods(maybeWrappedHandler, method));
182+
handlers.insertOrUpdate(path,
183+
new MethodHandlers(path).addMethod(method, version, handler),
184+
(handlers, ignoredHandler) -> handlers.addMethod(method, version, handler));
185+
}
186+
187+
public void registerHandler(final Route route, final RestHandler handler) {
188+
if (route.isReplacement()) {
189+
Route replaced = route.getReplacedRoute();
190+
registerAsReplacedHandler(route.getMethod(), route.getPath(), route.getRestApiVersion(), handler,
191+
replaced.getMethod(), replaced.getPath(), replaced.getRestApiVersion());
192+
} else if (route.isDeprecated()) {
193+
registerAsDeprecatedHandler(route.getMethod(), route.getPath(), route.getRestApiVersion(), handler,
194+
route.getDeprecationMessage());
195+
} else {
196+
// it's just a normal route
197+
registerHandler(route.getMethod(), route.getPath(), route.getRestApiVersion(), handler);
198+
}
168199
}
169200

170201
/**
171202
* Registers a REST handler with the controller. The REST handler declares the {@code method}
172203
* and {@code path} combinations.
173204
*/
174-
public void registerHandler(final RestHandler restHandler) {
175-
restHandler.routes().forEach(route -> registerHandler(route.getMethod(), route.getPath(), restHandler));
176-
restHandler.deprecatedRoutes().forEach(route ->
177-
registerAsDeprecatedHandler(route.getMethod(), route.getPath(), restHandler, route.getDeprecationMessage()));
178-
restHandler.replacedRoutes().forEach(route -> registerWithDeprecatedHandler(route.getMethod(), route.getPath(),
179-
restHandler, route.getDeprecatedMethod(), route.getDeprecatedPath()));
205+
public void registerHandler(final RestHandler handler) {
206+
handler.routes().forEach(route -> registerHandler(route, handler));
180207
}
181208

182209
@Override
@@ -320,7 +347,7 @@ private void tryAllHandlers(final RestRequest request, final RestChannel channel
320347
// we consume the error_trace parameter first to ensure that it is always consumed
321348
if (request.paramAsBoolean("error_trace", false) && channel.detailedErrorsEnabled() == false) {
322349
channel.sendResponse(
323-
BytesRestResponse.createSimpleErrorResponse(channel, BAD_REQUEST, "error traces in responses are disabled."));
350+
BytesRestResponse.createSimpleErrorResponse(channel, BAD_REQUEST, "error traces in responses are disabled."));
324351
return;
325352
}
326353

@@ -343,9 +370,9 @@ private void tryAllHandlers(final RestRequest request, final RestChannel channel
343370
handler = handlers.getHandler(requestMethod, restApiVersion);
344371
}
345372
if (handler == null) {
346-
if (handleNoHandlerFound(rawPath, requestMethod, uri, channel)) {
347-
return;
348-
}
373+
if (handleNoHandlerFound(rawPath, requestMethod, uri, channel)) {
374+
return;
375+
}
349376
} else {
350377
dispatchRequest(request, channel, handler, restApiVersion);
351378
return;
@@ -496,7 +523,7 @@ public XContentBuilder newBuilder(@Nullable XContentType xContentType, boolean u
496523

497524
@Override
498525
public XContentBuilder newBuilder(XContentType xContentType, XContentType responseContentType, boolean useFiltering)
499-
throws IOException {
526+
throws IOException {
500527
return delegate.newBuilder(xContentType, responseContentType, useFiltering)
501528
.withCompatibleVersion(restApiVersion);
502529
}

0 commit comments

Comments
 (0)