content: /*
   * 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.camel.quarkus.component.vertx.websocket;
  
  import java.net.URI;
  import java.util.Optional;
  import java.util.concurrent.ExecutionException;
  
  import io.quarkus.arc.Arc;
  import io.quarkus.runtime.LaunchMode;
  import io.quarkus.runtime.RuntimeValue;
  import io.quarkus.runtime.annotations.Recorder;
  import io.quarkus.tls.TlsConfiguration;
  import io.quarkus.tls.TlsConfigurationRegistry;
  import io.quarkus.vertx.http.runtime.CertificateConfig;
  import io.quarkus.vertx.http.runtime.ServerSslConfig;
  import io.quarkus.vertx.http.runtime.VertxHttpConfig;
  import io.vertx.core.Vertx;
  import io.vertx.core.http.HttpClientOptions;
  import io.vertx.core.http.WebSocketConnectOptions;
  import io.vertx.ext.web.Router;
  import org.apache.camel.CamelContext;
  import org.apache.camel.component.vertx.websocket.VertxWebsocketComponent;
  import org.apache.camel.component.vertx.websocket.VertxWebsocketConfiguration;
  import org.apache.camel.component.vertx.websocket.VertxWebsocketConstants;
  import org.apache.camel.component.vertx.websocket.VertxWebsocketEndpoint;
  import org.apache.camel.component.vertx.websocket.VertxWebsocketHost;
  import org.apache.camel.component.vertx.websocket.VertxWebsocketHostConfiguration;
  import org.apache.camel.component.vertx.websocket.VertxWebsocketHostKey;
  import org.apache.camel.spi.annotations.Component;
  import org.apache.camel.util.ObjectHelper;
  
  @Recorder
  public class VertxWebsocketRecorder {
      private static volatile int PORT;
      private static volatile String HOST;
  
      private final RuntimeValue<VertxHttpConfig> httpConfigRuntimeValue;
  
      public VertxWebsocketRecorder(RuntimeValue<VertxHttpConfig> httpConfigRuntimeValue) {
          this.httpConfigRuntimeValue = httpConfigRuntimeValue;
      }
  
      public RuntimeValue<VertxWebsocketComponent> createVertxWebsocketComponent(
              RuntimeValue<Vertx> vertx,
              RuntimeValue<Router> router,
              LaunchMode launchMode) {
  
          VertxHttpConfig vertxHttpConfig = httpConfigRuntimeValue.getValue();
          boolean sslEnabled = isHttpSeverSecureTransportConfigured(vertxHttpConfig);
          int httpPort = vertxHttpConfig.determinePort(launchMode);
          int httpsPort = vertxHttpConfig.determineSslPort(launchMode);
  
          HOST = vertxHttpConfig.host();
          PORT = sslEnabled ? httpsPort : httpPort;
  
          QuarkusVertxWebsocketComponent component = new QuarkusVertxWebsocketComponent();
          component.setVertx(vertx.getValue());
          component.setRouter(router.getValue());
          component.setDefaultHost(HOST);
          component.setDefaultPort(PORT);
          return new RuntimeValue<>(component);
      }
  
      private boolean isHttpSeverSecureTransportConfigured(VertxHttpConfig httpConfig) {
          return httpServerTlsRegistryConfigurationExists(httpConfig) || httpServerLegacySslConfigurationExists(httpConfig);
      }
  
      private boolean httpServerTlsRegistryConfigurationExists(VertxHttpConfig httpConfig) {
          if (Arc.container() != null) {
              TlsConfigurationRegistry tlsConfigurationRegistry = Arc.container().select(TlsConfigurationRegistry.class).orNull();
              if (tlsConfigurationRegistry != null) {
                  Optional<String> tlsConfigurationName = httpConfig.tlsConfigurationName();
                  Optional<TlsConfiguration> defaultTlsConfiguration = tlsConfigurationRegistry.getDefault();
                  if (tlsConfigurationName.isPresent() && tlsConfigurationRegistry.get(tlsConfigurationName.get()).isPresent()) {
                      return true;
                  } else {
                      return defaultTlsConfiguration.isPresent() && defaultTlsConfiguration.get().getKeyStoreOptions() != null;
                  }
              }
          }
          return false;
      }
  
      private boolean httpServerLegacySslConfigurationExists(VertxHttpConfig httpConfig) {
          ServerSslConfig ssl = httpConfig.ssl();
          if (ssl != null) {
              CertificateConfig certificate = ssl.certificate();
              if (certificate != null) {
                  if (certificate.files().isPresent() && certificate.keyFiles().isPresent()) {
                      return true;
                  }
                  return certificate.keyStoreFile().isPresent() && certificate.keyStorePassword().isPresent();
              }
          }
          return false;
      }
  
      @Component("vertx-websocket")
      static final class QuarkusVertxWebsocketComponent extends VertxWebsocketComponent {
          @Override
          protected VertxWebsocketHost createVertxWebsocketHost(VertxWebsocketHostConfiguration hostConfiguration,
                  VertxWebsocketHostKey hostKey) {
              // If a host / port was specified on the consumer, it must be the same as what the Quarkus HTTP server is bound to
              if (!hostKey.getHost().equals(HOST) || hostKey.getPort() != PORT) {
                  String message = String.format(
                          "Invalid host/port %s:%d. The host/port can only be configured as %s:%d",
                          hostKey.getHost(), hostKey.getPort(), HOST, PORT);
                  throw new IllegalArgumentException(message);
              }
              return new QuarkusVertxWebsocketHost(getCamelContext(), hostConfiguration, hostKey);
          }
  
          @Override
          protected VertxWebsocketEndpoint createEndpointInstance(String uri, VertxWebsocketConfiguration configuration) {
              return new QuarkusVertxWebsocketEndpoint(uri, this, configuration);
          }
      }
  
      public static final class QuarkusVertxWebsocketEndpoint extends VertxWebsocketEndpoint {
          public QuarkusVertxWebsocketEndpoint(String uri, VertxWebsocketComponent component,
                  VertxWebsocketConfiguration configuration) {
              super(uri, component, configuration);
          }
  
          @Override
          public WebSocketConnectOptions getWebSocketConnectOptions(HttpClientOptions options) {
              WebSocketConnectOptions connectOptions = super.getWebSocketConnectOptions(options);
              URI uri = URI.create(getEndpointUri().replaceFirst("wss?:/*", ""));
              if (ObjectHelper.isNotEmpty(uri.getHost()) && uri.getPort() == -1) {
                  connectOptions.setPort(connectOptions.isSsl() ? VertxWebsocketConstants.DEFAULT_VERTX_CLIENT_WSS_PORT
                          : VertxWebsocketConstants.DEFAULT_VERTX_CLIENT_WS_PORT);
              }
              return connectOptions;
          }
      }
  
      static final class QuarkusVertxWebsocketHost extends VertxWebsocketHost {
          public QuarkusVertxWebsocketHost(CamelContext camelContext, VertxWebsocketHostConfiguration websocketHostConfiguration,
                  VertxWebsocketHostKey key) {
              super(camelContext, websocketHostConfiguration, key);
          }
  
          @Override
          public void start() throws InterruptedException, ExecutionException {
              // Noop as quarkus-vertx-http handles the server lifecycle
          }
  
          @Override
          public int getPort() {
              return PORT;
          }
  
          @Override
          public boolean isManagedHost(String host) {
              return HOST.equals(host);
          }
  
          @Override
          public boolean isManagedPort(int port) {
              return port == getPort();
          }
      }
  }
  