

package gov.va.med.cds.socket.server;


import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.net.InetSocketAddress;
import java.rmi.UnmarshalException;

import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.xml.XmlConfiguration;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.filter.logging.LogLevel;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.integration.jmx.IoServiceMBean;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

import gov.va.med.cds.properties.PropertiesUtil;


public class MinaServerManager
{

    private static Log LOGGER = LogFactory.getLog( MinaServerManager.class );
   
    private LogLevel defaultLogLevel;

    /** The application context used by this application. */
    private static ApplicationContext applicationContext = null;

    private NioSocketAcceptor minaServerAcceptor;

    private InetSocketAddress socketAddress;

    private IoServiceMBean acceptorMBean;

    public String port;

    MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();

    
    public void setDefaultLogLevel( LogLevel defaultLogLevel )
    {
        this.defaultLogLevel = defaultLogLevel;
    }


    public void setAcceptorMBean( IoServiceMBean acceptorMBean )
    {
        this.acceptorMBean = acceptorMBean;
    }


    public void setSocketAddress( InetSocketAddress socketAddress )
    {
        this.socketAddress = socketAddress;
    }


    public void setMinaServer( NioSocketAcceptor minaServer )
    {
        this.minaServerAcceptor = minaServer;
    }


    private MinaServerManager( )
    {
        //No Operation
    }


    public void setPort( String port )
    {
        this.port = port;
    }


    public String getPort( )
    {
        return port;
    }


    public void init( )
    {
        
    	minaServerAcceptor.getSessionConfig().setReadBufferSize( 65536 );

        minaServerAcceptor.getSessionConfig().setIdleTime( IdleStatus.READER_IDLE, 5 );

        minaServerAcceptor.getSessionConfig().setKeepAlive( true );

        try
        {
            if ( ( null == port ) || ( port.isEmpty() ) )
            {
                minaServerAcceptor.bind( socketAddress );

                LOGGER.info( "Started listening on port : " + socketAddress.getPort() );
            }
            else
            {
                minaServerAcceptor.bind( new InetSocketAddress( Integer.parseInt( port ) ) );

                LOGGER.info( "Started listening on port" + port );
            }

            registerMBean( minaServerAcceptor );

            //setLoggingFilter();

        }
        catch ( IOException e )
        {
            // if any errors are encountered while starting up the server manager, shut it down.
            e.printStackTrace();
            System.err.println( "Error binding to port. Exiting CDS socket adapter application." );
            System.exit( -1 );
        }
        catch ( Exception e )
        {
            e.printStackTrace();
            System.err.println( "Error registering MINA Server with MBean Server" );
        }

    }


    private void registerMBean( NioSocketAcceptor acceptor )
        throws MalformedObjectNameException,
            InstanceAlreadyExistsException,
            MBeanRegistrationException,
            NotCompliantMBeanException
    {
        // create a JMX ObjectName.  This has to be in a specific format.  
        ObjectName acceptorName = new ObjectName( acceptor.getClass().getPackage().getName() + ":type=acceptor,name="
                        + acceptor.getClass().getSimpleName() );

        // register the bean on the MBeanServer.  Without this line, no JMX will happen for
        // this acceptor.
        mBeanServer.registerMBean( acceptorMBean, acceptorName );

    }


    private static void unregisterMBean( NioSocketAcceptor acceptor )
        throws Exception
    {
        ObjectName acceptorName = new ObjectName( acceptor.getClass().getPackage().getName() + ":type=acceptor,name="
                        + acceptor.getClass().getSimpleName() );

        ManagementFactory.getPlatformMBeanServer().unregisterMBean( acceptorName );
    }


    /**
     * Entry point for Server Manager application.
     * 
     * @param args
     */
    public static void main( String[] args )
    {
        allMethodsFromMain( args );
    }


    /** convenience Method for unit testing **/
    public static void allMethodsFromMain( String[] args )
    {
        configureLoggingAndProperties();
        if ( isShutdown( args ) )
        {
            doShutdown();
        }
    }


    private static boolean isShutdown( String[] args )
    {
        return args.length != 0 && "-shutdown".equals( args[0] ) ? true : false;
    }


    private static void configureLoggingAndProperties( )
    {
        // configure the runtime environment
        try
        {
            // load up the system properties
            new PropertiesUtil().loadSystemRunProperties();
            /*
             * NOTE: this method no longer needs to coinfigure log4j and so those steps are commented out - 
             * the log4j2 framework does the configuration by looking for the log4j.configureationFile System property - set in the startup.sh script
             * the code is getting the path to the external file and logs the location into a rolling file appender
             * with the location changes could be made externally and the app restarted
             */
            File logFile = new File( System.getProperty( "log4j.configurationFile" ) );
            //InputStream is = new FileInputStream(logFile.getAbsolutePath());
            //BufferedInputStream bis = new BufferedInputStream(is);
            //ConfigurationSource cs = new ConfigurationSource(bis);
            //XmlConfiguration configuration = new XmlConfiguration(cs);
            //configuration.initialize();
                        
            LOGGER.info( "using log file: " + logFile.getAbsolutePath() );
            // load up the Spring application context
            applicationContext = new FileSystemXmlApplicationContext( new String[] { System.getProperty( "socket.adapter.context" ) } );
        }
        catch ( Throwable t )
        {
            // if any errors are encountered while starting up the server manager, shut it down.
            t.printStackTrace();
            System.err.println( "Error configuring application logging and properties. Exiting CDS socket adapter application." );
            System.exit( -1 );
        }
    }


    private static void doShutdown( )
    {
        try
        {
            MBeanServerConnection mbsc = ( MBeanServerConnection )applicationContext.getBean( "clientConnector" );
            // MBeanServer's domain
            String domain = "gov.va.med.cds.adapter.socket";
            // Create MigrationManager MBean
            ObjectName stdMBeanName = new ObjectName( domain + ":name=ServerManager" );
            mbsc.getObjectInstance( stdMBeanName );
            mbsc.invoke( stdMBeanName, "shutdown", null, null );
        }
        catch ( UnmarshalException ue )
        {
            // this is expected here.
        }
        catch ( Exception e )
        {
            LOGGER.error( "Error invoking ServerManager.shutdown MBean operation.", e );
        }
        finally
        {
            System.exit( 0 );
        }
    }


    /*
     * (non-Javadoc)
     * 
     * @see gov.va.med.hds.hdr.nio.server.ServerManagerInterface#shutdown()
     */
    public void shutdown( )
        throws Exception
    {
        stopServers();
        System.exit( 0 );
    }


    public void stopServers( )
        throws Exception
    {
        unregisterMBean( minaServerAcceptor );

        LOGGER.info( String.format( "Shutting down server listening on %s:%d", minaServerAcceptor.getLocalAddress().getHostString(),
                        minaServerAcceptor.getLocalAddress().getPort() ) );

        minaServerAcceptor.dispose( false );
    }


    public static MinaServerManager createMinaServerManagerInstance( )
    {
        MinaServerManager minaServerManager = new MinaServerManager();

        return minaServerManager;
    }


    private void setLoggingFilter( )
    {
        LoggingFilter loggingFilter = ( LoggingFilter )minaServerAcceptor.getFilterChain().get( "loggingFilter" );

        loggingFilter.setExceptionCaughtLogLevel( defaultLogLevel );
        loggingFilter.setMessageReceivedLogLevel( defaultLogLevel );
        loggingFilter.setMessageSentLogLevel( defaultLogLevel );
        loggingFilter.setSessionClosedLogLevel( defaultLogLevel );
        loggingFilter.setSessionCreatedLogLevel( defaultLogLevel );
        loggingFilter.setSessionIdleLogLevel( defaultLogLevel );
        loggingFilter.setSessionOpenedLogLevel( defaultLogLevel );
    }

}
