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

package com.vmware.samples.connection;

import java.rmi.RemoteException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;

import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.axis2.transport.http.impl.httpclient4.HttpTransportPropertiesImpl;
import org.apache.axis2.transport.http.security.SSLProtocolSocketFactory;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;

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.LoginDrConfig;
import com.vmware.drconfig.LoginDrConfigRequestType;
import com.vmware.drconfig.LogoutDrConfig;
import com.vmware.drconfig.LogoutDrConfigRequestType;
import com.vmware.drconfig.RetrieveContent;
import com.vmware.drconfig.RetrieveContentRequestType;
import com.vmware.drconfig.DrConfigServiceStub;
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 class DrConfigConnectionUtils {


   private static final int SOCKET_TIMEOUT = 60000;
   private static final int SO_TIMEOUT = 300000;
   private static final int MAX_TOTAL_CONNECTIONS = 20;
   private static final String SSL_CONTEXT = "TLS";
   private final String PROTOCOL = "https";
   private final int PORT = 443;

   private final DrConfigServiceStub _configServiceStub;
   private final ManagedObjectReference _serviceInstanceRef;
   private DrConfigServiceInstanceContent _configInstanceContent;
   private boolean _loginSuccess = false;

   /**
    * Setup the Connection to the DrConfigurator
    *
    * @param url Url of the Virtual Appliance
    * @param vaUserName Virtual Appliance user name
    * @param vaPassword Virtual Appliance Password
    */
   public DrConfigConnectionUtils(String url, String username, String password, boolean ignoreCertificate)
         throws RemoteException,
                ApplicationException,
                DrConfigFaultInvalidLocaleFaultMsg,
                DrConfigFaultInvalidLoginFaultMsg,
                DrConfigFaultNoPermissionFaultMsg,
                InvalidArgumentFaultMsg,
                RuntimeFaultFaultMsg {
      this._configInstanceContent = new DrConfigServiceInstanceContent();
   _configServiceStub = new DrConfigServiceStub(url);

      // Setup Axis2 and HttpClient option
      // These options are needed because of the session that Axis2 keeps
      // for the "client", if we dont tell Axis2 to re-use the same session
      // it will create a new one for every invocation of a function on the server, which will result in
      // Not Authenticated exception.
      MultiThreadedHttpConnectionManager httpConnectionManager = new MultiThreadedHttpConnectionManager();
      HttpConnectionManagerParams params = httpConnectionManager.getParams();
      if (params == null) {
         params = new HttpConnectionManagerParams();
         params.setDefaultMaxConnectionsPerHost(MAX_TOTAL_CONNECTIONS);
         params.setMaxTotalConnections(MAX_TOTAL_CONNECTIONS);
         params.setSoTimeout(SO_TIMEOUT);
         params.setConnectionTimeout(SO_TIMEOUT);
         httpConnectionManager.setParams(params);
      }
      HttpClient httpClient = new HttpClient(httpConnectionManager);

      Options opts = _configServiceStub._getServiceClient().getOptions();
      opts.setProperty(HTTPConstants.CHUNKED, Boolean.FALSE);
      opts.setProperty(ServiceClient.AUTO_OPERATION_CLEANUP, Boolean.FALSE);
      opts.setProperty(HTTPConstants.SO_TIMEOUT, new Integer(SOCKET_TIMEOUT));
      opts.setProperty(HTTPConstants.CONNECTION_TIMEOUT, new Integer(
           SOCKET_TIMEOUT - 1000));
      opts.setManageSession(true);
      opts.setCallTransportCleanup(true);
      opts.setProperty(HTTPConstants.REUSE_HTTP_CLIENT, true);
      opts.setProperty(HTTPConstants.CACHED_HTTP_CLIENT, httpClient);

      HttpTransportPropertiesImpl.Authenticator basicAuth = new HttpTransportPropertiesImpl.Authenticator();
      basicAuth.setUsername(username);
      basicAuth.setPassword(password);
      basicAuth.setPreemptiveAuthentication(true);

      opts.setProperty(HTTPConstants.AUTHENTICATE, basicAuth);

      if(ignoreCertificate) {
        SSLContext sslCtx = ignoreCertificates();
        opts.setProperty(
              HTTPConstants.CUSTOM_PROTOCOL_HANDLER,
              new Protocol(PROTOCOL, (ProtocolSocketFactory) new SSLProtocolSocketFactory(sslCtx), PORT));
      }

      _configServiceStub._getServiceClient().setOptions(opts);

      _serviceInstanceRef = createServiceInstanceRef();
      login(username, password);
      findDrConfigServiceInstanceContent();
   }

   /**
    * 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).
    *
    * @throws ApplicationException If the content could not be retrieved.
    * @throws RemoteException If there is a problem connecting to the DrConfig
    */
   private void findDrConfigServiceInstanceContent()
      throws ApplicationException, RemoteException {

      RetrieveContent retrieveContent =
            new RetrieveContent();

      RetrieveContentRequestType requestType =
           new RetrieveContentRequestType();
      requestType.set_this(_serviceInstanceRef);

      retrieveContent.setRetrieveContent(requestType);
      try {
         _configInstanceContent =
            _configServiceStub.retrieveContent(retrieveContent).getReturnval();
      } catch (final RuntimeFaultFaultMsg e) {
         throw new ApplicationException(
            "Cannot retrieve DrConfig service content", e);
      }
   }

   /**
    * Login into the Virtual Appliance
    */
   private void login(String username, String password)
         throws RemoteException,
                DrConfigFaultInvalidLocaleFaultMsg,
                DrConfigFaultInvalidLoginFaultMsg,
                DrConfigFaultNoPermissionFaultMsg,
                InvalidArgumentFaultMsg,
                RuntimeFaultFaultMsg {
      LoginDrConfig loginDrConfig =
            new LoginDrConfig();
      LoginDrConfigRequestType loginRequest =
            new LoginDrConfigRequestType();
      loginRequest.set_this(_serviceInstanceRef);
      loginRequest.setUserName(username);
      loginRequest.setPassword(password);
      loginRequest.setLocale(null);
      loginDrConfig.setLoginDrConfig(loginRequest);
      _configServiceStub.loginDrConfig(loginDrConfig);

      _loginSuccess = true;
   }

   /**
    * Logout of the Virtual Appliance
    */
   public void logout()
      throws RemoteException, RuntimeFaultFaultMsg {
      LogoutDrConfig logoutDrConfig =
            new LogoutDrConfig();
      LogoutDrConfigRequestType logoutRequest =
            new LogoutDrConfigRequestType();
      logoutRequest.set_this(_serviceInstanceRef);
      logoutDrConfig.setLogoutDrConfig(logoutRequest);
      _configServiceStub.logoutDrConfig(logoutDrConfig);
   }

   public DrConfigServiceInstanceContent getDrConfigServiceInstanceContent() {
      return _configInstanceContent;
   }

   public DrConfigServiceStub getDrConfigServiceStub() {
      return _configServiceStub;
   }

   public ManagedObjectReference getServiceInstanceRef() {
      return _serviceInstanceRef;
   }

   public boolean isLoginSuccessful() {
      return _loginSuccess;
   }

   /**
    * (Application global) disabling of the SSL certificate verification.
    *
    * @return SSLContext Property containing the necessary information about
    * ignoring the certificate validation
    */
   private static SSLContext ignoreCertificates()
   {
      SSLContext sslCtx = null;
     try {
        sslCtx = SSLContext.getInstance(SSL_CONTEXT);
        sslCtx.init(null, new TrustManager[] { new TrustAllTrustManager()}, null);
     } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
     } catch (KeyManagementException e) {
        e.printStackTrace();
     }
      return sslCtx;
   }

   /**
    * 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 { }
   }

   /**
    * Create a managed-object reference referring to the DrConfigServiceInstanceContent
    * 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.setString("ServiceInstance");

      return configRef;
   }
}

