diff --git a/core/src/test/java/org/apache/iceberg/rest/RESTCatalogAdapter.java b/core/src/test/java/org/apache/iceberg/rest/RESTCatalogAdapter.java index 675aa8ab6f46..d6d6200d4e6d 100644 --- a/core/src/test/java/org/apache/iceberg/rest/RESTCatalogAdapter.java +++ b/core/src/test/java/org/apache/iceberg/rest/RESTCatalogAdapter.java @@ -51,7 +51,6 @@ import org.apache.iceberg.exceptions.RESTException; import org.apache.iceberg.exceptions.UnprocessableEntityException; import org.apache.iceberg.exceptions.ValidationException; -import org.apache.iceberg.relocated.com.google.common.base.Splitter; import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap; import org.apache.iceberg.relocated.com.google.common.collect.Lists; import org.apache.iceberg.rest.HTTPRequest.HTTPMethod; @@ -67,22 +66,14 @@ import org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest; import org.apache.iceberg.rest.requests.UpdateTableRequest; import org.apache.iceberg.rest.responses.ConfigResponse; -import org.apache.iceberg.rest.responses.CreateNamespaceResponse; import org.apache.iceberg.rest.responses.ErrorResponse; -import org.apache.iceberg.rest.responses.GetNamespaceResponse; -import org.apache.iceberg.rest.responses.ListNamespacesResponse; -import org.apache.iceberg.rest.responses.ListTablesResponse; import org.apache.iceberg.rest.responses.LoadTableResponse; -import org.apache.iceberg.rest.responses.LoadViewResponse; import org.apache.iceberg.rest.responses.OAuthTokenResponse; -import org.apache.iceberg.rest.responses.UpdateNamespacePropertiesResponse; import org.apache.iceberg.util.Pair; import org.apache.iceberg.util.PropertyUtil; /** Adaptor class to translate REST requests into {@link Catalog} API calls. */ public class RESTCatalogAdapter extends BaseHTTPClient { - private static final Splitter SLASH = Splitter.on('/'); - private static final Map, Integer> EXCEPTION_ERROR_CODES = ImmutableMap., Integer>builder() .put(IllegalArgumentException.class, 400) @@ -114,147 +105,6 @@ public RESTCatalogAdapter(Catalog catalog) { this.asViewCatalog = catalog instanceof ViewCatalog ? (ViewCatalog) catalog : null; } - enum Route { - TOKENS(HTTPMethod.POST, ResourcePaths.tokens(), null, OAuthTokenResponse.class), - SEPARATE_AUTH_TOKENS_URI( - HTTPMethod.POST, "https://auth-server.com/token", null, OAuthTokenResponse.class), - CONFIG(HTTPMethod.GET, ResourcePaths.config(), null, ConfigResponse.class), - LIST_NAMESPACES( - HTTPMethod.GET, ResourcePaths.V1_NAMESPACES, null, ListNamespacesResponse.class), - CREATE_NAMESPACE( - HTTPMethod.POST, - ResourcePaths.V1_NAMESPACES, - CreateNamespaceRequest.class, - CreateNamespaceResponse.class), - NAMESPACE_EXISTS(HTTPMethod.HEAD, ResourcePaths.V1_NAMESPACE), - LOAD_NAMESPACE(HTTPMethod.GET, ResourcePaths.V1_NAMESPACE, null, GetNamespaceResponse.class), - DROP_NAMESPACE(HTTPMethod.DELETE, ResourcePaths.V1_NAMESPACE), - UPDATE_NAMESPACE( - HTTPMethod.POST, - ResourcePaths.V1_NAMESPACE_PROPERTIES, - UpdateNamespacePropertiesRequest.class, - UpdateNamespacePropertiesResponse.class), - LIST_TABLES(HTTPMethod.GET, ResourcePaths.V1_TABLES, null, ListTablesResponse.class), - CREATE_TABLE( - HTTPMethod.POST, - ResourcePaths.V1_TABLES, - CreateTableRequest.class, - LoadTableResponse.class), - TABLE_EXISTS(HTTPMethod.HEAD, ResourcePaths.V1_TABLE), - LOAD_TABLE(HTTPMethod.GET, ResourcePaths.V1_TABLE, null, LoadTableResponse.class), - REGISTER_TABLE( - HTTPMethod.POST, - ResourcePaths.V1_TABLE_REGISTER, - RegisterTableRequest.class, - LoadTableResponse.class), - UPDATE_TABLE( - HTTPMethod.POST, ResourcePaths.V1_TABLE, UpdateTableRequest.class, LoadTableResponse.class), - DROP_TABLE(HTTPMethod.DELETE, ResourcePaths.V1_TABLE), - RENAME_TABLE(HTTPMethod.POST, ResourcePaths.V1_TABLE_RENAME, RenameTableRequest.class, null), - REPORT_METRICS( - HTTPMethod.POST, ResourcePaths.V1_TABLE_METRICS, ReportMetricsRequest.class, null), - COMMIT_TRANSACTION( - HTTPMethod.POST, - ResourcePaths.V1_TRANSACTIONS_COMMIT, - CommitTransactionRequest.class, - null), - LIST_VIEWS(HTTPMethod.GET, ResourcePaths.V1_VIEWS, null, ListTablesResponse.class), - VIEW_EXISTS(HTTPMethod.HEAD, ResourcePaths.V1_VIEW), - LOAD_VIEW(HTTPMethod.GET, ResourcePaths.V1_VIEW, null, LoadViewResponse.class), - CREATE_VIEW( - HTTPMethod.POST, ResourcePaths.V1_VIEWS, CreateViewRequest.class, LoadViewResponse.class), - UPDATE_VIEW( - HTTPMethod.POST, ResourcePaths.V1_VIEW, UpdateTableRequest.class, LoadViewResponse.class), - RENAME_VIEW(HTTPMethod.POST, ResourcePaths.V1_VIEW_RENAME, RenameTableRequest.class, null), - DROP_VIEW(HTTPMethod.DELETE, ResourcePaths.V1_VIEW); - - private final HTTPMethod method; - private final int requiredLength; - private final Map requirements; - private final Map variables; - private final Class requestClass; - private final Class responseClass; - private final String resourcePath; - - Route(HTTPMethod method, String pattern) { - this(method, pattern, null, null); - } - - Route( - HTTPMethod method, - String pattern, - Class requestClass, - Class responseClass) { - this.method = method; - this.resourcePath = pattern; - - // parse the pattern into requirements and variables - List parts = - SLASH.splitToList(pattern.replaceFirst("/v1/", "v1/").replace("/{prefix}", "")); - ImmutableMap.Builder requirementsBuilder = ImmutableMap.builder(); - ImmutableMap.Builder variablesBuilder = ImmutableMap.builder(); - for (int pos = 0; pos < parts.size(); pos += 1) { - String part = parts.get(pos); - if (part.startsWith("{") && part.endsWith("}")) { - variablesBuilder.put(pos, part.substring(1, part.length() - 1)); - } else { - requirementsBuilder.put(pos, part); - } - } - - this.requestClass = requestClass; - this.responseClass = responseClass; - - this.requiredLength = parts.size(); - this.requirements = requirementsBuilder.build(); - this.variables = variablesBuilder.build(); - } - - private boolean matches(HTTPMethod requestMethod, List requestPath) { - return method == requestMethod - && requiredLength == requestPath.size() - && requirements.entrySet().stream() - .allMatch( - requirement -> - requirement - .getValue() - .equalsIgnoreCase(requestPath.get(requirement.getKey()))); - } - - private Map variables(List requestPath) { - ImmutableMap.Builder vars = ImmutableMap.builder(); - variables.forEach((key, value) -> vars.put(value, requestPath.get(key))); - return vars.build(); - } - - public static Pair> from(HTTPMethod method, String path) { - List parts = SLASH.splitToList(path); - for (Route candidate : Route.values()) { - if (candidate.matches(method, parts)) { - return Pair.of(candidate, candidate.variables(parts)); - } - } - - return null; - } - - public Class requestClass() { - return requestClass; - } - - public Class responseClass() { - return responseClass; - } - - HTTPMethod method() { - return method; - } - - String resourcePath() { - return resourcePath; - } - } - private static OAuthTokenResponse handleOAuthRequest(Object body) { Map request = (Map) castRequest(Map.class, body); String grantType = request.get("grant_type"); @@ -306,7 +156,7 @@ public T handleRequest( ConfigResponse.builder() .withEndpoints( Arrays.stream(Route.values()) - .map(r -> Endpoint.create(r.method.name(), r.resourcePath)) + .map(r -> Endpoint.create(r.method().name(), r.resourcePath())) .collect(Collectors.toList())) .build()); diff --git a/core/src/test/java/org/apache/iceberg/rest/RESTCatalogServlet.java b/core/src/test/java/org/apache/iceberg/rest/RESTCatalogServlet.java index 0944945df9c6..80f66af4b077 100644 --- a/core/src/test/java/org/apache/iceberg/rest/RESTCatalogServlet.java +++ b/core/src/test/java/org/apache/iceberg/rest/RESTCatalogServlet.java @@ -40,7 +40,6 @@ import org.apache.iceberg.relocated.com.google.common.collect.Maps; import org.apache.iceberg.relocated.com.google.common.io.CharStreams; import org.apache.iceberg.rest.HTTPRequest.HTTPMethod; -import org.apache.iceberg.rest.RESTCatalogAdapter.Route; import org.apache.iceberg.rest.responses.ErrorResponse; import org.apache.iceberg.util.Pair; import org.slf4j.Logger; diff --git a/core/src/test/java/org/apache/iceberg/rest/Route.java b/core/src/test/java/org/apache/iceberg/rest/Route.java new file mode 100644 index 000000000000..44c7312f3f00 --- /dev/null +++ b/core/src/test/java/org/apache/iceberg/rest/Route.java @@ -0,0 +1,197 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.apache.iceberg.rest; + +import java.util.List; +import java.util.Map; +import org.apache.iceberg.relocated.com.google.common.base.Splitter; +import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap; +import org.apache.iceberg.rest.requests.CommitTransactionRequest; +import org.apache.iceberg.rest.requests.CreateNamespaceRequest; +import org.apache.iceberg.rest.requests.CreateTableRequest; +import org.apache.iceberg.rest.requests.CreateViewRequest; +import org.apache.iceberg.rest.requests.RegisterTableRequest; +import org.apache.iceberg.rest.requests.RenameTableRequest; +import org.apache.iceberg.rest.requests.ReportMetricsRequest; +import org.apache.iceberg.rest.requests.UpdateNamespacePropertiesRequest; +import org.apache.iceberg.rest.requests.UpdateTableRequest; +import org.apache.iceberg.rest.responses.ConfigResponse; +import org.apache.iceberg.rest.responses.CreateNamespaceResponse; +import org.apache.iceberg.rest.responses.GetNamespaceResponse; +import org.apache.iceberg.rest.responses.ListNamespacesResponse; +import org.apache.iceberg.rest.responses.ListTablesResponse; +import org.apache.iceberg.rest.responses.LoadTableResponse; +import org.apache.iceberg.rest.responses.LoadViewResponse; +import org.apache.iceberg.rest.responses.OAuthTokenResponse; +import org.apache.iceberg.rest.responses.UpdateNamespacePropertiesResponse; +import org.apache.iceberg.util.Pair; + +enum Route { + TOKENS(HTTPRequest.HTTPMethod.POST, ResourcePaths.tokens(), null, OAuthTokenResponse.class), + SEPARATE_AUTH_TOKENS_URI( + HTTPRequest.HTTPMethod.POST, "https://auth-server.com/token", null, OAuthTokenResponse.class), + CONFIG(HTTPRequest.HTTPMethod.GET, ResourcePaths.config(), null, ConfigResponse.class), + LIST_NAMESPACES( + HTTPRequest.HTTPMethod.GET, ResourcePaths.V1_NAMESPACES, null, ListNamespacesResponse.class), + CREATE_NAMESPACE( + HTTPRequest.HTTPMethod.POST, + ResourcePaths.V1_NAMESPACES, + CreateNamespaceRequest.class, + CreateNamespaceResponse.class), + NAMESPACE_EXISTS(HTTPRequest.HTTPMethod.HEAD, ResourcePaths.V1_NAMESPACE), + LOAD_NAMESPACE( + HTTPRequest.HTTPMethod.GET, ResourcePaths.V1_NAMESPACE, null, GetNamespaceResponse.class), + DROP_NAMESPACE(HTTPRequest.HTTPMethod.DELETE, ResourcePaths.V1_NAMESPACE), + UPDATE_NAMESPACE( + HTTPRequest.HTTPMethod.POST, + ResourcePaths.V1_NAMESPACE_PROPERTIES, + UpdateNamespacePropertiesRequest.class, + UpdateNamespacePropertiesResponse.class), + LIST_TABLES(HTTPRequest.HTTPMethod.GET, ResourcePaths.V1_TABLES, null, ListTablesResponse.class), + CREATE_TABLE( + HTTPRequest.HTTPMethod.POST, + ResourcePaths.V1_TABLES, + CreateTableRequest.class, + LoadTableResponse.class), + TABLE_EXISTS(HTTPRequest.HTTPMethod.HEAD, ResourcePaths.V1_TABLE), + LOAD_TABLE(HTTPRequest.HTTPMethod.GET, ResourcePaths.V1_TABLE, null, LoadTableResponse.class), + REGISTER_TABLE( + HTTPRequest.HTTPMethod.POST, + ResourcePaths.V1_TABLE_REGISTER, + RegisterTableRequest.class, + LoadTableResponse.class), + UPDATE_TABLE( + HTTPRequest.HTTPMethod.POST, + ResourcePaths.V1_TABLE, + UpdateTableRequest.class, + LoadTableResponse.class), + DROP_TABLE(HTTPRequest.HTTPMethod.DELETE, ResourcePaths.V1_TABLE), + RENAME_TABLE( + HTTPRequest.HTTPMethod.POST, ResourcePaths.V1_TABLE_RENAME, RenameTableRequest.class, null), + REPORT_METRICS( + HTTPRequest.HTTPMethod.POST, + ResourcePaths.V1_TABLE_METRICS, + ReportMetricsRequest.class, + null), + COMMIT_TRANSACTION( + HTTPRequest.HTTPMethod.POST, + ResourcePaths.V1_TRANSACTIONS_COMMIT, + CommitTransactionRequest.class, + null), + LIST_VIEWS(HTTPRequest.HTTPMethod.GET, ResourcePaths.V1_VIEWS, null, ListTablesResponse.class), + VIEW_EXISTS(HTTPRequest.HTTPMethod.HEAD, ResourcePaths.V1_VIEW), + LOAD_VIEW(HTTPRequest.HTTPMethod.GET, ResourcePaths.V1_VIEW, null, LoadViewResponse.class), + CREATE_VIEW( + HTTPRequest.HTTPMethod.POST, + ResourcePaths.V1_VIEWS, + CreateViewRequest.class, + LoadViewResponse.class), + UPDATE_VIEW( + HTTPRequest.HTTPMethod.POST, + ResourcePaths.V1_VIEW, + UpdateTableRequest.class, + LoadViewResponse.class), + RENAME_VIEW( + HTTPRequest.HTTPMethod.POST, ResourcePaths.V1_VIEW_RENAME, RenameTableRequest.class, null), + DROP_VIEW(HTTPRequest.HTTPMethod.DELETE, ResourcePaths.V1_VIEW); + + private final HTTPRequest.HTTPMethod method; + private final int requiredLength; + private final Map requirements; + private final Map variables; + private final Class requestClass; + private final Class responseClass; + private final String resourcePath; + + Route(HTTPRequest.HTTPMethod method, String pattern) { + this(method, pattern, null, null); + } + + Route( + HTTPRequest.HTTPMethod method, + String pattern, + Class requestClass, + Class responseClass) { + this.method = method; + this.resourcePath = pattern; + + // parse the pattern into requirements and variables + List parts = + Splitter.on('/').splitToList(pattern.replaceFirst("/v1/", "v1/").replace("/{prefix}", "")); + ImmutableMap.Builder requirementsBuilder = ImmutableMap.builder(); + ImmutableMap.Builder variablesBuilder = ImmutableMap.builder(); + for (int pos = 0; pos < parts.size(); pos += 1) { + String part = parts.get(pos); + if (part.startsWith("{") && part.endsWith("}")) { + variablesBuilder.put(pos, part.substring(1, part.length() - 1)); + } else { + requirementsBuilder.put(pos, part); + } + } + + this.requestClass = requestClass; + this.responseClass = responseClass; + + this.requiredLength = parts.size(); + this.requirements = requirementsBuilder.build(); + this.variables = variablesBuilder.build(); + } + + private boolean matches(HTTPRequest.HTTPMethod requestMethod, List requestPath) { + return method == requestMethod + && requiredLength == requestPath.size() + && requirements.entrySet().stream() + .allMatch( + requirement -> + requirement.getValue().equalsIgnoreCase(requestPath.get(requirement.getKey()))); + } + + private Map variables(List requestPath) { + ImmutableMap.Builder vars = ImmutableMap.builder(); + variables.forEach((key, value) -> vars.put(value, requestPath.get(key))); + return vars.build(); + } + + public static Pair> from(HTTPRequest.HTTPMethod method, String path) { + List parts = Splitter.on('/').splitToList(path); + for (Route candidate : Route.values()) { + if (candidate.matches(method, parts)) { + return Pair.of(candidate, candidate.variables(parts)); + } + } + + return null; + } + + public Class requestClass() { + return requestClass; + } + + public Class responseClass() { + return responseClass; + } + + HTTPRequest.HTTPMethod method() { + return method; + } + + String resourcePath() { + return resourcePath; + } +} diff --git a/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java b/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java index 7d1d5aac933b..2cea63f97e5e 100644 --- a/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java +++ b/core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java @@ -2272,7 +2272,7 @@ public void testPaginationForListNamespaces(int numberOfItems) { // verify initial request with empty pageToken Mockito.verify(adapter) .handleRequest( - eq(RESTCatalogAdapter.Route.LIST_NAMESPACES), + eq(Route.LIST_NAMESPACES), eq(ImmutableMap.of("pageToken", "", "pageSize", "10")), any(), eq(ListNamespacesResponse.class), @@ -2281,7 +2281,7 @@ public void testPaginationForListNamespaces(int numberOfItems) { // verify second request with updated pageToken Mockito.verify(adapter) .handleRequest( - eq(RESTCatalogAdapter.Route.LIST_NAMESPACES), + eq(Route.LIST_NAMESPACES), eq(ImmutableMap.of("pageToken", "10", "pageSize", "10")), any(), eq(ListNamespacesResponse.class), @@ -2290,7 +2290,7 @@ public void testPaginationForListNamespaces(int numberOfItems) { // verify third request with update pageToken Mockito.verify(adapter) .handleRequest( - eq(RESTCatalogAdapter.Route.LIST_NAMESPACES), + eq(Route.LIST_NAMESPACES), eq(ImmutableMap.of("pageToken", "20", "pageSize", "10")), any(), eq(ListNamespacesResponse.class), @@ -2334,7 +2334,7 @@ public void testPaginationForListTables(int numberOfItems) { // verify initial request with empty pageToken Mockito.verify(adapter) .handleRequest( - eq(RESTCatalogAdapter.Route.LIST_TABLES), + eq(Route.LIST_TABLES), eq(ImmutableMap.of("pageToken", "", "pageSize", "10", "namespace", namespaceName)), any(), eq(ListTablesResponse.class), @@ -2343,7 +2343,7 @@ public void testPaginationForListTables(int numberOfItems) { // verify second request with updated pageToken Mockito.verify(adapter) .handleRequest( - eq(RESTCatalogAdapter.Route.LIST_TABLES), + eq(Route.LIST_TABLES), eq(ImmutableMap.of("pageToken", "10", "pageSize", "10", "namespace", namespaceName)), any(), eq(ListTablesResponse.class), @@ -2352,7 +2352,7 @@ public void testPaginationForListTables(int numberOfItems) { // verify third request with update pageToken Mockito.verify(adapter) .handleRequest( - eq(RESTCatalogAdapter.Route.LIST_TABLES), + eq(Route.LIST_TABLES), eq(ImmutableMap.of("pageToken", "20", "pageSize", "10", "namespace", namespaceName)), any(), eq(ListTablesResponse.class), diff --git a/core/src/test/java/org/apache/iceberg/rest/TestRESTViewCatalog.java b/core/src/test/java/org/apache/iceberg/rest/TestRESTViewCatalog.java index a6b1b49d3e58..f3ad68c0020a 100644 --- a/core/src/test/java/org/apache/iceberg/rest/TestRESTViewCatalog.java +++ b/core/src/test/java/org/apache/iceberg/rest/TestRESTViewCatalog.java @@ -202,7 +202,7 @@ public void testPaginationForListViews(int numberOfItems) { // verify initial request with empty pageToken Mockito.verify(adapter) .handleRequest( - eq(RESTCatalogAdapter.Route.LIST_VIEWS), + eq(Route.LIST_VIEWS), eq(ImmutableMap.of("pageToken", "", "pageSize", "10", "namespace", namespaceName)), any(), eq(ListTablesResponse.class), @@ -211,7 +211,7 @@ public void testPaginationForListViews(int numberOfItems) { // verify second request with update pageToken Mockito.verify(adapter) .handleRequest( - eq(RESTCatalogAdapter.Route.LIST_VIEWS), + eq(Route.LIST_VIEWS), eq(ImmutableMap.of("pageToken", "10", "pageSize", "10", "namespace", namespaceName)), any(), eq(ListTablesResponse.class), @@ -220,7 +220,7 @@ public void testPaginationForListViews(int numberOfItems) { // verify third request with update pageToken Mockito.verify(adapter) .handleRequest( - eq(RESTCatalogAdapter.Route.LIST_VIEWS), + eq(Route.LIST_VIEWS), eq(ImmutableMap.of("pageToken", "20", "pageSize", "10", "namespace", namespaceName)), any(), eq(ListTablesResponse.class), diff --git a/core/src/test/java/org/apache/iceberg/rest/TestRESTViewCatalogWithAssumedViewSupport.java b/core/src/test/java/org/apache/iceberg/rest/TestRESTViewCatalogWithAssumedViewSupport.java index 849733b18427..2ac08284433d 100644 --- a/core/src/test/java/org/apache/iceberg/rest/TestRESTViewCatalogWithAssumedViewSupport.java +++ b/core/src/test/java/org/apache/iceberg/rest/TestRESTViewCatalogWithAssumedViewSupport.java @@ -18,8 +18,6 @@ */ package org.apache.iceberg.rest; -import static org.apache.iceberg.rest.RESTCatalogAdapter.Route.CONFIG; - import java.io.File; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -58,7 +56,7 @@ public T handleRequest( HTTPRequest httpRequest, Class responseType, Consumer> responseHeaders) { - if (CONFIG == route) { + if (Route.CONFIG == route) { // simulate a legacy server that doesn't send back supported endpoints return castResponse(responseType, ConfigResponse.builder().build()); }