/*
 * Copyright (c) 2002-2010 JSCAPE
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JSCAPE. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 *  the terms of the license agreement you entered into with JSCAPE.
 */

package com.jscape.util.net.socket;

import com.jscape.util.net.connection.Connection;
import com.jscape.util.net.connection.ConnectionProtector;
import com.jscape.util.reflection.MethodInvoker;

import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

import static com.jscape.util.Parameters.assertNotNull;

/**
 * @author Alex Usun
 */
public class SslSocketConnectionProtector
        implements ConnectionProtector {

    private final SSLSocketFactory sslSocketFactory;
    private final String[] protocols;
    private final String[] cipherSuites;
    private final ClientAuthenticationNecessity clientAuthenticationNecessity;
    private final boolean useClientMode;
    private final boolean shutdownSsl;
    private final HandshakeCompletedListener handshakeCompletedListener;

    public SslSocketConnectionProtector(SSLSocketFactory sslSocketFactory,
                                        String[] protocols,
                                        String[] cipherSuites,
                                        ClientAuthenticationNecessity clientAuthenticationNecessity,
                                        boolean useClientMode,
                                        boolean shutdownSsl,
                                        HandshakeCompletedListener handshakeCompletedListener) {
        assertNotNull(sslSocketFactory);
        this.sslSocketFactory = sslSocketFactory;

        this.protocols = protocols;
        this.cipherSuites = cipherSuites;

        assertNotNull(clientAuthenticationNecessity);
        this.clientAuthenticationNecessity = clientAuthenticationNecessity;

        this.useClientMode = useClientMode;
        this.shutdownSsl = shutdownSsl;
        this.handshakeCompletedListener = handshakeCompletedListener;
    }

    public void protect(Connection connection)
            throws OperationException {
        try {
            SocketConnection socketConnection = (SocketConnection) connection;
            SSLSocket sslSocket = socketConnection.protect(this.sslSocketFactory);
            setupSocket(sslSocket);
            sslSocket.startHandshake();
        } catch (Exception e) {
            throw new OperationException(e);
        }
    }

    public void unprotect(Connection connection) {
        try {
            SocketConnection socketConnection = (SocketConnection) connection;
            socketConnection.unprotect(this.shutdownSsl);
        } catch (Exception e) {
            // ignore
        }
    }

    private void setupSocket(SSLSocket socket) {
        if (this.protocols != null && this.protocols.length > 0) {
            socket.setEnabledProtocols(this.protocols);
        }

        if (this.cipherSuites != null && this.cipherSuites.length > 0) {
            socket.setEnabledCipherSuites(this.cipherSuites);
            applyPreferredCipherSuites(socket);
        }

        this.clientAuthenticationNecessity.applyTo(socket);

        socket.setUseClientMode(this.useClientMode);

        if (this.handshakeCompletedListener != null) {
            socket.addHandshakeCompletedListener(this.handshakeCompletedListener);
        }
    }

    private void applyPreferredCipherSuites(SSLSocket socket) {
        SSLParameters sslParameters = socket.getSSLParameters();

        try {
            MethodInvoker.setProperty(sslParameters, "UseCipherSuitesOrder", Boolean.TRUE);
            socket.setSSLParameters(sslParameters);
        } catch (Exception e) {
            // ignore
        }
    }

}
