Package de.netseeker.ejoe

Source Code of de.netseeker.ejoe.EJServer

/*********************************************************************
* EJServer.java
* created on 06.08.2004 by netseeker
* $Source: /cvsroot/ejoe/EJOE/src/de/netseeker/ejoe/EJServer.java,v $
* $Date: 2007/11/17 10:59:40 $
* $Revision: 1.78 $
*
* ====================================================================
*
*  Copyright 2005-2006 netseeker aka Michael Manske
*
*  Licensed 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
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
*  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.
* ====================================================================
*
* This file is part of the ejoe framework.
* For more information on the author, please see
* <http://www.manskes.de/>.
*
*********************************************************************/
package de.netseeker.ejoe;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.Deflater;

import de.netseeker.ejoe.core.ChannelRegistrar;
import de.netseeker.ejoe.core.CombinedConnectionProcessor;
import de.netseeker.ejoe.core.ConnectionAcceptor;
import de.netseeker.ejoe.core.EJServerRegistry;
import de.netseeker.ejoe.handler.ClassHandler;
import de.netseeker.ejoe.handler.PingHandler;
import de.netseeker.ejoe.handler.ServerHandler;
import de.netseeker.ejoe.handler.ServerHandlerMapping;
import de.netseeker.ejoe.io.IOUtil;
import de.netseeker.ejoe.jmx.EJServerConfig;
import de.netseeker.ejoe.jmx.EJServerConfigMBean;
import de.netseeker.ejoe.request.ClassloaderRequest;
import de.netseeker.ejoe.request.PingRequest;

/**
* This is the server component of EJOE. EJOE is a request object broker in it's natural meaning. You have to use this
* component if you want retrieve and send data from/to EJOE clients. EJOE offers basically three things for you:
* <ol>
* <li>a multithreaded, high performance io server</li>
* <li>(de)serializing input of input objects send by clients and return objects provided by YOUR business logic</li>
* <li>a simple, clean and unique interface to integrate a object request broker into your application</li>
* </ol>
* <p>
* Basic usage:
*
* <pre>
*                       EJServer server = new EJServer( new EchoHandler() );
*                       //to allow persistent client connections use
*                       //server.enablePersistentConnections( true );
*                      
*                       //to use old style blocking IO instead of NIO use
*                       //server.enableNonBlockingIO( false );
*                      
*                       server.start();
*                       ...
*                       server.stop();
* </pre>
*
* </p>
*
* @author netseeker aka Michael Manske
* @since 0.3.0
*/
public class EJServer
{
    private transient static final Logger logger         = Logger.getLogger( EJServer.class.getName() );

    private transient ConnectionAcceptor  _acceptor;

    private transient ChannelRegistrar[]  _processors;

    private transient ServerSocketChannel _channel;

    private ServerInfo          _serverInfo    = new ServerInfo();

    private boolean             _isShuttingDown = false;

    private EJServerConfigMBean _jmxConfigBean;

    /**
     * Creates an instance of the EJOE server component pre-configured whith a default value for the used port
     *
     * @param handler an implementation of de.netseeker.ejoe.ServerHandler
     */
    public EJServer(final ServerHandler handler)
    {
        this( handler, EJConstants.EJOE_PORT );
    }

    /**
     * Creates an instance of the EJOE server component pre-configured whith a default value for the used port
     *
     * @param handler an implementation of de.netseeker.ejoe.ServerHandler
     * @param bindAddr the IP address identifying the network interface to which EJServer should bind itself
     */
    public EJServer(final ServerHandler handler, String bindAddr)
    {
        this( handler, bindAddr, EJConstants.EJOE_PORT );
    }

    /**
     * Creates an instance of the EJOE server component
     *
     * @param handler an implementation of de.netseeker.ejoe.ServerHandler
     * @param port the port EJOE should listen to
     */
    public EJServer(final ServerHandler handler, int port)
    {
        ServerHandlerMapping mapping = null;
        if ( handler instanceof ServerHandlerMapping )
        {
            mapping = (ServerHandlerMapping) handler;
        }
        else
        {
            mapping = new ServerHandlerMapping();
            mapping.setDefaultHandler( handler );
        }

        mapping.addHandlerMapping( PingRequest.UNIQUE_NAME, new PingHandler() );

        _serverInfo.setHandler( handler );
        _serverInfo.setPort( port );
    }

    /**
     * Creates an instance of the EJOE server component
     *
     * @param handler an implementation of de.netseeker.ejoe.ServerHandler
     * @param bindAddr the IP address identifying the network interface to which EJServer should bind itself
     * @param port the port EJOE should listen to
     */
    public EJServer(final ServerHandler handler, String bindAddr, int port)
    {
        this( handler, port );
        _serverInfo.setInterface( bindAddr );
    }

    /**
     * Creates an instance of EJServer pre-configured with settings from the global ejserver.properties file. You MUST
     * provide such an property file on the classpath to use this constructor.
     */
    public EJServer()
    {
        Properties props = new Properties();
        try
        {
            props.load( EJServer.class.getResourceAsStream( "/ejserver.properties" ) );
            loadProperties( props );
        }
        catch ( IOException e )
        {
            logger.log( Level.SEVERE, "ejserver.properties could not be read! Make sure you have placed"
                    + " a valid properties file in your classpath.", e );
            throw new RuntimeException( e );
        }
    }

    /**
     * Creates an instance of EJServer pre-configured with settings from the given properties store
     *
     * @param properties properties store containing EJServer settings
     */
    public EJServer(Properties properties)
    {
        loadProperties( properties );
    }

    /**
     * Creates an instance of EJServer pre-configured with settings from a global properties file.
     *
     * @param pathToConfigFile path to the properties file
     */
    public EJServer(String pathToConfigFile)
    {
        Properties props = new Properties();
        FileInputStream fis = null;
        try
        {
            fis = new FileInputStream( pathToConfigFile );
            props.load( fis );
            loadProperties( props );
        }
        catch ( IOException e )
        {
            logger.log( Level.SEVERE, "ejserver.properties could not be read! Make sure you have placed"
                    + " a valid properties file in your classpath.", e );
            throw new RuntimeException( e );
        }
        finally
        {
            IOUtil.closeQuiet( fis );
        }
    }

    /**
     * Indicates if this EJServer is running and ready to accept and process incoming connections
     *
     * @return true if this EJServer is running and ready to accept and process incoming connections otherwise false
     */
    public boolean isRunning()
    {
        return (_processors != null && _acceptor != null && _acceptor.isAlive() && !_isShuttingDown);
    }

    /**
     * Returns the current amount of supported concurrent read processor threads.
     *
     * @return
     */
    public int getMaxReadProcessors()
    {
        return _serverInfo.getMaxReadProcessors();
    }

    /**
     * Sets the amount of threads used for processing read operations on accepted connections. This will indirectly
     * control the amount of concurrently processed io read operations and adapter calls.
     *
     * @param maxProcessors new amount of threads used for processing accepted connections
     * @see #setMaxWriteProcessors(int)
     */
    public void setMaxReadProcessors( int maxProcessors )
    {
        if ( !isRunning() )
        {
            _serverInfo.setMaxReadProcessors( maxProcessors );
        }
        else
        {
            throw new IllegalStateException( "The value for the maximum of used read processor"
                    + "threads can't be changed while EJOE is already running!" );
        }
    }

    /**
     * Returns the current amount of supported concurrent write processor threads.
     *
     * @return
     */
    public int getMaxWriteProcessors()
    {
        return _serverInfo.getMaxWriteProcessors();
    }

    /**
     * Sets the amount of threads used for processing write operations. This will directly control the amount of
     * concurrently processed io socket write operations.
     *
     * @param maxProcessors new amount of threads used for processing io socket write operations
     * @see #setMaxReadProcessors(int)
     */
    public void setMaxWriteProcessors( int maxProcessors )
    {
        if ( !isRunning() )
        {
            _serverInfo.setMaxWriteProcessors( maxProcessors );
        }
        else
        {
            throw new IllegalStateException(
                                             "The value for the maximum of used write processor threads can't be changed "
                                                     + "while EJOE is already running!" );
        }
    }

    /**
     * Enables/Disables automic, periodic control of the used worker pools. If enabled the worker pools will be checked
     * regulary and shrinked or increased if neccessary. The control ensures that the pool sizes will never extend the
     * size of maxReadProcessors/maxWriteProcessors.
     *
     * @param enable
     * @see #enableThreadPoolResizeControl(boolean, long)
     */
    public void enableThreadPoolResizeControl( boolean enable )
    {
        if ( _processors == null )
        {
            _serverInfo.setAutomaticThreadPoolResize( enable );
        }
        else
        {
            throw new IllegalStateException( "ThreadPoolResizeControl can't be changed"
                    + " while EJOE is already running!" );
        }
    }

    /**
     * This method works exactly as {@link #enableThreadedProcessorUsage(boolean)} but allows setting of a custom time
     * period between control checks of the used worker pools.
     *
     * @param enable
     * @param controlPeriod period between control checks in milliseconds
     * @see #enableThreadPoolResizeControl(boolean)
     */
    public void enableThreadPoolResizeControl( boolean enable, long controlPeriod )
    {
        if ( _processors == null )
        {
            _serverInfo.setAutomaticThreadPoolResize( enable );
            _serverInfo.setPoolResizePeriod( controlPeriod );
        }
        else
        {
            throw new IllegalStateException( "ThreadPoolResizeControl can't be changed"
                    + " while EJOE is already running!" );
        }
    }

    /**
     * Returns whether compression has been enabled or not.
     *
     * @return
     */
    public boolean hasCommpression()
    {
        return _serverInfo.hasCompression();
    }

    /**
     * Enables or disables the usage of compressing/decompressing for outgoing/incoming data. Enabling compression *can*
     * result in signifcantly better throughput especially when using a text based SerializeAdapter in confunction with
     * large data objects.
     *
     * @see de.netseeker.ejoe.adapter.SerializeAdapter
     */
    public void enableCompression( boolean enable )
    {
        this._serverInfo.setCompression( enable );
    }

    /**
     * Enables the usage of compressing/decompressing for outgoing/incoming data with the given compression level.
     * Enabling compression *can* result in signifcantly better throughput especially when using a text based
     * SerializeAdapter in confunction with large data objects.
     *
     * @param compressionLevel the level of compression to use, must be in range of 0-9
     * @see de.netseeker.ejoe.adapter.SerializeAdapter
     */
    public void enableCompression( int compressionLevel )
    {
        if ( compressionLevel < Deflater.NO_COMPRESSION || compressionLevel > Deflater.BEST_COMPRESSION )
        {
            throw new IllegalArgumentException( "Compressionlevel must be in the range of " + Deflater.NO_COMPRESSION
                    + ':' + Deflater.BEST_COMPRESSION );
        }
        this._serverInfo.setCompression( true );
        this._serverInfo.setCompressionLevel( compressionLevel );
    }

    /**
     * Returns whether NIO has been enabled or not.
     *
     * @return
     */
    public boolean hasNonBlockingIO()
    {
        return this._serverInfo.hasNonBlockingReadWrite();
    }

    /**
     * Enables/disables new style non-blocking IO for read and write operations. Sometimes this can be significantly
     * faster than using blocking io. Be aware that connection acception as well as EJOES internal connection
     * handshaking will use NIO anyway to ensure high latency.
     */
    public void enableNonBlockingIO( boolean enable )
    {
        if ( enable || (!enable && !hasHttPackaging()) )
        {
            this._serverInfo.setNonBlockingReadWrite( enable );
        }
        else
        {
            throw new IllegalStateException(
                                             "You have tried to disable non-blocking IO while HTTP support is still active! "
                                                     + "HTTP support is only implemented for non-blocking IO. "
                                                     + "To disable non-blocking IO you must disable HTTP packaging first." );
        }
    }

    /**
     * Returns whether support for persistent connections has been enabled or not.
     *
     * @return
     */
    public boolean hasPersistentConnections()
    {
        return this._serverInfo.isPersistent();
    }

    /**
     * Enables/disables support for persistents client connections. Usage of persistent connections is more performant
     * in most cases because the client doesn't need to open a new connection on each request. The drawback of
     * persistent connections can be a higher server load because the server may have to handle a lot of "idle" client
     * connections.
     *
     * @param enable
     */
    public void enablePersistentConnections( boolean enable )
    {
        this._serverInfo.setPersistent( enable );
    }

    /**
     * Enables/disables support for http packaging.
     *
     * @param enable
     */
    public void enableHttpPackaging( boolean enable )
    {
        if ( (enable && hasNonBlockingIO()) || !enable )
        {
            this._serverInfo.setHttp( enable );
        }
        else
        {
            throw new IllegalStateException(
                                             "HTTP support is only implemted for non-blocking IO! Enable non-blocking IO if you want to use HTTP packaging..." );
        }
    }

    /**
     * Returns whether support for HTTP packaging has been enabled or not.
     *
     * @return
     */
    public boolean hasHttPackaging()
    {
        return this._serverInfo.isHttp();
    }

    /**
     * Returns the number of Connection Processors used for delegating network IO operations to reader/writer workers
     *
     * @return the number of used Connection Processors
     */
    public int getConnectionProcessors()
    {
        return this._serverInfo.getTargetedConnectionProcessors();
    }

    /**
     * Sets the number of Connection Processors to use for delegating network IO operations to reader/writer workers. It
     * is strongly recommended to use the number of physically available processor units.
     *
     * @param count the number of Connection Processors to use
     */
    public void setConnectionProcessors( int count )
    {
        if ( !isRunning() )
        {
            this._serverInfo.setTargetedConnectionProcessors( count );
        }
        else
        {
            throw new IllegalStateException( "The number of Connection Processors must not be changed "
                    + "while EJServer is already running!" );
        }
    }

    /**
     * Enables support for remote classloading
     *
     * @param enable
     */
    public void enableRemoteClassLoading( boolean enable )
    {
        if ( !isRunning() )
        {
            _serverInfo.setClassServerEnabled( enable );
            ServerHandlerMapping mapping = (ServerHandlerMapping) _serverInfo.getHandler();

            if ( enable )
            {
                mapping.addHandlerMapping( ClassloaderRequest.UNIQUE_NAME, new ClassHandler() );
            }
            else
            {
                mapping.removeHandlerMapping( ClassloaderRequest.UNIQUE_NAME );
            }

        }
        else
        {
            throw new IllegalStateException( "Remote classloading can't be changed" + " while EJOE is already running!" );
        }
    }

    /**
     * Returns a JMX compliant bean to configure EJServer via JMX
     *
     * @return
     */
    public EJServerConfigMBean getJMXConfigurationBean()
    {
        if ( this._jmxConfigBean == null )
        {
            this._jmxConfigBean = new EJServerConfig( this );
        }

        return this._jmxConfigBean;
    }

    /**
     * @return
     */
    public IServerInfo getServerInfo()
    {
        return this._serverInfo;
    }

    /**
     * (Re)Starts the main server as well as the class loader server (if it's configured)
     */
    public void start() throws IOException
    {
        logger.log( Level.INFO, "Starting EJOE server..." );

        if ( _serverInfo.isServerRunning() )
        {
            logger.log( Level.WARNING, "EJOE server already running - will try a restart." );
            stop();
        }

        _isShuttingDown = false;
        ConnectionAcceptor acceptor = null;
        ChannelRegistrar processors[] = new ChannelRegistrar[this._serverInfo.getTargetedConnectionProcessors()];
        ServerSocketChannel channel = null;

        try
        {
            channel = ServerSocketChannel.open();
            channel.configureBlocking( false );
            channel.socket().setReuseAddress( true );
            InetSocketAddress address = new InetSocketAddress( _serverInfo.getInterface(), _serverInfo.getPort() );
            channel.socket().bind( address, 1024 );
            this._serverInfo.setHost( address.getHostName() + ':' + address.getPort() );
            // this._connectionHeader.setChannel(channel);
            for ( int i = 0; i < processors.length; i++ )
            {
                processors[i] = new CombinedConnectionProcessor( this._serverInfo );
                ((Thread) processors[i]).setDaemon( true );
            }

            acceptor = new ConnectionAcceptor( channel, processors );
            //acceptor.setDaemon( true );
        }
        catch ( IOException e )
        {
            logger.log( Level.SEVERE, "!!! IOException occured !!! ", e );
            IOUtil.closeQuiet( channel );
            throw (e);
        }

        for ( int i = 0; i < processors.length; i++ )
        {
            ((CombinedConnectionProcessor) processors[i]).start();
        }
        acceptor.start();

        this._serverInfo.setServerRunning( true );
        this._processors = processors;
        this._acceptor = acceptor;
        this._channel = channel;

        EJServerRegistry.getInstance().register( this );

        if ( logger.isLoggable( Level.INFO ) )
        {
            logger.log( Level.INFO, "EJOE server listening on: " + channel.socket().getLocalSocketAddress() );
            logger.log( Level.INFO, "Using " + processors.length + " Connection Processor"
                    + (processors.length > 1 ? "s" : "") );
            logger.log( Level.INFO, "Using non-blocking IO: " + this._serverInfo.hasNonBlockingReadWrite() );
            logger.log( Level.INFO, "Allowing persistent client connections: " + this._serverInfo.isPersistent() );
            logger.log( Level.INFO, "Using compression: " + this._serverInfo.hasCompression() );
            logger.log( Level.INFO, "Supporting HTTP tunneling: " + this._serverInfo.isHttp() );
            logger.log( Level.INFO, "Supporting remote classloading: " + this._serverInfo.isClassServerEnabled() );
            logger.log( Level.INFO, "Using automatic thread pool resizing: "
                    + this._serverInfo.isAutomaticThreadPoolResize() );
            logger.log( Level.INFO, "EJOE server started successfully." );
        }
    }

    /**
     * Stops the main server as well as the class loader server (if it's running)
     */
    public void stop()
    {
        logger.log( Level.INFO, "Stopping EJOE server..." );
        _isShuttingDown = true;
        EJServerRegistry.getInstance().deRegister( this );

        if ( isRunning() )
        {
            this._acceptor.interrupt();
            for ( int i = 0; i < this._processors.length; i++ )
            {
                ((Thread) this._processors[i]).interrupt();
            }
            this._serverInfo.setServerRunning( false );
            IOUtil.closeQuiet( this._channel );
            this._acceptor = null;
            this._processors = null;
            logger.log( Level.INFO, "EJOE server stopped." );
        }
    }

    /**
     * Reads the settings for this client from a given Properties instance
     *
     * @param props
     */
    private void loadProperties( Properties props )
    {
        String clazz = null;

        try
        {
            clazz = props.getProperty( "ejoe.serverHandler" );
            this._serverInfo.setHandler( (ServerHandler) Class.forName( clazz ).newInstance() );

            this._serverInfo.setInterface( props.getProperty( "ejoe.interface", "127.0.0.1" ) );
            this._serverInfo.setPort( Integer.parseInt( props.getProperty( "ejoe.port", String
                    .valueOf( EJConstants.EJOE_PORT ) ) ) );

            enableNonBlockingIO( Boolean.valueOf( props.getProperty( "ejoe.useNIO", "false" ) ).booleanValue() );

            enableCompression( Boolean.valueOf( props.getProperty( "ejoe.compression", "false" ) ).booleanValue() );

            enablePersistentConnections( Boolean.valueOf( props.getProperty( "ejoe.persistentConnection", "true" ) )
                    .booleanValue() );

            enableHttpPackaging( Boolean.valueOf( props.getProperty( "ejoe.httpTunneling", "false" ) ).booleanValue() );

            if ( Boolean.valueOf( props.getProperty( "ejoe.remoteClassloader", "false" ) ).booleanValue() )
            {
                enableRemoteClassLoading( true );
            }
        }
        catch ( InstantiationException e )
        {
            logger.log( Level.SEVERE, "Can't instantiate class " + clazz );
            throw new RuntimeException( e );
        }
        catch ( IllegalAccessException e )
        {
            logger.log( Level.SEVERE, "Can't access configured class " + clazz );
            throw new RuntimeException( e );
        }
        catch ( ClassNotFoundException e )
        {
            logger.log( Level.SEVERE, "Can't locate configured class " + clazz );
            throw new RuntimeException( e );
        }
    }
}
TOP

Related Classes of de.netseeker.ejoe.EJServer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.