code: // Licensed to the Software Freedom Conservancy (SFC) under one
  // or more contributor license agreements.  See the NOTICE file
  // distributed with this work for additional information
  // regarding copyright ownership.  The SFC 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.openqa.selenium.testing.drivers;
  
  import static java.util.concurrent.TimeUnit.SECONDS;
  
  import java.lang.reflect.Constructor;
  import java.lang.reflect.InvocationTargetException;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.util.Optional;
  import java.util.function.Supplier;
  import java.util.logging.Logger;
  import org.openqa.selenium.Capabilities;
  import org.openqa.selenium.ImmutableCapabilities;
  import org.openqa.selenium.WebDriver;
  import org.openqa.selenium.net.UrlChecker;
  import org.openqa.selenium.remote.LocalFileDetector;
  import org.openqa.selenium.remote.RemoteWebDriver;
  
  /**
   * Supports providing WebDriver instances from an external source using the following system
   * properties:
   *
   * <dl>
   *   <dt>selenium.external.serverUrl
   *   <dd>Defines the fully qualified URL of an external WebDriver server to send commands to. This
   *       server <i>must</i> be compliant with the <a
   *       href="https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol">JSON wire protocol</a>.
   *       If only this property is provided, then this supplier will provide a new {@link
   *       RemoteWebDriver} instance pointed at the designated server. Otherwise, if a custom supplier
   *       is also defined (see below), this supplier will wait for the server to be accepting
   *       commands before delegating to the designated class for the actual client creation.
   *   <dt>selenium.external.supplierClass
   *   <dd>Specifies the fully qualified name of another class on the classpath. This class must
   *       implement {@code Supplier<WebDriver>} and have a public constructor that accepts two {@link
   *       Capabilities} objects as arguments (for the desired and required capabilities,
   *       respectively).
   * </dl>
   */
  class ExternalDriverSupplier implements Supplier<WebDriver> {
    private static final Logger LOG = Logger.getLogger(ExternalDriverSupplier.class.getName());
  
    private static final String DELEGATE_SUPPLIER_CLASS_PROPERTY = "selenium.external.supplierClass";
    private static final String EXTERNAL_SERVER_URL_PROPERTY = "selenium.external.serverUrl";
  
    private final Capabilities desiredCapabilities;
  
    ExternalDriverSupplier(Capabilities desiredCapabilities) {
      this.desiredCapabilities = new ImmutableCapabilities(desiredCapabilities);
    }
  
    @Override
    public WebDriver get() {
      Optional<Supplier<WebDriver>> delegate = createDelegate(desiredCapabilities);
      delegate = createForExternalServer(desiredCapabilities, delegate);
  
      return delegate.orElse(() -> null).get();
    }
  
    private static Optional<Supplier<WebDriver>> createForExternalServer(
        Capabilities desiredCapabilities, Optional<Supplier<WebDriver>> delegate) {
      String externalUrl = System.getProperty(EXTERNAL_SERVER_URL_PROPERTY);
      if (externalUrl != null) {
        LOG.info("Using external WebDriver server: " + externalUrl);
        URL url;
        try {
          url = new URL(externalUrl);
        } catch (MalformedURLException e) {
          throw new RuntimeException("Invalid server URL: " + externalUrl, e);
        }
        Supplier<WebDriver> defaultSupplier = new DefaultRemoteSupplier(url, desiredCapabilities);
        Supplier<WebDriver> supplier =
            new ExternalServerDriverSupplier(url, delegate.orElse(defaultSupplier));
        return Optional.of(supplier);
      }
      return delegate;
    }
  
    private static Optional<Supplier<WebDriver>> createDelegate(Capabilities desiredCapabilities) {
      Optional<Class<? extends Supplier<WebDriver>>> supplierClass = getDelegateClass();
      if (supplierClass.isPresent()) {
        Class<? extends Supplier<WebDriver>> clazz = supplierClass.get();
        LOG.info("Using delegate supplier: " + clazz.getName());
        try {
          @SuppressWarnings("unchecked")
          Constructor<Supplier<WebDriver>> ctor =
              (Constructor<Supplier<WebDriver>>) clazz.getConstructor(Capabilities.class);
          return Optional.of(ctor.newInstance(desiredCapabilities));
        } catch (InvocationTargetException e) {
          throw new RuntimeException(e.getTargetException());
        } catch (Exception e) {
          throw new RuntimeException(e);
        }
      }
      return Optional.empty();
    }
  
    @SuppressWarnings("unchecked")
    private static Optional<Class<? extends Supplier<WebDriver>>> getDelegateClass() {
      String delegateClassName = System.getProperty(DELEGATE_SUPPLIER_CLASS_PROPERTY);
      if (delegateClassName != null) {
        try {
          LOG.info("Loading custom supplier: " + delegateClassName);
          Class<? extends Supplier<WebDriver>> clazz =
              (Class<? extends Supplier<WebDriver>>) Class.forName(delegateClassName);
          return Optional.of(clazz);
        } catch (Exception e) {
          throw new RuntimeException(e);
        }
      }
      return Optional.empty();
    }
  
    /**
     * Waits for an external WebDriver server to be ready before delegating to another supplier for
     * driver creation.
     */
    private static class ExternalServerDriverSupplier implements Supplier<WebDriver> {
  
      private final URL serverUrl;
      private final Supplier<WebDriver> delegateSupplier;
  
      private ExternalServerDriverSupplier(URL serverUrl, Supplier<WebDriver> delegateSupplier) {
        this.serverUrl = serverUrl;
        this.delegateSupplier = delegateSupplier;
      }
  
      @Override
      public WebDriver get() {
        try {
          LOG.info("Waiting for server to be ready at " + serverUrl);
          new UrlChecker().waitUntilAvailable(60, SECONDS, new URL(serverUrl + "/status"));
          LOG.info("Server is ready");
        } catch (UrlChecker.TimeoutException e) {
          throw new RuntimeException("The external server is not accepting commands", e);
        } catch (MalformedURLException e) {
          throw new RuntimeException(e);
        }
        return delegateSupplier.get();
      }
    }
  
    /** Creates basic {@link RemoteWebDriver} instances. */
    private static class DefaultRemoteSupplier implements Supplier<WebDriver> {
      private final URL url;
      private final Capabilities desiredCapabilities;
  
      private DefaultRemoteSupplier(URL url, Capabilities desiredCapabilities) {
        this.url = url;
        this.desiredCapabilities = desiredCapabilities;
      }
  
      @Override
      public WebDriver get() {
        RemoteWebDriver driver = new RemoteWebDriver(url, desiredCapabilities);
        driver.setFileDetector(new LocalFileDetector());
        return driver;
      }
    }
  }
  