View Javadoc

1   package org.apache.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.net.MalformedURLException;
24  import java.rmi.Naming;
25  import java.rmi.NotBoundException;
26  import java.rmi.RemoteException;
27  import java.rmi.registry.LocateRegistry;
28  import java.rmi.registry.Registry;
29  import java.rmi.server.RMIClientSocketFactory;
30  import java.rmi.server.RMISocketFactory;
31  import java.util.Properties;
32  import java.util.concurrent.Executors;
33  import java.util.concurrent.ScheduledExecutorService;
34  import java.util.concurrent.ThreadFactory;
35  import java.util.concurrent.TimeUnit;
36  
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  import org.apache.jcs.auxiliary.AuxiliaryCacheConfigurator;
40  import org.apache.jcs.auxiliary.remote.RemoteUtils;
41  import org.apache.jcs.auxiliary.remote.behavior.IRemoteCacheConstants;
42  import org.apache.jcs.auxiliary.remote.behavior.IRemoteCacheServiceAdmin;
43  import org.apache.jcs.engine.logging.behavior.ICacheEventLogger;
44  import org.apache.jcs.utils.config.OptionConverter;
45  import org.apache.jcs.utils.config.PropertySetter;
46  
47  /**
48   * Provides remote cache services. This creates remote cache servers and can proxy command line
49   * requests to a running server.
50   */
51  public class RemoteCacheServerFactory
52      implements IRemoteCacheConstants
53  {
54      /** The logger */
55      private final static Log log = LogFactory.getLog( RemoteCacheServerFactory.class );
56  
57      /** The single instance of the RemoteCacheServer object. */
58      private static RemoteCacheServer remoteCacheServer;
59  
60      /** The name of the service. */
61      private static String serviceName = IRemoteCacheConstants.REMOTE_CACHE_SERVICE_VAL;
62  
63      /** Executes the registry keep alive. */
64      private static ScheduledExecutorService keepAliveDaemon;
65  
66      /** Constructor for the RemoteCacheServerFactory object. */
67      private RemoteCacheServerFactory()
68      {
69          super();
70      }
71  
72      /**
73       * This will allow you to get stats from the server, etc. Perhaps we should provide methods on
74       * the factory to do this instead.
75       * <p>
76       * A remote cache is either a local cache or a cluster cache. <p.
77       * @return Returns the remoteCacheServer.
78       */
79      public static RemoteCacheServer getRemoteCacheServer()
80      {
81          return remoteCacheServer;
82      }
83  
84      // ///////////////////// Startup/shutdown methods. //////////////////
85      /**
86       * Starts up the remote cache server on this JVM, and binds it to the registry on the given host
87       * and port.
88       * <p>
89       * A remote cache is either a local cache or a cluster cache.
90       * <p>
91       * @param host
92       * @param port
93       * @param propFile
94       * @throws IOException
95       */
96      public static void startup( String host, int port, String propFile )
97          throws IOException
98      {
99          if ( remoteCacheServer != null )
100         {
101             throw new IllegalArgumentException( "Server already started." );
102         }
103 
104         synchronized ( RemoteCacheServer.class )
105         {
106             if ( remoteCacheServer != null )
107             {
108                 return;
109             }
110             if ( host == null )
111             {
112                 host = "";
113             }
114             if ( log.isInfoEnabled() )
115             {
116                 log.info( "ConfigFileName = [" + propFile + "]" );
117             }
118             Properties props = RemoteUtils.loadProps( propFile );
119             RemoteCacheServerAttributes rcsa = configureServerAttributes( propFile );
120             // These should come from the file!
121             rcsa.setRemotePort( port );
122             rcsa.setRemoteHost( host );
123             if ( log.isInfoEnabled() )
124             {
125                 log.info( "Creating server with these attributes: " + rcsa );
126             }
127 
128             setServiceName( rcsa.getRemoteServiceName() );
129 
130             RMISocketFactory customRMISocketFactory = configureObjectSpecificCustomFactory( props );
131 
132             RemoteUtils.configureGlobalCustomSocketFactory( rcsa.getRmiSocketFactoryTimeoutMillis() );
133 
134             // CONFIGURE THE EVENT LOGGER
135             ICacheEventLogger cacheEventLogger = configureCacheEventLogger( props );
136 
137             // CREATE SERVER
138             if ( customRMISocketFactory != null )
139             {
140                 remoteCacheServer = new RemoteCacheServer( rcsa, customRMISocketFactory );
141             }
142             else
143             {
144                 remoteCacheServer = new RemoteCacheServer( rcsa );
145             }
146             remoteCacheServer.setCacheEventLogger( cacheEventLogger );
147 
148             // START THE REGISTRY
149             startTheRegistry( port, rcsa );
150 
151             // REGISTER THE SERVER
152             registerServer( host, port, serviceName );
153 
154             // KEEP THE REGISTRY ALIVE
155             if ( rcsa.isUseRegistryKeepAlive() )
156             {
157                 if ( keepAliveDaemon == null )
158                 {
159                     keepAliveDaemon = Executors.newScheduledThreadPool(1, new MyThreadFactory());
160                 }
161                 RegistryKeepAliveRunner runner = new RegistryKeepAliveRunner( host, port, serviceName );
162                 runner.setCacheEventLogger( cacheEventLogger );
163                 keepAliveDaemon.scheduleAtFixedRate(runner, 0, rcsa.getRegistryKeepAliveDelayMillis(), TimeUnit.MILLISECONDS);
164             }
165         }
166     }
167 
168     /**
169      * Tries to get the event logger by new and old config styles.
170      * <p>
171      * @param props
172      * @return ICacheEventLogger
173      */
174     protected static ICacheEventLogger configureCacheEventLogger( Properties props )
175     {
176         ICacheEventLogger cacheEventLogger = AuxiliaryCacheConfigurator
177             .parseCacheEventLogger( props, IRemoteCacheConstants.CACHE_SERVER_PREFIX );
178 
179         // try the old way
180         if ( cacheEventLogger == null )
181         {
182             cacheEventLogger = AuxiliaryCacheConfigurator.parseCacheEventLogger( props,
183                                                                                  IRemoteCacheConstants.PROPERTY_PREFIX );
184         }
185         return cacheEventLogger;
186     }
187 
188     /**
189      * This configures an object specific custom factory. This will be configured for just this
190      * object in the registry. This can be null.
191      * <p>
192      * @param props
193      * @return RMISocketFactory
194      */
195     protected static RMISocketFactory configureObjectSpecificCustomFactory( Properties props )
196     {
197         RMISocketFactory customRMISocketFactory = (RMISocketFactory) OptionConverter
198             .instantiateByKey( props, CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX, RMIClientSocketFactory.class, null );
199 
200         if ( customRMISocketFactory != null )
201         {
202             PropertySetter.setProperties( customRMISocketFactory, props, CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX
203                 + "." );
204             if ( log.isInfoEnabled() )
205             {
206                 log.info( "Will use server specific custom socket factory. " + customRMISocketFactory );
207             }
208         }
209         else
210         {
211             if ( log.isInfoEnabled() )
212             {
213                 log.info( "No server specific custom socket factory defined." );
214             }
215         }
216         return customRMISocketFactory;
217     }
218 
219     /**
220      * Starts the registry if needed
221      * <p>
222      * @param registryPort
223      * @param rcsa
224      */
225     private static void startTheRegistry( int registryPort, RemoteCacheServerAttributes rcsa )
226     {
227         if ( rcsa.isStartRegistry() )
228         {
229             try
230             {
231                 LocateRegistry.createRegistry( registryPort );
232             }
233             catch ( RemoteException e )
234             {
235                 log.warn( "Problem creating registry.  It may already be started. " + e.getMessage() );
236             }
237             catch ( Throwable t )
238             {
239                 log.error( "Problem creating registry.", t );
240             }
241         }
242     }
243 
244     /**
245      * Registers the server with the registry. I broke this off because we might want to have code
246      * that will restart a dead registry. It will need to rebind the server.
247      * <p>
248      * @param host
249      * @param port
250      * @param serviceName
251      * @throws RemoteException
252      */
253     protected static void registerServer( String host, int port, String serviceName )
254         throws RemoteException
255     {
256         if ( remoteCacheServer == null )
257         {
258             String message = "Cannot register the server until it is created.  Please start the server first.";
259             log.error( message );
260             throw new RemoteException( message );
261         }
262 
263         if ( log.isInfoEnabled() )
264         {
265             log.info( "Binding server to " + host + ":" + port + " with the name " + serviceName );
266         }
267         try
268         {
269             Naming.rebind( "//" + host + ":" + port + "/" + serviceName, remoteCacheServer );
270         }
271         catch ( MalformedURLException ex )
272         {
273             // impossible case.
274             throw new IllegalArgumentException( ex.getMessage() + "; host=" + host + ", port=" + port );
275         }
276     }
277 
278     /**
279      * Configures the RemoteCacheServerAttributes from the props file.
280      * <p>
281      * @param propFile
282      * @return RemoteCacheServerAttributes
283      * @throws IOException
284      */
285     protected static RemoteCacheServerAttributes configureServerAttributes( String propFile )
286         throws IOException
287     {
288         Properties prop = RemoteUtils.loadProps( propFile );
289         // Properties prop = PropertyLoader.loadProperties( propFile );
290 
291         RemoteCacheServerAttributes rcsa = configureRemoteCacheServerAttributes( prop );
292         rcsa.setConfigFileName( propFile );
293 
294         return rcsa;
295     }
296 
297     /**
298      * Configure.
299      * <p>
300      * jcs.remotecache.serverattributes.ATTRIBUTENAME=ATTRIBUTEVALUE
301      * <p>
302      * @param prop
303      * @return RemoteCacheServerAttributesconfigureRemoteCacheServerAttributes
304      */
305     protected static RemoteCacheServerAttributes configureRemoteCacheServerAttributes( Properties prop )
306     {
307         RemoteCacheServerAttributes rcsa = new RemoteCacheServerAttributes();
308 
309         // configure automatically
310         PropertySetter.setProperties( rcsa, prop, CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + "." );
311 
312         configureManuallyIfValuesArePresent( prop, rcsa );
313 
314         return rcsa;
315     }
316 
317     /**
318      * This looks for the old config values.
319      * <p>
320      * @param prop
321      * @param rcsa
322      */
323     private static void configureManuallyIfValuesArePresent( Properties prop, RemoteCacheServerAttributes rcsa )
324     {
325         // DEPRECATED CONFIG
326         String servicePortStr = prop.getProperty( REMOTE_CACHE_SERVICE_PORT );
327         if ( servicePortStr != null )
328         {
329             try
330             {
331                 int servicePort = Integer.parseInt( servicePortStr );
332                 rcsa.setServicePort( servicePort );
333                 log.debug( "Remote cache service uses port number " + servicePort + "." );
334             }
335             catch ( NumberFormatException ignore )
336             {
337                 log.debug( "Remote cache service port property " + REMOTE_CACHE_SERVICE_PORT
338                     + " not specified.  An anonymous port will be used." );
339             }
340         }
341 
342         String socketTimeoutMillisStr = prop.getProperty( SOCKET_TIMEOUT_MILLIS );
343         if ( socketTimeoutMillisStr != null )
344         {
345             try
346             {
347                 int rmiSocketFactoryTimeoutMillis = Integer.parseInt( socketTimeoutMillisStr );
348                 rcsa.setRmiSocketFactoryTimeoutMillis( rmiSocketFactoryTimeoutMillis );
349                 log.debug( "Remote cache socket timeout " + rmiSocketFactoryTimeoutMillis + "ms." );
350             }
351             catch ( NumberFormatException ignore )
352             {
353                 log.debug( "Remote cache socket timeout property " + SOCKET_TIMEOUT_MILLIS
354                     + " not specified.  The default will be used." );
355             }
356         }
357 
358         String lccStr = prop.getProperty( REMOTE_LOCAL_CLUSTER_CONSISTENCY );
359         if ( lccStr != null )
360         {
361             boolean lcc = Boolean.valueOf( lccStr ).booleanValue();
362             rcsa.setLocalClusterConsistency( lcc );
363         }
364 
365         String acgStr = prop.getProperty( REMOTE_ALLOW_CLUSTER_GET );
366         if ( acgStr != null )
367         {
368             boolean acg = Boolean.valueOf( acgStr ).booleanValue();
369             rcsa.setAllowClusterGet( acg );
370         }
371 
372         // Register the RemoteCacheServer remote object in the registry.
373         rcsa.setRemoteServiceName( prop.getProperty( REMOTE_CACHE_SERVICE_NAME, REMOTE_CACHE_SERVICE_VAL ).trim() );
374     }
375 
376     /**
377      * Unbinds the remote server.
378      * <p>
379      * @param host
380      * @param port
381      * @exception IOException
382      */
383     static void shutdownImpl( String host, int port )
384         throws IOException
385     {
386         synchronized ( RemoteCacheServer.class )
387         {
388             if ( remoteCacheServer == null )
389             {
390                 return;
391             }
392             log.info( "Unbinding host=" + host + ", port=" + port + ", serviceName=" + getServiceName() );
393             try
394             {
395                 Naming.unbind( "//" + host + ":" + port + "/" + getServiceName() );
396             }
397             catch ( MalformedURLException ex )
398             {
399                 // impossible case.
400                 throw new IllegalArgumentException( ex.getMessage() + "; host=" + host + ", port=" + port
401                     + ", serviceName=" + getServiceName() );
402             }
403             catch ( NotBoundException ex )
404             {
405                 // ignore.
406             }
407             remoteCacheServer.release();
408             remoteCacheServer = null;
409             // TODO: safer exit ?
410             try
411             {
412                 Thread.sleep( 2000 );
413             }
414             catch ( InterruptedException ex )
415             {
416                 // swallow
417             }
418             System.exit( 0 );
419         }
420     }
421 
422     /**
423      * Creates an local RMI registry on the default port, starts up the remote cache server, and
424      * binds it to the registry.
425      * <p>
426      * A remote cache is either a local cache or a cluster cache.
427      * <p>
428      * @param args The command line arguments
429      * @throws Exception
430      */
431     public static void main( String[] args )
432         throws Exception
433     {
434         Properties prop = args.length > 0 ? RemoteUtils.loadProps( args[args.length - 1] ) : new Properties();
435 
436         int port;
437         try
438         {
439             port = Integer.parseInt( prop.getProperty( "registry.port" ) );
440         }
441         catch ( NumberFormatException ex )
442         {
443             port = Registry.REGISTRY_PORT;
444         }
445 
446         // shutdown
447         if ( args.length > 0 && args[0].toLowerCase().indexOf( "-shutdown" ) != -1 )
448         {
449             String serviceName = prop.getProperty( REMOTE_CACHE_SERVICE_NAME, REMOTE_CACHE_SERVICE_VAL ).trim();
450             String registry = "//:" + port + "/" + serviceName;
451 
452             if ( log.isDebugEnabled() )
453             {
454                 log.debug( "looking up server " + registry );
455             }
456             Object obj = Naming.lookup( registry );
457             if ( log.isDebugEnabled() )
458             {
459                 log.debug( "server found" );
460             }
461             IRemoteCacheServiceAdmin admin = (IRemoteCacheServiceAdmin) obj;
462             try
463             {
464                 admin.shutdown();
465             }
466             catch ( Exception ex )
467             {
468                 log.error( "Problem calling shutdown.", ex );
469             }
470             log.debug( "done." );
471             System.exit( 0 );
472         }
473 
474         // STATS
475         if ( args.length > 0 && args[0].toLowerCase().indexOf( "-stats" ) != -1 )
476         {
477 
478             log.debug( "getting cache stats" );
479 
480             try
481             {
482                 String serviceName = prop.getProperty( REMOTE_CACHE_SERVICE_NAME, REMOTE_CACHE_SERVICE_VAL ).trim();
483                 String registry = "//:" + port + "/" + serviceName;
484                 log.debug( "looking up server " + registry );
485                 Object obj = Naming.lookup( registry );
486                 log.debug( "server found" );
487 
488                 log.debug( "obj = " + obj );
489                 IRemoteCacheServiceAdmin admin = (IRemoteCacheServiceAdmin) obj;
490 
491                 try
492                 {
493                     System.out.println( admin.getStats().toString() );
494                     log.debug( admin.getStats() );
495                 }
496                 catch ( Exception es )
497                 {
498                     log.error( es );
499                 }
500 
501             }
502             catch ( Exception ex )
503             {
504                 log.error( "Problem getting stats.", ex );
505             }
506             log.debug( "done." );
507             System.exit( 0 );
508         }
509 
510         // startup.
511         String host = prop.getProperty( "registry.host" );
512 
513         if ( host == null || host.trim().equals( "" ) || host.trim().equals( "localhost" ) )
514         {
515             log.debug( "main> creating registry on the localhost" );
516             port = RemoteUtils.createRegistry( port );
517         }
518         log.debug( "main> starting up RemoteCacheServer" );
519         RemoteCacheServerFactory.startup( host, port, args.length > 0 ? args[0] : null );
520         log.debug( "main> done" );
521     }
522 
523     /**
524      * @param serviceName the serviceName to set
525      */
526     protected static void setServiceName( String serviceName )
527     {
528         RemoteCacheServerFactory.serviceName = serviceName;
529     }
530 
531     /**
532      * @return the serviceName
533      */
534     protected static String getServiceName()
535     {
536         return serviceName;
537     }
538 
539     /**
540      * Allows us to set the daemon status on the clockdaemon
541      */
542     protected static class MyThreadFactory
543         implements ThreadFactory
544     {
545         /**
546          * @param runner
547          * @return a new thread for the given Runnable
548          */
549         public Thread newThread( Runnable runner )
550         {
551             Thread t = new Thread( runner );
552             String oldName = t.getName();
553             t.setName( "JCS-RemoteCacheServerFactory-" + oldName );
554             t.setDaemon( true );
555             t.setPriority( Thread.MIN_PRIORITY );
556             return t;
557         }
558     }
559 }