/**
 * 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
 * <p/>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p/>
 * 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.superbiz.calculator;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Service;
import junit.framework.TestCase;
import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.transport.http.HTTPConduit;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(Arquillian.class)
public class CalculatorTest {

    @Deployment(testable = false)
    public static Archive<?> app() {
        return ShrinkWrap.create(WebArchive.class, "app.war")
                .addClasses(CalculatorWs.class, CalculatorImpl.class);
    }

    @ArquillianResource
    private URL base;

    /**
     * Create a webservice client using wsdl url
     *
     * @throws Exception
     */
    //START SNIPPET: webservice
    @Test
    public void remoteCallWithSslClient() throws Exception {
        // create the service from the WSDL
        final URL url = new URL(base.toExternalForm() + "webservices/CalculatorImpl?wsdl");
        final QName calcServiceQName = new QName("http://superbiz.org/wsdl", "CalculatorWsService");
        final Service calcService = Service.create(url, calcServiceQName);

        assertNotNull(calcService);

        // get the port for the service
        final CalculatorWs calc = calcService.getPort(CalculatorWs.class);

        // switch the target URL for invocation to HTTPS
        ((BindingProvider) calc).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "https://localhost:8443/app/webservices/CalculatorImpl");

        // add the SSL Client certificate, set the trust store and the hostname verifier
        setupTLS(calc);

        // call the remote JAX-WS webservice
        assertEquals(10, calc.sum(4, 6));
        assertEquals(12, calc.multiply(3, 4));
    }
    //END SNIPPET: webservice


    public static void setupTLS(final Object port) throws GeneralSecurityException, IOException {

        final HTTPConduit httpConduit = (HTTPConduit) ClientProxy.getClient(port).getConduit();

        final TLSClientParameters tlsCP = new TLSClientParameters();
        final String storePassword = "keystorePass";
        final String keyPassword = "clientPassword";
        final KeyStore keyStore = KeyStore.getInstance("jks");
        final String keyStoreLoc = "META-INF/clientStore.jks";
        keyStore.load(Thread.currentThread().getContextClassLoader().getResourceAsStream(keyStoreLoc), storePassword.toCharArray());

        // set the key managers from the Java KeyStore we just loaded
        final KeyManager[] myKeyManagers = getKeyManagers(keyStore, keyPassword);
        tlsCP.setKeyManagers(myKeyManagers);
        tlsCP.setCertAlias("clientalias"); // in case there is multiple certs in the keystore, make sure we pick the one we want

        // Create a trust manager that does not validate certificate chains
        // this should not be done in production. It's recommended to create a cacerts with the certificate chain or
        // to rely on a well known CA such as Verisign which is already available in the JVM
        TrustManager[] trustAllCerts = getTrustManagers();
        tlsCP.setTrustManagers(trustAllCerts);

        // don't check the host name of the certificate to match the server (running locally)
        // this should not be done on a real production system
        tlsCP.setHostnameVerifier((s, sslSession) -> true);

        httpConduit.setTlsClientParameters(tlsCP);
    }

    private static TrustManager[] getTrustManagers() throws NoSuchAlgorithmException, KeyStoreException {
        return new TrustManager[]{
                new X509TrustManager() {
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }
                    public void checkClientTrusted(
                            java.security.cert.X509Certificate[] certs, String authType) {
                    }
                    public void checkServerTrusted(
                            java.security.cert.X509Certificate[] certs, String authType) {
                    }
                }
        };
    }

    private static KeyManager[] getKeyManagers(KeyStore keyStore, String keyPassword) throws GeneralSecurityException, IOException {
        String alg = KeyManagerFactory.getDefaultAlgorithm();
        char[] keyPass = keyPassword != null ? keyPassword.toCharArray() : null;
        KeyManagerFactory fac = KeyManagerFactory.getInstance(alg);
        fac.init(keyStore, keyPass);
        return fac.getKeyManagers();
    }

}
