yuqi1129 commented on code in PR #12541:
URL: https://github.com/apache/gravitino/pull/12541#discussion_r3976602801


##########
spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/plugin/restcatalog/lance/LanceRESTCatalogProvider.java:
##########
@@ -0,0 +1,121 @@
+/*
+ * 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.gravitino.spark.connector.plugin.restcatalog.lance;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import 
org.apache.gravitino.spark.connector.plugin.restcatalog.LakehouseRESTCatalogProvider;
+import org.lance.namespace.client.apache.ApiClient;
+import org.lance.namespace.client.apache.ApiException;
+import org.lance.namespace.client.apache.api.NamespaceApi;
+import org.lance.namespace.model.ListNamespacesResponse;
+
+/** Discovers and configures Lance REST catalogs. */
+public class LanceRESTCatalogProvider implements LakehouseRESTCatalogProvider {
+
+  static final String FORMAT = "lance";
+  static final String CATALOG_CLASS = 
"org.lance.spark.LanceNamespaceSparkCatalog";
+  static final String SPARK_EXTENSIONS = 
"org.lance.spark.extensions.LanceSparkSessionExtensions";
+
+  private static final String ROOT_NAMESPACE_ID = "$";
+  private static final String NAMESPACE_DELIMITER = "$";
+
+  @Override
+  public String format() {
+    return FORMAT;
+  }
+
+  @Override
+  public List<String> listCatalogs(String uri, Map<String, String> 
catalogProperties) {
+    List<String> catalogs = new ArrayList<>();
+    Set<String> seenPageTokens = new HashSet<>();
+    String pageToken = null;
+
+    ApiClient apiClient = new ApiClient().setBasePath(normalizeUri(uri));
+    try (Closeable httpClient = getHttpClient(apiClient)) {
+      NamespaceApi namespaceApi = new NamespaceApi(apiClient);
+      do {
+        ListNamespacesResponse response =
+            namespaceApi.listNamespaces(ROOT_NAMESPACE_ID, 
NAMESPACE_DELIMITER, pageToken, null);
+        Preconditions.checkState(response != null, "Lance REST server returned 
an empty response");
+        Preconditions.checkState(
+            response.getNamespaces() != null,
+            "Lance REST server returned a response without namespaces");
+        catalogs.addAll(response.getNamespaces());
+
+        pageToken = StringUtils.trimToNull(response.getPageToken());
+        Preconditions.checkState(
+            pageToken == null || seenPageTokens.add(pageToken),
+            "Lance REST server returned repeated page token: %s",
+            pageToken);
+      } while (pageToken != null);
+    } catch (ApiException | IOException e) {
+      throw new IllegalStateException("Failed to list catalogs from Lance REST 
server " + uri, e);
+    }
+
+    return catalogs;
+  }
+
+  @Override
+  public String catalogClassName() {
+    return CATALOG_CLASS;
+  }
+
+  @Override
+  public Map<String, String> generatedCatalogProperties(String uri, String 
advertisedCatalogName) {

Review Comment:
   What is the meaning of `advertised` here?



##########
spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/plugin/restcatalog/GravitinoLakehouseRESTDiscoveryDriverPlugin.java:
##########
@@ -0,0 +1,420 @@
+/*
+ * 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.gravitino.spark.connector.plugin.restcatalog;
+
+import static org.apache.gravitino.spark.connector.ConnectorConstants.COMMA;
+import static 
org.apache.gravitino.spark.connector.utils.ConnectorUtil.removeDuplicateSparkExtensions;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.spark.connector.plugin.GravitinoSparkPlugin;
+import org.apache.spark.SparkConf;
+import org.apache.spark.SparkContext;
+import org.apache.spark.api.plugin.DriverPlugin;
+import org.apache.spark.api.plugin.PluginContext;
+import org.apache.spark.sql.catalyst.parser.CatalystSqlParser$;
+import org.apache.spark.sql.internal.StaticSQLConf;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import scala.Tuple2;
+import scala.collection.Seq;
+
+class GravitinoLakehouseRESTDiscoveryDriverPlugin implements DriverPlugin {
+
+  @VisibleForTesting
+  static final String REGISTRATION_POLICY_CONFIG = 
"spark.sql.gravitino.REST.registrationPolicy";
+
+  private static final Logger LOG =
+      
LoggerFactory.getLogger(GravitinoLakehouseRESTDiscoveryDriverPlugin.class);
+  private static final String GRAVITINO_PREFIX = "spark.sql.gravitino.";
+  private static final String SPARK_CATALOG_PREFIX = "spark.sql.catalog.";
+  private static final String URI_SUFFIX = "REST.uri";
+  private static final String CATALOG_PROPERTIES_INFIX = 
"REST.catalogProperties.";
+  private static final Pattern PROVIDER_URI_PATTERN =
+      
Pattern.compile("^spark\\.sql\\.gravitino\\.([A-Za-z][A-Za-z0-9]*)REST\\.uri$");
+  private static final CatalogRegistrationPolicy DEFAULT_POLICY = (format, 
catalogName) -> true;
+
+  GravitinoLakehouseRESTDiscoveryDriverPlugin() {}
+
+  @Override
+  public Map<String, String> init(SparkContext sc, PluginContext 
pluginContext) {
+    initialize(sc.conf());
+    return Collections.emptyMap();
+  }
+
+  @VisibleForTesting
+  void initialize(SparkConf sparkConf) {
+    initialize(sparkConf, BuiltinRESTCatalogProviders.providerClassNames());
+  }
+
+  @VisibleForTesting
+  void initialize(SparkConf sparkConf, Map<String, String> providerClassNames) 
{
+    validatePluginOrder(sparkConf);
+    SparkConf userConf = sparkConf.clone();
+    Map<String, String> activeFormats = findActiveFormats(userConf);
+    if (activeFormats.isEmpty()) {
+      return;
+    }
+
+    ClassLoader classLoader = contextClassLoader();
+    CatalogRegistrationPolicy policy = loadRegistrationPolicy(userConf, 
classLoader);
+    List<CatalogRegistration> registrations = new ArrayList<>();
+    Set<String> registeredNames = new LinkedHashSet<>();
+    Set<String> extensions = new LinkedHashSet<>();
+
+    activeFormats.forEach(
+        (format, uri) -> {
+          LakehouseRESTCatalogProvider provider =
+              loadProvider(format, providerClassNames, classLoader);
+          validateProviderRuntime(provider, classLoader);
+
+          Map<String, String> globalProperties = 
extractCatalogProperties(userConf, format);
+          List<String> advertisedCatalogs =
+              provider.listCatalogs(uri, 
Collections.unmodifiableMap(globalProperties));
+          Preconditions.checkState(
+              advertisedCatalogs != null,
+              "Lakehouse REST catalog provider %s returned a null catalog 
list",
+              format);
+
+          List<String> sortedCatalogs = new ArrayList<>(advertisedCatalogs);
+          Collections.sort(sortedCatalogs);

Review Comment:
   Why do we need to sort them?



##########
spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/plugin/restcatalog/CatalogRegistrationPolicy.java:
##########
@@ -0,0 +1,47 @@
+/*
+ * 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.gravitino.spark.connector.plugin.restcatalog;
+
+import org.apache.gravitino.annotation.DeveloperApi;
+
+/** Decides whether an advertised REST catalog is registered, and under what 
Spark name. */
+@DeveloperApi
+public interface CatalogRegistrationPolicy {
+
+  /**
+   * Returns whether to register an advertised catalog automatically.
+   *
+   * @param format the lakehouse format that advertised the catalog
+   * @param catalogName the catalog name advertised by the format's REST server
+   * @return true to register the catalog, false to skip it
+   */
+  boolean shouldRegister(String format, String catalogName);

Review Comment:
   So the default value is `true` or `false`? I mean: for each catalog, will we 
register it or not by default?



##########
spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/plugin/restcatalog/BuiltinRESTCatalogProviders.java:
##########
@@ -0,0 +1,38 @@
+/*
+ * 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.gravitino.spark.connector.plugin.restcatalog;
+
+import java.util.Collections;
+import java.util.Map;
+
+final class BuiltinRESTCatalogProviders {
+
+  private static final String LANCE_FORMAT = "lance";

Review Comment:
   Will `iceberg` also be added in the future?



##########
spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/plugin/restcatalog/GravitinoLakehouseRESTDiscoveryDriverPlugin.java:
##########
@@ -0,0 +1,420 @@
+/*
+ * 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.gravitino.spark.connector.plugin.restcatalog;
+
+import static org.apache.gravitino.spark.connector.ConnectorConstants.COMMA;
+import static 
org.apache.gravitino.spark.connector.utils.ConnectorUtil.removeDuplicateSparkExtensions;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.spark.connector.plugin.GravitinoSparkPlugin;
+import org.apache.spark.SparkConf;
+import org.apache.spark.SparkContext;
+import org.apache.spark.api.plugin.DriverPlugin;
+import org.apache.spark.api.plugin.PluginContext;
+import org.apache.spark.sql.catalyst.parser.CatalystSqlParser$;
+import org.apache.spark.sql.internal.StaticSQLConf;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import scala.Tuple2;
+import scala.collection.Seq;
+
+class GravitinoLakehouseRESTDiscoveryDriverPlugin implements DriverPlugin {
+
+  @VisibleForTesting
+  static final String REGISTRATION_POLICY_CONFIG = 
"spark.sql.gravitino.REST.registrationPolicy";
+
+  private static final Logger LOG =
+      
LoggerFactory.getLogger(GravitinoLakehouseRESTDiscoveryDriverPlugin.class);
+  private static final String GRAVITINO_PREFIX = "spark.sql.gravitino.";
+  private static final String SPARK_CATALOG_PREFIX = "spark.sql.catalog.";
+  private static final String URI_SUFFIX = "REST.uri";
+  private static final String CATALOG_PROPERTIES_INFIX = 
"REST.catalogProperties.";
+  private static final Pattern PROVIDER_URI_PATTERN =
+      
Pattern.compile("^spark\\.sql\\.gravitino\\.([A-Za-z][A-Za-z0-9]*)REST\\.uri$");
+  private static final CatalogRegistrationPolicy DEFAULT_POLICY = (format, 
catalogName) -> true;
+
+  GravitinoLakehouseRESTDiscoveryDriverPlugin() {}
+
+  @Override
+  public Map<String, String> init(SparkContext sc, PluginContext 
pluginContext) {
+    initialize(sc.conf());
+    return Collections.emptyMap();
+  }
+
+  @VisibleForTesting
+  void initialize(SparkConf sparkConf) {
+    initialize(sparkConf, BuiltinRESTCatalogProviders.providerClassNames());
+  }
+
+  @VisibleForTesting
+  void initialize(SparkConf sparkConf, Map<String, String> providerClassNames) 
{
+    validatePluginOrder(sparkConf);
+    SparkConf userConf = sparkConf.clone();
+    Map<String, String> activeFormats = findActiveFormats(userConf);
+    if (activeFormats.isEmpty()) {
+      return;
+    }
+
+    ClassLoader classLoader = contextClassLoader();
+    CatalogRegistrationPolicy policy = loadRegistrationPolicy(userConf, 
classLoader);
+    List<CatalogRegistration> registrations = new ArrayList<>();
+    Set<String> registeredNames = new LinkedHashSet<>();
+    Set<String> extensions = new LinkedHashSet<>();
+
+    activeFormats.forEach(
+        (format, uri) -> {
+          LakehouseRESTCatalogProvider provider =
+              loadProvider(format, providerClassNames, classLoader);
+          validateProviderRuntime(provider, classLoader);
+
+          Map<String, String> globalProperties = 
extractCatalogProperties(userConf, format);
+          List<String> advertisedCatalogs =
+              provider.listCatalogs(uri, 
Collections.unmodifiableMap(globalProperties));
+          Preconditions.checkState(
+              advertisedCatalogs != null,
+              "Lakehouse REST catalog provider %s returned a null catalog 
list",
+              format);
+
+          List<String> sortedCatalogs = new ArrayList<>(advertisedCatalogs);
+          Collections.sort(sortedCatalogs);
+          for (String catalogName : sortedCatalogs) {
+            addRegistration(
+                userConf,
+                provider,
+                policy,
+                format,
+                uri,
+                catalogName,
+                globalProperties,
+                registeredNames,
+                registrations);
+          }
+          extensions.addAll(Arrays.asList(provider.sparkExtensions()));
+        });
+
+    applyRegistrations(sparkConf, userConf, registrations);
+    registerSqlExtensions(sparkConf, extensions);
+  }
+
+  private static void validatePluginOrder(SparkConf sparkConf) {
+    String configuredPlugins = sparkConf.get("spark.plugins", "");
+    List<String> plugins = new ArrayList<>();
+    for (String plugin : configuredPlugins.split(COMMA)) {
+      if (StringUtils.isNotBlank(plugin)) {
+        plugins.add(plugin.trim());
+      }
+    }
+
+    int discoveryPluginIndex =
+        plugins.indexOf(GravitinoLakehouseRESTDiscoveryPlugin.class.getName());
+    int gravitinoPluginIndex = 
plugins.indexOf(GravitinoSparkPlugin.class.getName());
+    Preconditions.checkArgument(
+        discoveryPluginIndex < 0
+            || gravitinoPluginIndex < 0
+            || discoveryPluginIndex < gravitinoPluginIndex,
+        "%s must be listed before %s in spark.plugins",
+        GravitinoLakehouseRESTDiscoveryPlugin.class.getName(),
+        GravitinoSparkPlugin.class.getName());
+  }
+
+  private static Map<String, String> findActiveFormats(SparkConf userConf) {
+    Map<String, String> activeFormats = new TreeMap<>();
+    for (Tuple2<String, String> entry : userConf.getAll()) {
+      Matcher matcher = PROVIDER_URI_PATTERN.matcher(entry._1);
+      if (matcher.matches()) {
+        String format = matcher.group(1);
+        String uri = entry._2;
+        Preconditions.checkArgument(
+            StringUtils.isNotBlank(uri),
+            "%s%s%s must not be blank",
+            GRAVITINO_PREFIX,
+            format,
+            URI_SUFFIX);
+        activeFormats.put(format, uri);
+      }
+    }
+    return activeFormats;
+  }
+
+  private static CatalogRegistrationPolicy loadRegistrationPolicy(
+      SparkConf userConf, ClassLoader classLoader) {
+    if (!userConf.contains(REGISTRATION_POLICY_CONFIG)) {
+      return DEFAULT_POLICY;
+    }
+
+    String policyClassName = userConf.get(REGISTRATION_POLICY_CONFIG);
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(policyClassName),
+        "%s must not be blank",
+        REGISTRATION_POLICY_CONFIG);
+    try {
+      Class<?> policyClass = Class.forName(policyClassName, true, classLoader);
+      Preconditions.checkArgument(
+          CatalogRegistrationPolicy.class.isAssignableFrom(policyClass),
+          "%s does not implement %s",
+          policyClassName,
+          CatalogRegistrationPolicy.class.getName());
+      return 
CatalogRegistrationPolicy.class.cast(policyClass.getConstructor().newInstance());

Review Comment:
   `policyClass.getConstructor().newInstance()` depends on a constructor with 
empty parameter; you'd better add some restriction or description on the 
`Policy`.



##########
spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/plugin/restcatalog/GravitinoLakehouseRESTDiscoveryDriverPlugin.java:
##########
@@ -0,0 +1,420 @@
+/*
+ * 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.gravitino.spark.connector.plugin.restcatalog;
+
+import static org.apache.gravitino.spark.connector.ConnectorConstants.COMMA;
+import static 
org.apache.gravitino.spark.connector.utils.ConnectorUtil.removeDuplicateSparkExtensions;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.spark.connector.plugin.GravitinoSparkPlugin;
+import org.apache.spark.SparkConf;
+import org.apache.spark.SparkContext;
+import org.apache.spark.api.plugin.DriverPlugin;
+import org.apache.spark.api.plugin.PluginContext;
+import org.apache.spark.sql.catalyst.parser.CatalystSqlParser$;
+import org.apache.spark.sql.internal.StaticSQLConf;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import scala.Tuple2;
+import scala.collection.Seq;
+
+class GravitinoLakehouseRESTDiscoveryDriverPlugin implements DriverPlugin {
+
+  @VisibleForTesting
+  static final String REGISTRATION_POLICY_CONFIG = 
"spark.sql.gravitino.REST.registrationPolicy";
+
+  private static final Logger LOG =
+      
LoggerFactory.getLogger(GravitinoLakehouseRESTDiscoveryDriverPlugin.class);
+  private static final String GRAVITINO_PREFIX = "spark.sql.gravitino.";
+  private static final String SPARK_CATALOG_PREFIX = "spark.sql.catalog.";
+  private static final String URI_SUFFIX = "REST.uri";
+  private static final String CATALOG_PROPERTIES_INFIX = 
"REST.catalogProperties.";
+  private static final Pattern PROVIDER_URI_PATTERN =
+      
Pattern.compile("^spark\\.sql\\.gravitino\\.([A-Za-z][A-Za-z0-9]*)REST\\.uri$");
+  private static final CatalogRegistrationPolicy DEFAULT_POLICY = (format, 
catalogName) -> true;
+
+  GravitinoLakehouseRESTDiscoveryDriverPlugin() {}
+
+  @Override
+  public Map<String, String> init(SparkContext sc, PluginContext 
pluginContext) {
+    initialize(sc.conf());
+    return Collections.emptyMap();
+  }
+
+  @VisibleForTesting
+  void initialize(SparkConf sparkConf) {
+    initialize(sparkConf, BuiltinRESTCatalogProviders.providerClassNames());
+  }
+
+  @VisibleForTesting
+  void initialize(SparkConf sparkConf, Map<String, String> providerClassNames) 
{
+    validatePluginOrder(sparkConf);
+    SparkConf userConf = sparkConf.clone();
+    Map<String, String> activeFormats = findActiveFormats(userConf);
+    if (activeFormats.isEmpty()) {
+      return;
+    }
+
+    ClassLoader classLoader = contextClassLoader();
+    CatalogRegistrationPolicy policy = loadRegistrationPolicy(userConf, 
classLoader);
+    List<CatalogRegistration> registrations = new ArrayList<>();
+    Set<String> registeredNames = new LinkedHashSet<>();
+    Set<String> extensions = new LinkedHashSet<>();
+
+    activeFormats.forEach(
+        (format, uri) -> {
+          LakehouseRESTCatalogProvider provider =
+              loadProvider(format, providerClassNames, classLoader);
+          validateProviderRuntime(provider, classLoader);
+
+          Map<String, String> globalProperties = 
extractCatalogProperties(userConf, format);
+          List<String> advertisedCatalogs =
+              provider.listCatalogs(uri, 
Collections.unmodifiableMap(globalProperties));
+          Preconditions.checkState(
+              advertisedCatalogs != null,
+              "Lakehouse REST catalog provider %s returned a null catalog 
list",
+              format);
+
+          List<String> sortedCatalogs = new ArrayList<>(advertisedCatalogs);
+          Collections.sort(sortedCatalogs);
+          for (String catalogName : sortedCatalogs) {
+            addRegistration(
+                userConf,
+                provider,
+                policy,
+                format,
+                uri,
+                catalogName,
+                globalProperties,
+                registeredNames,
+                registrations);
+          }
+          extensions.addAll(Arrays.asList(provider.sparkExtensions()));
+        });
+
+    applyRegistrations(sparkConf, userConf, registrations);
+    registerSqlExtensions(sparkConf, extensions);
+  }
+
+  private static void validatePluginOrder(SparkConf sparkConf) {
+    String configuredPlugins = sparkConf.get("spark.plugins", "");
+    List<String> plugins = new ArrayList<>();
+    for (String plugin : configuredPlugins.split(COMMA)) {
+      if (StringUtils.isNotBlank(plugin)) {
+        plugins.add(plugin.trim());
+      }
+    }
+
+    int discoveryPluginIndex =
+        plugins.indexOf(GravitinoLakehouseRESTDiscoveryPlugin.class.getName());
+    int gravitinoPluginIndex = 
plugins.indexOf(GravitinoSparkPlugin.class.getName());
+    Preconditions.checkArgument(
+        discoveryPluginIndex < 0
+            || gravitinoPluginIndex < 0
+            || discoveryPluginIndex < gravitinoPluginIndex,
+        "%s must be listed before %s in spark.plugins",
+        GravitinoLakehouseRESTDiscoveryPlugin.class.getName(),
+        GravitinoSparkPlugin.class.getName());
+  }
+
+  private static Map<String, String> findActiveFormats(SparkConf userConf) {
+    Map<String, String> activeFormats = new TreeMap<>();
+    for (Tuple2<String, String> entry : userConf.getAll()) {
+      Matcher matcher = PROVIDER_URI_PATTERN.matcher(entry._1);
+      if (matcher.matches()) {
+        String format = matcher.group(1);
+        String uri = entry._2;
+        Preconditions.checkArgument(
+            StringUtils.isNotBlank(uri),
+            "%s%s%s must not be blank",
+            GRAVITINO_PREFIX,
+            format,
+            URI_SUFFIX);
+        activeFormats.put(format, uri);
+      }
+    }
+    return activeFormats;
+  }
+
+  private static CatalogRegistrationPolicy loadRegistrationPolicy(
+      SparkConf userConf, ClassLoader classLoader) {
+    if (!userConf.contains(REGISTRATION_POLICY_CONFIG)) {
+      return DEFAULT_POLICY;
+    }
+
+    String policyClassName = userConf.get(REGISTRATION_POLICY_CONFIG);
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(policyClassName),
+        "%s must not be blank",
+        REGISTRATION_POLICY_CONFIG);
+    try {
+      Class<?> policyClass = Class.forName(policyClassName, true, classLoader);
+      Preconditions.checkArgument(
+          CatalogRegistrationPolicy.class.isAssignableFrom(policyClass),
+          "%s does not implement %s",
+          policyClassName,
+          CatalogRegistrationPolicy.class.getName());
+      return 
CatalogRegistrationPolicy.class.cast(policyClass.getConstructor().newInstance());
+    } catch (ClassNotFoundException
+        | NoSuchMethodException
+        | InstantiationException
+        | IllegalAccessException
+        | InvocationTargetException e) {
+      throw new IllegalArgumentException(
+          "Failed to instantiate catalog registration policy " + 
policyClassName, e);
+    }
+  }
+
+  private static LakehouseRESTCatalogProvider loadProvider(
+      String format, Map<String, String> providerClassNames, ClassLoader 
classLoader) {
+    String providerClassName = providerClassNames.get(format);
+    Preconditions.checkArgument(
+        providerClassName != null,
+        "No lakehouse REST catalog provider found for configured format: %s",
+        format);
+    try {
+      Class<?> providerClass = Class.forName(providerClassName, true, 
classLoader);
+      Preconditions.checkArgument(
+          LakehouseRESTCatalogProvider.class.isAssignableFrom(providerClass),
+          "%s does not implement %s",
+          providerClassName,
+          LakehouseRESTCatalogProvider.class.getName());
+      return providerClass
+          .asSubclass(LakehouseRESTCatalogProvider.class)
+          .getConstructor()
+          .newInstance();
+    } catch (ReflectiveOperationException | LinkageError e) {
+      throw new IllegalArgumentException(
+          String.format(
+              "Failed to instantiate lakehouse REST catalog provider %s for 
format %s",
+              providerClassName, format),
+          e);
+    }
+  }
+
+  private static void validateProviderRuntime(
+      LakehouseRESTCatalogProvider provider, ClassLoader classLoader) {
+    validateRuntimeClass(provider.format(), provider.catalogClassName(), 
classLoader);
+    String[] providerExtensions = provider.sparkExtensions();
+    Preconditions.checkState(
+        providerExtensions != null,
+        "Lakehouse REST catalog provider %s returned null Spark extensions",
+        provider.format());
+    for (String extension : providerExtensions) {
+      validateRuntimeClass(provider.format(), extension, classLoader);
+    }
+  }
+
+  private static void validateRuntimeClass(

Review Comment:
   loadRuntimeClass



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to