package org.maachang.comet.net ;

import java.io.FileInputStream;
import java.net.ServerSocket;
import java.security.KeyStore;
import java.security.SecureRandom;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.TrustManagerFactory;

/**
 * SSLサーバファクトリ.
 *  
 * @version 2007/11/17
 * @author  masahito suzuki
 * @since   MaachangComet 1.00
 */
public class SslServerFactory {
    
    /**
     * SSLサーバソケットファクトリ.
     */
    private SSLServerSocketFactory sslServerSocketFactory = null ;
    
    /**
     * SSLオプション.
     */
    private SslOption sslOption = null ;
    
    /**
     * コンストラクタ.
     */
    private SslServerFactory() {
        
    }
    
    /**
     * コンストラクタ.
     * <BR><BR>
     * SSLサーバソケット初期化処理を行うオブジェクトです.
     * <BR>
     * @param option SSLソケットオプションを設定します.
     * @exception Exception 例外.
     */
    public SslServerFactory( SslOption option )
        throws Exception {
        if( option == null ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        // 認証ファクトリを設定.
        KeyManagerFactory keyFactory = null ;
        
        if( option.getKeyStoreFile() != null &&
            option.getKeyStore() != null &&
            option.getKeyManagerAlgorithm() != null ) {
            
            char[] keyStorePasswd = null ;
            if( option.getKeyStorePasswd() != null &&
                option.getKeyStorePasswd().length() > 0 ) {
                keyStorePasswd = option.getKeyStorePasswd().toCharArray() ;
            }
            
            KeyStore keyStore = KeyStore.getInstance( option.getKeyStore() ) ;
            keyStore.load( new FileInputStream( option.getKeyStoreFile() ),keyStorePasswd ) ;
            keyFactory = KeyManagerFactory.getInstance( option.getKeyManagerAlgorithm() ) ;
            keyFactory.init( keyStore,keyStorePasswd ) ;
            
        }
        
        // ピア認証信頼を判断するファクトリを設定.
        TrustManagerFactory trustFactory = null ;
        if( option.getTrustFile() != null &&
            option.getTrustStore() != null &&
            option.getTrustKeyManagerAlgorithm() != null ) {
            
            char[] trustPasswd = null ;
            if( option.getTrustPassword() != null &&
                option.getTrustPassword().length() > 0 ) {
                trustPasswd = option.getTrustPassword().toCharArray() ;
            }
            
            KeyStore trust = KeyStore.getInstance( option.getTrustStore() ) ;
            trust.load( new FileInputStream( option.getTrustFile() ),trustPasswd ) ;
            trustFactory = TrustManagerFactory.getInstance( option.getTrustKeyManagerAlgorithm() ) ;
            trustFactory.init( trust ) ;
            
        }
        
        // 乱数アルゴリズム(PRNG)を設定.
        SecureRandom prng = null ;
        if( option.getRandomAlgorithm() != null ) {
            prng = SecureRandom.getInstance( option.getRandomAlgorithm() ) ;
        }
        
        // SSLコンテキストを設定.
        SSLContext context = SSLContext.getInstance( option.getSslProtocol() ) ;
        context.init( keyFactory.getKeyManagers(),trustFactory.getTrustManagers(),prng ) ;
        
        // サーバソケットファクトリを生成.
        this.sslServerSocketFactory = context.getServerSocketFactory() ;
        this.sslOption = option ;
    }
    
    /**
     * デストラクタ.
     */
    protected void finalize() throws Exception {
        this.sslServerSocketFactory = null ;
        this.sslOption = null ;
    }
    
    /**
     * バインドされていないサーバソケットを取得.
     * <BR><BR>
     * バインドされていないサーバソケットを取得します.
     * <BR>
     * @return ServerSocket バインドされていないサーバソケットが返されます.
     */
    public ServerSocket serverSocket()
        throws Exception {
        ServerSocket ret = this.sslServerSocketFactory.createServerSocket() ;
        if( sslOption.isClientAuth() == true ){
            ( ( SSLServerSocket )ret ).setNeedClientAuth( true ) ;
        }
        return ret ;
    }
    
    /**
     * SSLオプションを取得.
     * <BR><BR>
     * SSLオプションを取得します.
     * <BR>
     * @return SslOption SSLオプションが返されます.
     */
    public SslOption getSslOption() {
        return this.sslOption ;
    }
}

