View Javadoc
1   package org.apache.commons.jcs.auxiliary.remote.server;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.IOException;
23  import java.io.Serializable;
24  import java.net.MalformedURLException;
25  import java.rmi.Naming;
26  import java.rmi.NotBoundException;
27  import java.rmi.Remote;
28  import java.rmi.RemoteException;
29  import java.rmi.registry.Registry;
30  import java.rmi.server.RMISocketFactory;
31  import java.rmi.server.UnicastRemoteObject;
32  import java.util.Properties;
33  import java.util.concurrent.Executors;
34  import java.util.concurrent.ScheduledExecutorService;
35  import java.util.concurrent.TimeUnit;
36  
37  import org.apache.commons.jcs.auxiliary.AuxiliaryCacheConfigurator;
38  import org.apache.commons.jcs.auxiliary.remote.RemoteUtils;
39  import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheConstants;
40  import org.apache.commons.jcs.engine.behavior.ICacheServiceAdmin;
41  import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger;
42  import org.apache.commons.jcs.utils.config.OptionConverter;
43  import org.apache.commons.jcs.utils.config.PropertySetter;
44  import org.apache.commons.jcs.utils.threadpool.DaemonThreadFactory;
45  import org.apache.commons.logging.Log;
46  import org.apache.commons.logging.LogFactory;
47  
48  /**
49   * Provides remote cache services. This creates remote cache servers and can proxy command line
50   * requests to a running server.
51   */
52  public class RemoteCacheServerFactory
53      implements IRemoteCacheConstants
54  {
55      /** The logger */
56      private static final Log log = LogFactory.getLog( RemoteCacheServerFactory.class );
57  
58      /** The single instance of the RemoteCacheServer object. */
59      private static RemoteCacheServer<?, ?> remoteCacheServer;
60  
61      /** The name of the service. */
62      private static String serviceName = IRemoteCacheConstants.REMOTE_CACHE_SERVICE_VAL;
63  
64      /** Executes the registry keep alive. */
65      private static ScheduledExecutorService keepAliveDaemon;
66  
67      /** A reference to the registry. */
68      private static Registry registry = null;
69  
70      /** Constructor for the RemoteCacheServerFactory object. */
71      private RemoteCacheServerFactory()
72      {
73          super();
74      }
75  
76      /**
77       * This will allow you to get stats from the server, etc. Perhaps we should provide methods on
78       * the factory to do this instead.
79       * <p>
80       * A remote cache is either a local cache or a cluster cache.
81       * </p>
82       * @return Returns the remoteCacheServer.
83       */
84      @SuppressWarnings("unchecked") // Need cast to specific RemoteCacheServer
85      public static <K, V> RemoteCacheServer<K, V> getRemoteCacheServer()
86      {
87          return (RemoteCacheServer<K, V>)remoteCacheServer;
88      }
89  
90      // ///////////////////// Startup/shutdown methods. //////////////////
91      /**
92       * Starts up the remote cache server on this JVM, and binds it to the registry on the given host
93       * and port.
94       * <p>
95       * A remote cache is either a local cache or a cluster cache.
96       * <p>
97       * @param host the host name
98       * @param port the port number
99       * @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 }