001package org.apache.commons.jcs.auxiliary.remote.server;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.IOException;
023import java.io.Serializable;
024import java.net.MalformedURLException;
025import java.rmi.Naming;
026import java.rmi.NotBoundException;
027import java.rmi.Remote;
028import java.rmi.RemoteException;
029import java.rmi.registry.Registry;
030import java.rmi.server.RMISocketFactory;
031import java.rmi.server.UnicastRemoteObject;
032import java.util.Properties;
033import java.util.concurrent.Executors;
034import java.util.concurrent.ScheduledExecutorService;
035import java.util.concurrent.TimeUnit;
036
037import org.apache.commons.jcs.auxiliary.AuxiliaryCacheConfigurator;
038import org.apache.commons.jcs.auxiliary.remote.RemoteUtils;
039import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheConstants;
040import org.apache.commons.jcs.engine.behavior.ICacheServiceAdmin;
041import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
042import org.apache.commons.jcs.utils.config.OptionConverter;
043import org.apache.commons.jcs.utils.config.PropertySetter;
044import org.apache.commons.jcs.utils.threadpool.DaemonThreadFactory;
045import org.apache.commons.logging.Log;
046import org.apache.commons.logging.LogFactory;
047
048/**
049 * Provides remote cache services. This creates remote cache servers and can proxy command line
050 * requests to a running server.
051 */
052public class RemoteCacheServerFactory
053    implements IRemoteCacheConstants
054{
055    /** The logger */
056    private static final Log log = LogFactory.getLog( RemoteCacheServerFactory.class );
057
058    /** The single instance of the RemoteCacheServer object. */
059    private static RemoteCacheServer<?, ?> remoteCacheServer;
060
061    /** The name of the service. */
062    private static String serviceName = IRemoteCacheConstants.REMOTE_CACHE_SERVICE_VAL;
063
064    /** Executes the registry keep alive. */
065    private static ScheduledExecutorService keepAliveDaemon;
066
067    /** A reference to the registry. */
068    private static Registry registry = null;
069
070    /** Constructor for the RemoteCacheServerFactory object. */
071    private RemoteCacheServerFactory()
072    {
073        super();
074    }
075
076    /**
077     * This will allow you to get stats from the server, etc. Perhaps we should provide methods on
078     * the factory to do this instead.
079     * <p>
080     * A remote cache is either a local cache or a cluster cache.
081     * </p>
082     * @return Returns the remoteCacheServer.
083     */
084    @SuppressWarnings("unchecked") // Need cast to specific RemoteCacheServer
085    public static <K, V> RemoteCacheServer<K, V> getRemoteCacheServer()
086    {
087        return (RemoteCacheServer<K, V>)remoteCacheServer;
088    }
089
090    // ///////////////////// Startup/shutdown methods. //////////////////
091    /**
092     * Starts up the remote cache server on this JVM, and binds it to the registry on the given host
093     * and port.
094     * <p>
095     * A remote cache is either a local cache or a cluster cache.
096     * <p>
097     * @param host the host name
098     * @param port the port number
099     * @param propFile the remote cache hub configuration file
100     * @throws IOException
101     *
102     * @deprecated Use startup(String, int, Properties) instead
103     */
104    @Deprecated
105    public static void startup( String host, int port, String propFile )
106        throws IOException
107    {
108        if ( log.isInfoEnabled() )
109        {
110            log.info( "ConfigFileName = [" + propFile + "]" );
111        }
112        Properties props = RemoteUtils.loadProps( propFile );
113        startup(host, port, props);
114    }
115
116    /**
117     * Starts up the remote cache server on this JVM, and binds it to the registry on the given host
118     * and port.
119     * <p>
120     * A remote cache is either a local cache or a cluster cache.
121     * <p>
122     * @param host the host name
123     * @param port the port number
124     * @param props the remote cache hub configuration
125     * @param propFile the remote cache hub configuration file
126     * @throws IOException
127     *
128     * @deprecated Use startup(String, int, Properties) instead
129     */
130    @Deprecated
131    public static void startup( String host, int port, Properties props, String propFile )
132        throws IOException
133    {
134        if ( log.isWarnEnabled() )
135        {
136            log.warn( "ConfigFileName = [" + propFile + "] ignored" );
137        }
138        startup(host, port, props);
139    }
140
141    /**
142     * Starts up the remote cache server on this JVM, and binds it to the registry on the given host
143     * and port.
144     * <p>
145     * A remote cache is either a local cache or a cluster cache.
146     * <p>
147     * @param host
148     * @param port
149     * @param props
150     * @throws IOException
151     */
152    public static void startup( String host, int port, Properties props)
153        throws IOException
154    {
155        if ( remoteCacheServer != null )
156        {
157            throw new IllegalArgumentException( "Server already started." );
158        }
159
160        synchronized ( RemoteCacheServer.class )
161        {
162            if ( remoteCacheServer != null )
163            {
164                return;
165            }
166            if ( host == null )
167            {
168                host = "";
169            }
170
171            RemoteCacheServerAttributes rcsa = configureRemoteCacheServerAttributes(props);
172
173            // These should come from the file!
174            rcsa.setRemoteLocation( host, port );
175            if ( log.isInfoEnabled() )
176            {
177                log.info( "Creating server with these attributes: " + rcsa );
178            }
179
180            setServiceName( rcsa.getRemoteServiceName() );
181
182            RMISocketFactory customRMISocketFactory = configureObjectSpecificCustomFactory( props );
183
184            RemoteUtils.configureGlobalCustomSocketFactory( rcsa.getRmiSocketFactoryTimeoutMillis() );
185
186            // CONFIGURE THE EVENT LOGGER
187            ICacheEventLogger cacheEventLogger = configureCacheEventLogger( props );
188
189            // CREATE SERVER
190            if ( customRMISocketFactory != null )
191            {
192                remoteCacheServer = new RemoteCacheServer<Serializable, Serializable>( rcsa, props, customRMISocketFactory );
193            }
194            else
195            {
196                remoteCacheServer = new RemoteCacheServer<Serializable, Serializable>( rcsa, props );
197            }
198
199            remoteCacheServer.setCacheEventLogger( cacheEventLogger );
200
201            // START THE REGISTRY
202                registry = RemoteUtils.createRegistry(port);
203
204            // REGISTER THE SERVER
205            registerServer( serviceName, remoteCacheServer );
206
207            // KEEP THE REGISTRY ALIVE
208            if ( rcsa.isUseRegistryKeepAlive() )
209            {
210                if ( keepAliveDaemon == null )
211                {
212                    keepAliveDaemon = Executors.newScheduledThreadPool(1,
213                            new DaemonThreadFactory("JCS-RemoteCacheServerFactory-"));
214                }
215                RegistryKeepAliveRunner runner = new RegistryKeepAliveRunner( host, port, serviceName );
216                runner.setCacheEventLogger( cacheEventLogger );
217                keepAliveDaemon.scheduleAtFixedRate(runner, 0, rcsa.getRegistryKeepAliveDelayMillis(), TimeUnit.MILLISECONDS);
218            }
219        }
220    }
221
222    /**
223     * Tries to get the event logger by new and old config styles.
224     * <p>
225     * @param props
226     * @return ICacheEventLogger
227     */
228    protected static ICacheEventLogger configureCacheEventLogger( Properties props )
229    {
230        ICacheEventLogger cacheEventLogger = AuxiliaryCacheConfigurator
231            .parseCacheEventLogger( props, IRemoteCacheConstants.CACHE_SERVER_PREFIX );
232
233        // try the old way
234        if ( cacheEventLogger == null )
235        {
236            cacheEventLogger = AuxiliaryCacheConfigurator.parseCacheEventLogger( props,
237                                                                                 IRemoteCacheConstants.PROPERTY_PREFIX );
238        }
239        return cacheEventLogger;
240    }
241
242    /**
243     * This configures an object specific custom factory. This will be configured for just this
244     * object in the registry. This can be null.
245     * <p>
246     * @param props
247     * @return RMISocketFactory
248     */
249    protected static RMISocketFactory configureObjectSpecificCustomFactory( Properties props )
250    {
251        RMISocketFactory customRMISocketFactory =
252            OptionConverter.instantiateByKey( props, CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX, null );
253
254        if ( customRMISocketFactory != null )
255        {
256            PropertySetter.setProperties( customRMISocketFactory, props, CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX
257                + "." );
258            if ( log.isInfoEnabled() )
259            {
260                log.info( "Will use server specific custom socket factory. " + customRMISocketFactory );
261            }
262        }
263        else
264        {
265            if ( log.isInfoEnabled() )
266            {
267                log.info( "No server specific custom socket factory defined." );
268            }
269        }
270        return customRMISocketFactory;
271    }
272
273    /**
274     * Registers the server with the registry. I broke this off because we might want to have code
275     * that will restart a dead registry. It will need to rebind the server.
276     * <p>
277     * @param serviceName the name of the service
278     * @param server the server object to bind
279     * @throws RemoteException
280     */
281    protected static void registerServer(String serviceName, Remote server )
282        throws RemoteException
283    {
284        if ( server == null )
285        {
286            throw new RemoteException( "Cannot register the server until it is created." );
287        }
288
289        if ( registry == null )
290        {
291            throw new RemoteException( "Cannot register the server: Registry is null." );
292        }
293
294        if ( log.isInfoEnabled() )
295        {
296            log.info( "Binding server to " + serviceName );
297        }
298
299        registry.rebind( serviceName, server );
300    }
301
302    /**
303     * Configure.
304     * <p>
305     * jcs.remotecache.serverattributes.ATTRIBUTENAME=ATTRIBUTEVALUE
306     * <p>
307     * @param prop
308     * @return RemoteCacheServerAttributesconfigureRemoteCacheServerAttributes
309     */
310    protected static RemoteCacheServerAttributes configureRemoteCacheServerAttributes( Properties prop )
311    {
312        RemoteCacheServerAttributes rcsa = new RemoteCacheServerAttributes();
313
314        // configure automatically
315        PropertySetter.setProperties( rcsa, prop, CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + "." );
316
317        configureManuallyIfValuesArePresent( prop, rcsa );
318
319        return rcsa;
320    }
321
322    /**
323     * This looks for the old config values.
324     * <p>
325     * @param prop
326     * @param rcsa
327     */
328    private static void configureManuallyIfValuesArePresent( Properties prop, RemoteCacheServerAttributes rcsa )
329    {
330        // DEPRECATED CONFIG
331        String servicePortStr = prop.getProperty( REMOTE_CACHE_SERVICE_PORT );
332        if ( servicePortStr != null )
333        {
334            try
335            {
336                int servicePort = Integer.parseInt( servicePortStr );
337                rcsa.setServicePort( servicePort );
338                log.debug( "Remote cache service uses port number " + servicePort + "." );
339            }
340            catch ( NumberFormatException ignore )
341            {
342                log.debug( "Remote cache service port property " + REMOTE_CACHE_SERVICE_PORT
343                    + " not specified.  An anonymous port will be used." );
344            }
345        }
346
347        String socketTimeoutMillisStr = prop.getProperty( SOCKET_TIMEOUT_MILLIS );
348        if ( socketTimeoutMillisStr != null )
349        {
350            try
351            {
352                int rmiSocketFactoryTimeoutMillis = Integer.parseInt( socketTimeoutMillisStr );
353                rcsa.setRmiSocketFactoryTimeoutMillis( rmiSocketFactoryTimeoutMillis );
354                log.debug( "Remote cache socket timeout " + rmiSocketFactoryTimeoutMillis + "ms." );
355            }
356            catch ( NumberFormatException ignore )
357            {
358                log.debug( "Remote cache socket timeout property " + SOCKET_TIMEOUT_MILLIS
359                    + " not specified.  The default will be used." );
360            }
361        }
362
363        String lccStr = prop.getProperty( REMOTE_LOCAL_CLUSTER_CONSISTENCY );
364        if ( lccStr != null )
365        {
366            boolean lcc = Boolean.parseBoolean( lccStr );
367            rcsa.setLocalClusterConsistency( lcc );
368        }
369
370        String acgStr = prop.getProperty( REMOTE_ALLOW_CLUSTER_GET );
371        if ( acgStr != null )
372        {
373            boolean acg = Boolean.parseBoolean( lccStr );
374            rcsa.setAllowClusterGet( acg );
375        }
376
377        // Register the RemoteCacheServer remote object in the registry.
378        rcsa.setRemoteServiceName( prop.getProperty( REMOTE_CACHE_SERVICE_NAME, REMOTE_CACHE_SERVICE_VAL ).trim() );
379    }
380
381    /**
382     * Unbinds the remote server.
383     * <p>
384     * @param host
385     * @param port
386     * @throws IOException
387     */
388    static void shutdownImpl( String host, int port )
389        throws IOException
390    {
391        synchronized ( RemoteCacheServer.class )
392        {
393            if ( remoteCacheServer == null )
394            {
395                return;
396            }
397            log.info( "Unbinding host=" + host + ", port=" + port + ", serviceName=" + getServiceName() );
398            try
399            {
400                Naming.unbind( RemoteUtils.getNamingURL(host, port, getServiceName()) );
401            }
402            catch ( MalformedURLException ex )
403            {
404                // impossible case.
405                throw new IllegalArgumentException( ex.getMessage() + "; host=" + host + ", port=" + port
406                    + ", serviceName=" + getServiceName() );
407            }
408            catch ( NotBoundException ex )
409            {
410                // ignore.
411            }
412            remoteCacheServer.release();
413            remoteCacheServer = null;
414
415            // Shut down keepalive scheduler
416            if ( keepAliveDaemon != null )
417            {
418                keepAliveDaemon.shutdownNow();
419                keepAliveDaemon = null;
420            }
421
422            // Try to release registry
423            if (registry != null)
424            {
425                UnicastRemoteObject.unexportObject(registry, true);
426                registry = null;
427            }
428        }
429    }
430
431    /**
432     * Creates an local RMI registry on the default port, starts up the remote cache server, and
433     * binds it to the registry.
434     * <p>
435     * A remote cache is either a local cache or a cluster cache.
436     * <p>
437     * @param args The command line arguments
438     * @throws Exception
439     */
440    public static void main( String[] args )
441        throws Exception
442    {
443        Properties prop = args.length > 0 ? RemoteUtils.loadProps( args[args.length - 1] ) : new Properties();
444
445        int port;
446        try
447        {
448            port = Integer.parseInt( prop.getProperty( "registry.port" ) );
449        }
450        catch ( NumberFormatException ex )
451        {
452            port = Registry.REGISTRY_PORT;
453        }
454
455        // shutdown
456        if ( args.length > 0 && args[0].toLowerCase().indexOf( "-shutdown" ) != -1 )
457        {
458            try
459            {
460                ICacheServiceAdmin admin = lookupCacheServiceAdmin(prop, port);
461                admin.shutdown();
462            }
463            catch ( Exception ex )
464            {
465                log.error( "Problem calling shutdown.", ex );
466            }
467            log.debug( "done." );
468            System.exit( 0 );
469        }
470
471        // STATS
472        if ( args.length > 0 && args[0].toLowerCase().indexOf( "-stats" ) != -1 )
473        {
474            log.debug( "getting cache stats" );
475
476            try
477            {
478                ICacheServiceAdmin admin = lookupCacheServiceAdmin(prop, port);
479
480                try
481                {
482//                    System.out.println( admin.getStats().toString() );
483                    log.debug( admin.getStats() );
484                }
485                catch ( IOException es )
486                {
487                    log.error( es );
488                }
489            }
490            catch ( Exception ex )
491            {
492                log.error( "Problem getting stats.", ex );
493            }
494            log.debug( "done." );
495            System.exit( 0 );
496        }
497
498        // startup.
499        String host = prop.getProperty( "registry.host" );
500
501        if ( host == null || host.trim().equals( "" ) || host.trim().equals( "localhost" ) )
502        {
503            log.debug( "main> creating registry on the localhost" );
504            RemoteUtils.createRegistry( port );
505        }
506        log.debug( "main> starting up RemoteCacheServer" );
507        startup( host, port, prop);
508        log.debug( "main> done" );
509    }
510
511    /**
512     * Look up the remote cache service admin instance
513     *
514     * @param config the configuration properties
515     * @param port the local port
516     * @return the admin object instance
517     *
518     * @throws Exception if lookup fails
519     */
520    private static ICacheServiceAdmin lookupCacheServiceAdmin(Properties config, int port) throws Exception
521    {
522        String remoteServiceName = config.getProperty( REMOTE_CACHE_SERVICE_NAME, REMOTE_CACHE_SERVICE_VAL ).trim();
523        String registry = RemoteUtils.getNamingURL("", port, remoteServiceName);
524
525        if ( log.isDebugEnabled() )
526        {
527            log.debug( "looking up server " + registry );
528        }
529        Object obj = Naming.lookup( registry );
530        if ( log.isDebugEnabled() )
531        {
532            log.debug( "server found" );
533        }
534
535        return (ICacheServiceAdmin) obj;
536    }
537
538    /**
539     * @param serviceName the serviceName to set
540     */
541    protected static void setServiceName( String serviceName )
542    {
543        RemoteCacheServerFactory.serviceName = serviceName;
544    }
545
546    /**
547     * @return the serviceName
548     */
549    protected static String getServiceName()
550    {
551        return serviceName;
552    }
553}