/* ****************************************************************************
 * Copyright 2013-2014 VMware, Inc.  All rights reserved.
 * ***************************************************************************/

package com.vmware.samples.srm;

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.srm.InvalidLocaleFaultMsg;
import com.vmware.srm.InvalidLoginFaultMsg;
import com.vmware.srm.RuntimeFaultFaultMsg;
import com.vmware.srm.SrmFaultAlreadyLoggedInFaultFaultMsg;
import com.vmware.srm.SrmFaultConnectionLimitReachedFaultMsg;
import com.vmware.srm.SrmFaultNoPermissionFaultMsg;
import com.vmware.srm.SrmPortType;
import com.vmware.srm.SrmService;
import com.vmware.srm.SrmServiceInstanceContent;

import com.vmware.vim25.ManagedObjectReference;


/**
 * This is a simple interface for connecting to SRM's API.  It is not specific
 * to any particular sample application.
 */
public final class SrmConnectionUtil {

   /**
    * The reference to the managed object that contains SRM's 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 =
      SrmConnectionUtil.createServiceInstanceRef();

   /**
    * Retrieve the SRM service instance content.  With this content, you can
    * access the more specific sub-components of the SRM API (e.g., the recovery
    * API).
    *
    * @param srmPort The open connection to the SRM API.
    * @return SRM's service-instance content object.
    * @throws ApplicationException If the content could not be retrieved.
    */
   public static SrmServiceInstanceContent findSrmServiceInstanceContent(
      final SrmPortType srmPort)
      throws ApplicationException {

      try {
         return srmPort.retrieveContent(SrmConnectionUtil._serviceInstanceRef);
      } catch (final RuntimeFaultFaultMsg e) {
         throw new ApplicationException(
            "Cannot retrieve SRM 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() {
            @Override public boolean verify(
               final String hostname, final SSLSession session) {
               return true;
            }
         });

      return true;
   }

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

      final SrmService  srmService = new SrmService();
      final SrmPortType srmPort    = srmService.getSrmPort();

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

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

      try {
         srmPort.srmLoginLocale(
            SrmConnectionUtil._serviceInstanceRef, userName, password, null);
      } catch (final SrmFaultAlreadyLoggedInFaultFaultMsg ali) {
         // If we're already logged in, that is OK.  Continue on.
      } catch (final SrmFaultNoPermissionFaultMsg e) {
         throw new ApplicationException("No permissions to log into SRM", e);
      } catch (final RuntimeFaultFaultMsg e) {
         throw new ApplicationException("Cannot log into SRM", e);
      } catch (final InvalidLocaleFaultMsg e) {
         throw new ApplicationException(
            "Cannot log into SRM -- invalid locale", e);
      } catch (final InvalidLoginFaultMsg e) {
         throw new ApplicationException(
            "Cannot log into SRM -- invalid login", e);
      } catch (final SrmFaultConnectionLimitReachedFaultMsg e) {
         throw new ApplicationException(
            "Cannot log into SRM -- connection limit reached", e);
      }

      return srmPort;
   }

   /**
    * 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}.
       */
      @Override public X509Certificate[] getAcceptedIssuers() { return null; }

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

      /**
       * Stub method.  This does nothing.
       */
      @Override 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 SrmConnectionUtil() {
      // Does nothing.
   }

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

      final ManagedObjectReference svcRef = new ManagedObjectReference();
      svcRef.setType("SrmServiceInstance");
      svcRef.setValue("SrmServiceInstance");

      return svcRef;
   }
}
