/*********************************************************************
* EJClient.java
* created on 08.08.2004 by netseeker
* $Source: /cvsroot/ejoe/EJOE/src/de/netseeker/ejoe/EJClient.java,v $
* $Date: 2007/11/17 10:59:41 $
* $Revision: 1.100 $
*
* ====================================================================
*
* 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.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.rmi.RemoteException;
import java.text.ParseException;
import java.util.Iterator;
import java.util.Properties;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import de.netseeker.ejoe.adapter.AdapterFactory;
import de.netseeker.ejoe.adapter.SerializeAdapter;
import de.netseeker.ejoe.cache.ByteBufferAllocator;
import de.netseeker.ejoe.core.InJvmProcessor;
import de.netseeker.ejoe.core.InJvmProcessorFactory;
import de.netseeker.ejoe.io.ByteBufferInputStream;
import de.netseeker.ejoe.io.ByteBufferOutputStream;
import de.netseeker.ejoe.io.ChannelInputStream;
import de.netseeker.ejoe.io.DataChannel;
import de.netseeker.ejoe.io.IOUtil;
import de.netseeker.ejoe.io.IncompleteIOException;
/**
* This is the client component of EJOE. You have to use this component to send and retrieve data to/from a EJOE server.
* <p>
* Basic usage:
*
* <pre>
* EJClient client = new EJClient("127.0.0.1", 12577); //you could also use EJConstants.EJOE_PORT
* Object response = client.execute("Hello World");
* ...
* </pre>
*
* </p>
* <p>
* Usage with persistent connections:
*
* <pre>
* EJClient client = new EJClient("127.0.0.1", 12577); //you could also use EJConstants.EJOE_PORT
* client.enablePersistentConnection(true); //request a persistent connection to the server
* Object response = client.execute("Hello World");
* ...
* reponse = client.execute("Bye World");
* //close the connection
* client.close();
* ...
* </pre>
*
* </p>
* <p>
* Usage with In-JVM EJServer:
*
* <pre>
* EJClient client = new EJClient("127.0.0.1", 12577); //you could also use EJConstants.EJOE_PORT
* client.setInJvm(true); //enable In-JVM mode, no socket connections will be used
* Object response = client.execute("Hello World");
* ...
* reponse = client.execute("Bye World");
* ...
* </pre>
*
* </p>
*
* @author netseeker aka Michael Manske
* @since 0.3.0
*/
public class EJClient implements Serializable
{
private static final long serialVersionUID = 1L;
private transient static final Logger log = Logger.getLogger( EJClient.class.getName() );
private SerializeAdapter _adapter;
private String _host = "127.0.0.1";
private int _port = EJConstants.EJOE_PORT;
private int _connectionTimeout = EJConstants.EJOE_CONNECTION_TIMEOUT;
private boolean _inJVM = false;
private final ConnectionHeader _clientInfo = new ConnectionHeader( true );
private transient ConnectionHeader _serverInfo;
private transient SocketChannel _channel;
private transient Selector _selector;
private Object _mutex = new Object();
private static Random _idGenerator = new Random();
/**
* Creates an instance of the EJOE client pre-configured with settings from the global ejoe.properties file. You
* MUST provide such an property file on the classpath to use this constructor.
*/
public EJClient()
{
Properties props = new Properties();
try
{
props.load( EJClient.class.getResourceAsStream( "/ejoe.properties" ) );
loadProperties( props );
}
catch ( IOException e )
{
log.log( Level.SEVERE, "ejoe.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 the EJOE client pre-configured with settings from a global properties file.
*
* @param pathToConfigFile path to the properties file
*/
public EJClient(String pathToConfigFile)
{
Properties props = new Properties();
FileInputStream fis = null;
try
{
fis = new FileInputStream( pathToConfigFile );
props.load( fis );
loadProperties( props );
}
catch ( IOException e )
{
log.log( Level.SEVERE, "ejoe.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 );
}
}
/**
* Creates an instance of the EJOE client pre-configured with settings from the given properties store.
*
* @param properties properties store containing EJClient settings
*/
public EJClient(Properties properties)
{
loadProperties( properties );
}
/**
* Creates an instance of the EJOE client preconfigured to use an instance of
* de.netseeker.ejoe.adapter.ObjectStreamAdapter for (de)serializing.
*
* @param host address (dns name or ip address) of the EJOE server
* @param port port which the EJOE server listens to
*/
public EJClient(String host, int port)
{
this._host = host;
this._port = port;
this._clientInfo.setHost( _host + ':' + _port );
this._adapter = AdapterFactory.createAdapter( EJConstants.EJOE_DEFAULT_ADAPTER.getName() );
this._clientInfo.setAdapterName( EJConstants.EJOE_DEFAULT_ADAPTER.getName() );
}
/**
* Creates an instance of the EJOE client.
*
* @param host address (dns name or ip address) of the EJOE server
* @param port port which the EJOE server listens to
* @param adapter the adapter used for (de)serializing input paramter objects for the server and the return values
*/
public EJClient(String host, int port, final SerializeAdapter adapter)
{
this._host = host;
this._port = port;
this._clientInfo.setHost( _host + ':' + _port );
this._adapter = adapter;
this._clientInfo.setAdapterName( adapter.getClass().getName() );
}
/**
* Creates an instance of the EJOE client.
*
* @param host address (dns name or ip address) of the EJOE server
* @param port port which the EJOE server listens to
* @param adapter the adapter used for (de)serializing input paramter objects for the server and the return values
* @param isPersistent whether EJClient should use a persistent connection to the server or not
* @param isHttp whether EJClient should wrap requests in HTTP headers or not (lets EJClient socket data look like a
* HTTP 1.1 browser communication)
*/
public EJClient(String host, int port, final SerializeAdapter adapter, boolean isPersistent, boolean isHttp,
boolean useCompression)
{
this( host, port, adapter );
enablePersistentConnection( isPersistent );
enableCompression( useCompression );
enableHttpPackaging( isHttp );
}
/**
* Sets the connection timeout used when waiting for server responses. A value of zero (the default) blocks
* indefinitely.
*
* @param timeout the new timeout in milliseconds
*/
public synchronized void setConnectionTimeout( int timeout )
{
this._connectionTimeout = timeout;
}
/**
* Tells this client to use compression (if supported by the server) or not.
*
* @param enable
*/
public synchronized void enableCompression( boolean enable )
{
this._clientInfo.setCompression( enable );
}
/**
* Tells this client to use compression (if supported by the server) with the given compression level.
*
* @param compressionLevel the level of compression to use, must be in range of 0-9
*/
public synchronized void enableCompression( int compressionLevel )
{
this._clientInfo.setCompression( true );
this._clientInfo.setCompressionLevel( compressionLevel );
}
/**
* Enables/disables usage of a persistent connection. If persistent connection is disabled a new connection will be
* used for each request.
*
* @param enable
*/
public synchronized void enablePersistentConnection( boolean enable )
{
this._clientInfo.setPersistent( enable );
}
/**
* Enables/disables usage of a persistent connection. If persistent connection is disabled a new connection will be
* used for each request.
*
* @param enable
*/
public synchronized void enableHttpPackaging( boolean enable )
{
this._clientInfo.setHttp( enable );
}
/**
* @return the _inJVM
*/
public boolean isInJVM()
{
return _inJVM;
}
/**
* @param injvm the _inJVM to set
*/
public synchronized void setInJVM( boolean injvm )
{
enableHttpPackaging( false );
_inJVM = injvm;
}
/**
* Controls the used Adapter Strategy:
* <ul>
* <li>ADAPTER_STRATEGY_DEFAULT: both, client and server, will serialize and deserialize objects. The client will
* return a deserialized object</li>
* <li>ADAPTER_STRATEGY_DIRECT: both, client and server, will NOT serialize and deserialize objects. The client
* will expect ByteBuffers as arguments and the ServerHandler will get ByteBuffers too. The client will return
* ByteBuffers to the caller.</li>
* <li>ADAPTER_STRATEGY_MIXED: both, client and server, will exchange serialized objects. The ServerHandler will
* get deserialized objects too handle. The client will return a ByteBuffer instead of derserialized objects to the
* caller.</li>
* </ul>
*
* @param adapterStrategy
*/
public synchronized void setAdapterStrategy( int adapterStrategy )
{
switch ( adapterStrategy )
{
case EJConstants.ADAPTER_STRATEGY_DEFAULT:
this._clientInfo.setIsDirect( false );
this._clientInfo.setIsMixed( false );
break;
case EJConstants.ADAPTER_STRATEGY_DIRECT:
this._clientInfo.setIsDirect( true );
this._clientInfo.setIsMixed( false );
break;
case EJConstants.ADAPTER_STRATEGY_MIXED:
this._clientInfo.setIsDirect( false );
this._clientInfo.setIsMixed( true );
break;
default:
throw new IllegalArgumentException( "Unknown Adapter Strategy: " + adapterStrategy );
}
}
/**
* Enables remote classloading on the default remote port.
*/
public synchronized void enableRemoteClassloading()
{
if ( Thread.currentThread().getContextClassLoader() instanceof EJClassLoader )
throw new IllegalStateException( "Remote classloading already enabled!" );
initClassLoader();
}
/**
* Getter method to get direct access to the underlying ConnectionHeader (as required by the WSIF port
* implementation)
*
* @return the used client configuration represented as ConnectionHeader
*/
public ConnectionHeader getConnectionHeader()
{
return _clientInfo;
}
/**
* Main entry point for client tier implementations. Handles all remote server calls... This method is threadsafe,
* ensuring that only one request is processed at a time.
*
* @param obj input objects for the EJOE Server
* @return the object(s) returned by the EJOE server
*/
public Object execute( final Object obj ) throws IOException
{
synchronized ( _mutex )
{
Object result = null;
// ensure that only one request is in process at one time without
// blocking the whole client
ConnectionHeader clientInfo = this._clientInfo.copy();
if ( _inJVM )
{
InJvmProcessor processor = InJvmProcessorFactory.createProcessor( clientInfo );
try
{
result = processor.process( obj );
}
catch ( Exception e )
{
result = e;
}
}
else
{
SelectionKey selKey = null;
boolean error = false;
try
{
if ( this._channel == null || !this._channel.isOpen() )
{
log.log( Level.FINEST, "opening new connection" );
openSocketChannel();
_channel.register( this._selector, SelectionKey.OP_CONNECT | SelectionKey.OP_WRITE );
}
else
{
log.log( Level.FINEST, "reusing persistent connection" );
selKey = _channel.keyFor( this._selector );
// register this channel again
selKey = _channel.register( this._selector, 0 );
selKey.interestOps( SelectionKey.OP_WRITE );
}
Iterator it = null;
while ( this._selector.select( _connectionTimeout ) > 0 )
{
it = this._selector.selectedKeys().iterator();
while ( it.hasNext() )
{
selKey = (SelectionKey) it.next();
it.remove();
if ( !selKey.isValid() )
{
continue;
}
try
{
if ( selKey.isConnectable() && _channel.isConnectionPending() )
{
if ( !_channel.finishConnect() )
{
continue;
}
clientInfo.setChannel( _channel );
if ( log.isLoggable( Level.INFO ) )
{
log.log( Level.INFO, "Connection established to "
+ _channel.socket().getRemoteSocketAddress() );
}
_channel.register( this._selector, SelectionKey.OP_WRITE );
}
if ( selKey.isWritable() )
{
if ( _serverInfo == null )
{
log.log( Level.FINEST, "Handshaking server..." );
_serverInfo = DataChannel.getInstance().handshake( clientInfo, _channel,
_connectionTimeout );
if ( _serverInfo == null )
{
throw new IOException( "Connection timeout (" + _connectionTimeout
+ "ms) reached while waiting for handshake completing." );
}
else if ( clientInfo.isHttp() && !_serverInfo.isHttp() )
{
throw new UnsupportedOperationException(
"The server does not permit usage of HTTP packaging!" );
}
}
if ( _serverInfo.hasNonBlockingReadWrite() )
{
send( _serverInfo, clientInfo, selKey, obj );
_channel.register( this._selector, SelectionKey.OP_READ );
}
else
{
selKey.cancel();
_channel.configureBlocking( true );
result = processBlocked( clientInfo, obj );
if ( result != null && (result instanceof Throwable) )
{
if ( !(result instanceof RemoteException) )
{
throw new RemoteException(
"The server did return an Exception while handling your request!",
(Throwable) result );
}
else
{
throw (RemoteException) result;
}
}
return result;
}
}
else if ( selKey.isReadable() )
{
result = receive( clientInfo );
selKey.cancel();
if ( result != null && (result instanceof Throwable) )
{
if ( !(result instanceof RemoteException) )
{
throw new RemoteException(
"The server did return an Exception while handling your request!",
(Throwable) result );
}
else
{
throw (RemoteException) result;
}
}
return result;
}
}
catch ( IncompleteIOException ioe )
{
// selKey.cancel();
if ( ioe.getIOBuffer() != null )
{
clientInfo.setWaitingBuffer( ioe.getIOBuffer() );
_channel.register( this._selector, ioe.getSelectionInterest() );
}
else
{
// rethrow origin exception to inform the caller
// without the hassle of nested exceptions
throw ioe;
}
}
catch ( ParseException e )
{
error = true;
throw new IOException(
"Connection closed due to unparseable connection header! Exact message was: "
+ e.getMessage() );
}
catch ( IOException e )
{
if ( !(e instanceof RemoteException) )
{
error = true;
}
// rethrow origin exception to inform the caller
// without the hassle of nested exceptions
throw e;
}
}
}
}
finally
{
selKey.cancel();
// beeing a little bit paranoid is sometimes better
clientInfo.releaseAttachment();
clientInfo.releaseWaitingBuffer();
if ( _serverInfo != null )
{
_serverInfo.releaseAttachment();
_serverInfo.releaseWaitingBuffer();
}
if ( error )
{
close();
}
else if ( !clientInfo.isPersistent() || _serverInfo == null || !_serverInfo.isPersistent() )
{
close();
}
else
{
// clean out cancelled keys
this._selector.selectNow();
if ( _channel.isBlocking() ) _channel.configureBlocking( false );
}
}
}
if ( result != null && result instanceof Throwable )
{
throw new RemoteException( "The server did return an Exception while handling your request!",
(Throwable) result );
}
return result;
}
}
/**
* Asynchrounous entry point for executing server invocations. This method works asynchrounous in that way, as it
* starts each invocation in a new thread.
*
* @param obj input objects for the EJOE Server
* @param callback callback instance which will be notified if the request was processed or an an error occured
* @return an unique identifier for the started asynchrounous server invocation
*/
public long executeAsync( final Object obj, final EJAsyncCallback callback )
{
long ident = _idGenerator.nextLong();
Thread t = new Thread( new EJAsyncWorker( this, obj, callback, ident ) );
t.setDaemon( true );
t.start();
return ident;
}
/**
* Closes an existing persistent connection to the corresponding EJOE server. Invoking this method when using
* non-persistent connections has no effect.
*/
public void close()
{
synchronized ( _mutex )
{
IOUtil.closeQuiet( this._selector );
IOUtil.closeQuiet( this._channel );
this._serverInfo = null;
this._channel = null;
}
}
/**
* Opens a new socket connection to the EJServer
*
* @return
* @throws IOException
*/
private void openSocketChannel() throws IOException
{
this._channel = SocketChannel.open();
this._channel.configureBlocking( false );
Socket socket = this._channel.socket();
try
{
socket.setSoTimeout( this._connectionTimeout );
socket.setReuseAddress( true );
socket.setSoLinger( false, 0 );
socket.setTrafficClass( EJConstants.IPTOS_THROUGHPUT );
}
catch ( Exception e )
{
// OK, can happen. Probably the current plattform respectively it's
// IP implementation doesn't allow setting these socket options
}
this._selector = Selector.open();
// clean out cancelled keys
this._selector.selectNow();
log.log( Level.INFO, "Opening new connection..." );
this._channel.connect( new InetSocketAddress( _host, _port ) );
}
/**
* Non-blocking request sending.
*
* @param serverInfo ConnectionHeader received from the EJServer
* @param selKey
* @param obj the request
* @throws IOException
*/
private void send( ConnectionHeader serverInfo, ConnectionHeader clientInfo, SelectionKey selKey, Object obj )
throws IOException
{
ByteBuffer dataBuf = (ByteBuffer) selKey.attachment();
DataChannel dataChannel = DataChannel.getInstance( serverInfo );
ByteBufferOutputStream out = null;
try
{
if ( dataBuf == null )
{
// usual way: serialize using a adapter
if ( !clientInfo.isDirect() )
{
out = new ByteBufferOutputStream();
serialize( out, obj, clientInfo );
dataBuf = out.getBackingBuffer();
}
// direct mode: just use the attachement
else
{
dataBuf = (ByteBuffer) obj;
}
if ( dataBuf.position() > 0 )
{
dataBuf.flip();
}
if ( log.isLoggable( Level.FINE ) )
{
byte[] tmp = new byte[dataBuf.remaining()];
dataBuf.get( tmp );
dataBuf.position( 0 );
log.log( Level.FINE, "Going to send request..."
+ new String( tmp, EJConstants.EJOE_DEFAULT_CHARSET ) );
}
dataChannel.writeHeader( serverInfo, dataBuf, this._connectionTimeout );
}
dataChannel.nonBlockingWrite( _channel, dataBuf );
log.log( Level.FINE, "Request sent." );
}
finally
{
IOUtil.closeQuiet( out );
}
}
/**
* Non-blocking response reading.
*
* @param clientInfo client ConnectionHeader
* @return server response received by the corresponding EJServer
* @throws IOException
*/
private Object receive( ConnectionHeader clientInfo ) throws IOException
{
Object result = null;
DataChannel dataChannel = DataChannel.getInstance( _serverInfo );
ByteBuffer dataBuf = clientInfo.getWaitingBuffer();
ByteBufferInputStream in = null;
try
{
if ( dataBuf == null )
{
int length = dataChannel.readHeader( _serverInfo, this._connectionTimeout );
// server signals a null result?
if ( length == 0 )
{
return null;
}
// maybe the DataChannel signals that it has already read
// partial data
if ( _serverInfo.hasWaitingBuffer() )
{
dataBuf = _serverInfo.getWaitingBuffer();
}
else
{
dataBuf = ByteBufferAllocator.allocate( length );
}
log.log( Level.FINE, "Going to read server response with length: " + length );
}
if ( dataBuf.hasRemaining() )
{
DataChannel.nonBlockingRead( _channel, dataBuf );
}
dataBuf.flip();
log
.log( Level.FINE,
"Response read. Now calling (de)serialize adapter to convert response back to object." );
if ( dataBuf.hasRemaining() )
{
try
{
// usual way: deserialize using a adapter
if ( !clientInfo.isDirect() && !clientInfo.isMixed() )
{
in = new ByteBufferInputStream( dataBuf );
result = deserialize( in, clientInfo );
}
// direct or mixed mode: don't deserialize, just copy and return
// the read ByteBuffer
else
{
// copy the bytebuffer
ByteBuffer tmp = ByteBufferAllocator.allocate( dataBuf.remaining() );
tmp.put( dataBuf );
tmp.flip();
result = tmp;
}
log.log( Level.FINE, "Server response successfully converted to object." );
}
finally
{
clientInfo.releaseWaitingBuffer();
}
}
}
finally
{
IOUtil.closeQuiet( in );
}
return result;
}
/**
* Blocking request sending
*
* @param channel the connected SocketChannel
* @param obj the request
* @return server response received by the corresponding EJServer
* @throws IOException
*/
private Object processBlocked( ConnectionHeader clientInfo, Object obj ) throws IOException
{
Object result = null;
// usual way: (de)serialize using a adapter
if ( !clientInfo.isDirect() )
{
serialize( Channels.newOutputStream( _channel ), obj, clientInfo );
// default mode: deserialize the server response
if ( !clientInfo.isMixed() )
{
result = deserialize( new ChannelInputStream( Channels.newInputStream( _channel ) ), clientInfo );
}
// mixed mode: don't deserialize the server response
else
{
result = IOUtil.readDirect( new ChannelInputStream( Channels.newInputStream( _channel ) ) );
}
}
// direct mode: don't (de)serialize, just copy and return the read
// ByteBuffer
else
{
ByteBuffer tmp = (ByteBuffer) obj;
if ( tmp.position() > 0 )
{
tmp.flip();
}
IOUtil.writeDirect( Channels.newOutputStream( _channel ), tmp );
result = IOUtil.readDirect( new ChannelInputStream( Channels.newInputStream( _channel ) ) );
}
return result;
}
/**
* Serializes a given request object through the given OutputStream
*
* @param out the OutputStream to use
* @param obj the object to serialize
* @param serverInfo ConnectionHeader containing the connection data of the connected EJServer
* @throws IOException
*/
private void serialize( OutputStream out, Object obj, ConnectionHeader clientInfo ) throws IOException
{
boolean compressed = !clientInfo.isHttp() && clientInfo.hasCompression() && _serverInfo.hasCompression();
try
{
IOUtil.adapterSerialize( this._adapter, out, obj, compressed, clientInfo.getCompressionLevel() );
}
catch ( Exception e )
{
throw new RemoteException( "The client encountered an error while serializing your request data!", e );
}
}
/**
* Deserializes a server response into an object from the given InputStream
*
* @param in the InputStream to read the response from
* @param serverInfo ConnectionHeader containing the connection data of the connected EJServer
* @return a deserialized response object
* @throws IOException
*/
private Object deserialize( InputStream in, ConnectionHeader clientInfo ) throws IOException
{
boolean compressed = clientInfo.hasCompression() && _serverInfo.hasCompression();
try
{
return IOUtil.adapterDeserialize( this._adapter, in, compressed );
}
catch ( Exception e )
{
throw new RemoteException( "The client encountered an error while deserializing the server response!", e );
}
}
/**
* Initializes a EJClassloader and sets it as the current context classloader. Also notifies the used
* SerializeAdapter to recognize the classloader change.
*/
private void initClassLoader()
{
EJClassLoader ejcl = new EJClassLoader( Thread.currentThread().getContextClassLoader(), _host, _port );
Thread.currentThread().setContextClassLoader( ejcl );
this._adapter.handleClassLoaderChange( ejcl );
}
/**
* Reads the settings for this client from a given Properties instance
*
* @param props
*/
private void loadProperties( Properties props )
{
String clazz = null;
clazz = props.getProperty( "ejoe.adapter", EJConstants.EJOE_DEFAULT_ADAPTER.getName() );
_adapter = AdapterFactory.createAdapter( clazz );
this._clientInfo.setAdapterName( clazz );
_host = props.getProperty( "ejoe.host", "127.0.0.1" );
_port = Integer.parseInt( props.getProperty( "ejoe.port", String.valueOf( EJConstants.EJOE_PORT ) ) );
this._clientInfo.setHost( _host + ':' + _port );
setConnectionTimeout( Integer.parseInt( props.getProperty( "ejoe.connectionTimeout", String
.valueOf( EJConstants.EJOE_CONNECTION_TIMEOUT ) ) ) );
enableCompression( Boolean.valueOf( props.getProperty( "ejoe.compression", "false" ) ).booleanValue() );
enablePersistentConnection( Boolean.valueOf( props.getProperty( "ejoe.persistentConnection", "true" ) )
.booleanValue() );
enableHttpPackaging( Boolean.valueOf( props.getProperty( "ejoe.httpPackaging", "false" ) ).booleanValue() );
if ( Boolean.valueOf( props.getProperty( "ejoe.remoteClassloader", "false" ) ).booleanValue() )
{
enableRemoteClassloading();
}
if ( Boolean.valueOf( props.getProperty( "ejoe.inJVM", "false" ) ).booleanValue() )
{
setInJVM( true );
}
int adapterStrategy = Integer.parseInt( props.getProperty( "ejoe.adapterStrategy", "0" ) );
setAdapterStrategy( adapterStrategy );
}
}