/* **********************************************************
 * Copyright (c) 2019 VMware, Inc.  All rights reserved.
 * -- VMware Confidential
 * **********************************************************/

package com.vmware.samples.connection;

import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.Map;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.xml.ws.BindingProvider;

import com.vmware.drconfig.DrConfigFaultInvalidLocaleFaultMsg;
import com.vmware.drconfig.DrConfigFaultInvalidLoginFaultMsg;
import com.vmware.drconfig.RuntimeFaultFaultMsg;
import com.vmware.drconfig.DrConfigFaultNoPermissionFaultMsg;
import com.vmware.drconfig.InvalidArgumentFaultMsg;
import com.vmware.drconfig.DrConfigPortType;
import com.vmware.drconfig.DrConfigService;
import com.vmware.drconfig.DrConfigServiceInstanceContent;

import com.vmware.vim25.ManagedObjectReference;


/**
 * This is a simple interface for connecting to DrConfig API.  It is not specific
 * to any particular sample application.
 */
public final class DrConfigConnectionUtils {

   /**
    * The reference to the managed object that contains DrConfig service
    * information.  This object is a singleton on the SRM server, and its
    * reference information never changes.  Thus, it is safe to use this
    * reference statically, and without locking.
    */
   public static final ManagedObjectReference _serviceInstanceRef =
      DrConfigConnectionUtils.createServiceInstanceRef();

   /**
    * Retrieve the DrConfig service instance content.  With this content, you can
    * access the more specific sub-components of the DrConfig API (e.g., the configuration
    * API).
    *
    * @param drConfigPort The open connection to the DrConfig API.
    * @return DrConfig's service-instance content object.
    * @throws ApplicationException If the content could not be retrieved.
    */
   public static DrConfigServiceInstanceContent getDrConfigServiceInstanceContent(
      final DrConfigPortType drConfigPort)
      throws ApplicationException {

      try {
         return drConfigPort.retrieveContent(DrConfigConnectionUtils._serviceInstanceRef);
      } catch (final RuntimeFaultFaultMsg e) {
         throw new ApplicationException(
            "Cannot retrieve DrConfig service content", e);
      }
   }

   /**
    * (Application global) disabling of the SSL certificate verification.
    *
    * @return Successful execution returns {@code true}; otherwise
    *    {@code false}.
    */
   public static boolean ignoreSslCert() {

      final SSLContext sslContext;
      try {
         sslContext = SSLContext.getInstance("SSL");
         sslContext.getServerSessionContext().setSessionTimeout(0);
         sslContext.init(
            null, // KeyManager
            new TrustManager[] { new TrustAllTrustManager() },
            null // SecureRandom
            );
      } catch (final KeyManagementException kme) {
         System.err.println(
            "Warning: cannot ignore SSL certificates -- " + kme.getMessage());
         return false;
      } catch (final NoSuchAlgorithmException nsae) {
         System.err.println(
            "Warning: cannot ignore SSL certificates -- " + nsae.getMessage());
         return false;
      }

      HttpsURLConnection.setDefaultSSLSocketFactory(
         sslContext.getSocketFactory());

      HttpsURLConnection.setDefaultHostnameVerifier(
         new HostnameVerifier() {
            public boolean verify(
               final String hostname, final SSLSession session) {
               return true;
            }
         });

      return true;
   }

   /**
    * Open a connection to the DrConfig API and log in with the provided credentials.
    *
    * @param url      The URL to the DrConfig API's end-point.
    * @param userName An DrConfig user with administrator privileges to execute the API.
    * @param password The password for the provided user.
    *
    * @return A connected port to DrConfig through which the API can be used.
    * @throws ApplicationException If there was an error logging in or using the
    *    DrConfig API port in general.
    */
   public static DrConfigPortType openDrConfigPort(
      final URL url, final String userName, final String password)
      throws ApplicationException {

      final DrConfigService  drConfig = new DrConfigService();
      final DrConfigPortType drConfigPort = drConfig.getDrConfigPort();

      final Map<String, Object> ctxt =
         ((BindingProvider)drConfigPort).getRequestContext();

      ctxt.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, url.toString());
      ctxt.put(BindingProvider.SESSION_MAINTAIN_PROPERTY, true);

      try {
        drConfigPort.loginDrConfig(_serviceInstanceRef, userName, password, null);
      } catch (final DrConfigFaultNoPermissionFaultMsg e) {
         throw new ApplicationException("No permissions to log into Appliance", e);
      } catch (final RuntimeFaultFaultMsg e) {
         throw new ApplicationException("Cannot log into Appliance", e);
      } catch (final DrConfigFaultInvalidLocaleFaultMsg e) {
         throw new ApplicationException(
            "Cannot log into Appliance -- invalid locale", e);
      } catch (final DrConfigFaultInvalidLoginFaultMsg e) {
         throw new ApplicationException(
            "Cannot log into Appliance -- invalid login", e);
      } catch (final InvalidArgumentFaultMsg e) {
        throw new ApplicationException(
              "Cannot log into Appliance -- invalid argument", e);
      }

      return drConfigPort;
   }

   /**
    * Create an SSL trust manager that simply allows all connections.
    */
   private static final class TrustAllTrustManager
      implements javax.net.ssl.X509TrustManager {

      /**
       * Stub method.
       * @return This always returns {@code null}.
       */
      public X509Certificate[] getAcceptedIssuers() { return null; }

      /**
       * Stub method.  This does nothing.
       */
      public void checkServerTrusted(
         X509Certificate[] certs, String authType)
         throws java.security.cert.CertificateException { }

      /**
       * Stub method.  This does nothing.
       */
      public void checkClientTrusted(
         X509Certificate[] certs, String authType)
         throws java.security.cert.CertificateException { }
   }

   /**
    * This class defines a library of functions.  So far there is no need for an
    * instance, so we will hide the CTOR.
    */
   private DrConfigConnectionUtils() {
      // Does nothing.
   }

   /**
    * Create a managed-object reference referring to the ServiceInstanceContent
    * (singleton) for DrConfigurator.
    *
    * @return A newly allocated reference.  This will not return {@code null}.
    */
   private static ManagedObjectReference createServiceInstanceRef() {

      final ManagedObjectReference configRef = new ManagedObjectReference();
      configRef.setType("DrConfigServiceInstance");
      configRef.setValue("ServiceInstance");

      return configRef;
   }
}

