/* Copyright (c) 2001-2018, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb.server;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Base class for producing the Socket objects used by HSQLDB.
 *
 * @author Blaine Simpson (blaine dot simpson at admc dot com)
 * @author Campbell Burnet (campbell-burnet@users dot sourceforge.net)
 * @version 2.3.0
 * @since 1.7.2
 */
public class HsqlSocketFactory {

// ----------------------------- static members ---------------------------------
    private static HsqlSocketFactory plainImpl;
    private static HsqlSocketFactory sslImpl;

// ------------------------------ constructors ---------------------------------

    /**
     * External construction disabled.  New factory instances are retrieved
     * through the newHsqlSocketFactory method instead.
     */
    protected HsqlSocketFactory() throws Exception {}

// ------------------------- factory builder method ----------------------------

    /**
     * Retrieves an HsqlSocketFactory whose subclass and attributes are
     * determined by the specified argument, tls.
     *
     * @param tls whether to retrieve a factory producing SSL sockets
     * @throws Exception if the new factory cannot be constructed or is
     *      of the wrong type
     * @return a new factory
     */
    public static HsqlSocketFactory getInstance(boolean tls) throws Exception {
        return tls ? getSSLImpl()
                   : getPlainImpl();
    }

// -------------------------- public instance methods --------------------------
    public void configureSocket(Socket socket) {

        // default: do nothing
    }

    /**
     * Returns a server socket bound to the specified port.
     * The socket is configured with the socket options
     * given to this factory.
     *
     * @return the ServerSocket
     * @param port the port to which to bind the ServerSocket
     * @throws Exception if a network error occurs
     */
    public ServerSocket createServerSocket(int port) throws Exception {
        return new ServerSocket(port);
    }

    /**
     * Returns a server socket bound to the specified port.
     * The socket is configured with the socket options
     * given to this factory.
     *
     * @return the ServerSocket
     * @param port the port to which to bind the ServerSocket
     * @throws Exception if a network error occurs
     */
    public ServerSocket createServerSocket(int port,
                                           String address) throws Exception {
        return new ServerSocket(port, 128, InetAddress.getByName(address));
    }

    /**
     * If socket argument is not null, returns it. If it is null,
     * creates a socket and connects it to the specified remote host at the
     * specified remote port. This socket is configured using the socket options
     * established for this factory.
     *
     * @return the socket
     * @param socket the existing socket, can be null
     * @param host the server host
     * @param port the server port
     * @throws Exception if a network error occurs
     */
    public Socket createSocket(Socket socket, String host,
                               int port) throws Exception {
        return socket == null ? new Socket(host, port)
                              : socket;
    }

    /**
     * Creates a socket and connects it to the specified remote host at the
     * specified remote port. This socket is configured using the socket options
     * established for this factory.
     *
     * @return the socket
     * @param host the server host
     * @param port the server port
     * @throws Exception if a network error occurs
     */
    public Socket createSocket(String host, int port) throws Exception {
        return new Socket(host, port);
    }

    /**
     * Retrieves whether this factory produces secure sockets.
     *
     * @return true if this factory produces secure sockets
     */
    public boolean isSecure() {
        return false;
    }

// ------------------------ static utility methods -----------------------------
    private static HsqlSocketFactory getPlainImpl() throws Exception {

        synchronized (HsqlSocketFactory.class) {
            if (plainImpl == null) {
                plainImpl = new HsqlSocketFactory();
            }
        }

        return plainImpl;
    }

    private static HsqlSocketFactory getSSLImpl() throws Exception {

        synchronized (HsqlSocketFactory.class) {
            if (sslImpl == null) {
                sslImpl =
                    newFactory("org.hsqldb.server.HsqlSocketFactorySecure");
            }
        }

        return sslImpl;
    }

    /**
     * Retrieves a new HsqlSocketFactory whose class
     * is determined by the implClass argument. The basic contract here
     * is that implementations constructed by this method should return
     * true upon calling isSecure() iff they actually create secure sockets.
     * There is no way to guarantee this directly here, so it is simply
     * trusted that an  implementation is secure if it returns true
     * for calls to isSecure();
     *
     * @return a new secure socket factory
     * @param implClass the fully qualified name of the desired
     *      class to construct
     * @throws Exception if a new secure socket factory cannot
     *      be constructed
     */
    private static HsqlSocketFactory newFactory(String implClass)
    throws Exception {

        Class       clazz;
        Constructor ctor;
        Class[]     ctorParm;
        Object[]    ctorArg;
        Object      factory;

        clazz    = Class.forName(implClass);
        ctorParm = new Class[0];

        // protected constructor
        ctor    = clazz.getDeclaredConstructor(ctorParm);
        ctorArg = new Object[0];

        try {
            factory = ctor.newInstance(ctorArg);
        } catch (InvocationTargetException e) {
            Throwable t = e.getTargetException();

            throw (t instanceof Exception) ? ((Exception) t)
                                           : new RuntimeException(
                                               t.toString());
        }

        return (HsqlSocketFactory) factory;
    }

// --
}
