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.grid.node.local;
  
  import com.google.common.collect.ImmutableList;
  import java.io.File;
  import java.time.Duration;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Comparator;
  import java.util.List;
  import java.util.ServiceLoader;
  import org.openqa.selenium.Capabilities;
  import org.openqa.selenium.grid.config.Config;
  import org.openqa.selenium.grid.data.DefaultSlotMatcher;
  import org.openqa.selenium.grid.data.SlotMatcher;
  import org.openqa.selenium.grid.log.LoggingOptions;
  import org.openqa.selenium.grid.node.Node;
  import org.openqa.selenium.grid.node.SessionFactory;
  import org.openqa.selenium.grid.node.config.DriverServiceSessionFactory;
  import org.openqa.selenium.grid.node.config.NodeOptions;
  import org.openqa.selenium.grid.node.docker.DockerOptions;
  import org.openqa.selenium.grid.node.relay.RelayOptions;
  import org.openqa.selenium.grid.security.SecretOptions;
  import org.openqa.selenium.grid.server.BaseServerOptions;
  import org.openqa.selenium.grid.server.EventBusOptions;
  import org.openqa.selenium.grid.server.NetworkOptions;
  import org.openqa.selenium.remote.http.HttpClient;
  import org.openqa.selenium.remote.service.DriverService;
  import org.openqa.selenium.remote.tracing.Tracer;
  
  public class LocalNodeFactory {
  
    public static Node create(Config config) {
      LoggingOptions loggingOptions = new LoggingOptions(config);
      EventBusOptions eventOptions = new EventBusOptions(config);
      BaseServerOptions serverOptions = new BaseServerOptions(config);
      NodeOptions nodeOptions = new NodeOptions(config);
      NetworkOptions networkOptions = new NetworkOptions(config);
      SecretOptions secretOptions = new SecretOptions(config);
  
      Tracer tracer = loggingOptions.getTracer();
      HttpClient.Factory clientFactory = networkOptions.getHttpClientFactory(tracer);
  
      Duration sessionTimeout = nodeOptions.getSessionTimeout();
      LocalNode.Builder builder =
          LocalNode.builder(
                  tracer,
                  eventOptions.getEventBus(),
                  serverOptions.getExternalUri(),
                  nodeOptions.getPublicGridUri().orElseGet(serverOptions::getExternalUri),
                  secretOptions.getRegistrationSecret())
              .maximumConcurrentSessions(nodeOptions.getMaxSessions())
              .sessionTimeout(sessionTimeout)
              .drainAfterSessionCount(nodeOptions.getDrainAfterSessionCount())
              .enableCdp(nodeOptions.isCdpEnabled())
              .enableBiDi(nodeOptions.isBiDiEnabled())
              .enableManagedDownloads(nodeOptions.isManagedDownloadsEnabled())
              .heartbeatPeriod(nodeOptions.getHeartbeatPeriod());
  
      List<DriverService.Builder<?, ?>> builders = new ArrayList<>();
      ServiceLoader.load(DriverService.Builder.class).forEach(builders::add);
  
      nodeOptions
          .getSessionFactories(
              caps -> createSessionFactory(tracer, clientFactory, sessionTimeout, builders, caps))
          .forEach((caps, factories) -> factories.forEach(factory -> builder.add(caps, factory)));
  
      if (config.getAll("docker", "configs").isPresent()) {
        new DockerOptions(config)
            .getDockerSessionFactories(tracer, clientFactory, nodeOptions)
            .forEach((caps, factories) -> factories.forEach(factory -> builder.add(caps, factory)));
      }
  
      if (config.getAll("relay", "configs").isPresent()) {
        new RelayOptions(config)
            .getSessionFactories(tracer, clientFactory, sessionTimeout)
            .forEach((caps, factories) -> factories.forEach(factory -> builder.add(caps, factory)));
      }
  
      return builder.build();
    }
  
    private static Collection<SessionFactory> createSessionFactory(
        Tracer tracer,
        HttpClient.Factory clientFactory,
        Duration sessionTimeout,
        List<DriverService.Builder<?, ?>> builders,
        Capabilities stereotype) {
      ImmutableList.Builder<SessionFactory> toReturn = ImmutableList.builder();
      SlotMatcher slotMatcher = new DefaultSlotMatcher();
      String webDriverExecutablePath =
          String.valueOf(stereotype.asMap().getOrDefault("se:webDriverExecutable", ""));
  
      builders.stream()
          .filter(builder -> builder.score(stereotype) > 0)
          .max(Comparator.comparingInt(builder -> builder.score(stereotype)))
          .ifPresent(
              builder -> {
                DriverService.Builder<?, ?> driverServiceBuilder;
                Class<?> clazz = builder.getClass();
                try {
                  // We do this to give each Node slot its own instance of the DriverService.Builder.
                  // This is important because the Node processes many new session requests
                  // and the DriverService creation needs to be thread safe.
                  Object driverBuilder = clazz.newInstance();
                  driverServiceBuilder =
                      ((DriverService.Builder<?, ?>) driverBuilder).usingAnyFreePort();
                  if (!webDriverExecutablePath.isEmpty()) {
                    driverServiceBuilder =
                        driverServiceBuilder.usingDriverExecutable(new File(webDriverExecutablePath));
                  }
                } catch (InstantiationException | IllegalAccessException e) {
                  throw new IllegalArgumentException(
                      String.format("Class %s could not be found or instantiated", clazz));
                }
                toReturn.add(
                    new DriverServiceSessionFactory(
                        tracer,
                        clientFactory,
                        sessionTimeout,
                        stereotype,
                        capabilities -> slotMatcher.matches(stereotype, capabilities),
                        driverServiceBuilder));
              });
  
      return toReturn.build();
    }
  }
  