View Javadoc
1   package org.apache.commons.jcs3.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.InetAddress;
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.Objects;
33  import java.util.Properties;
34  import java.util.concurrent.Executors;
35  import java.util.concurrent.ScheduledExecutorService;
36  import java.util.concurrent.TimeUnit;
37  
38  import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheConfigurator;
39  import org.apache.commons.jcs3.auxiliary.remote.RemoteUtils;
40  import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheConstants;
41  import org.apache.commons.jcs3.engine.behavior.ICacheServiceAdmin;
42  import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
43  import org.apache.commons.jcs3.log.Log;
44  import org.apache.commons.jcs3.log.LogManager;
45  import org.apache.commons.jcs3.utils.config.OptionConverter;
46  import org.apache.commons.jcs3.utils.config.PropertySetter;
47  import org.apache.commons.jcs3.utils.threadpool.DaemonThreadFactory;
48  
49  /**
50   * Provides remote cache services. This creates remote cache servers and can proxy command line
51   * requests to a running server.
52   */
53  public class RemoteCacheServerFactory
54      implements IRemoteCacheConstants
55  {
56      /** The logger */
57      private static final Log log = LogManager.getLog( RemoteCacheServerFactory.class );
58  
59      /** The single instance of the RemoteCacheServer object. */
60      private static RemoteCacheServer<?, ?> remoteCacheServer;
61  
62      /** The name of the service. */
63      private static String serviceName = IRemoteCacheConstants.REMOTE_CACHE_SERVICE_VAL;
64  
65      /** Executes the registry keep alive. */
66      private static ScheduledExecutorService keepAliveDaemon;
67  
68      /** A reference to the registry. */
69      private static Registry registry;
70  
71      /** Constructor for the RemoteCacheServerFactory object. */
72      private RemoteCacheServerFactory()
73      {
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
98       * @param port
99       * @param props
100      * @throws IOException
101      */
102     public static void startup( String host, final int port, final Properties props)
103         throws IOException
104     {
105         if ( remoteCacheServer != null )
106         {
107             throw new IllegalArgumentException( "Server already started." );
108         }
109 
110         synchronized ( RemoteCacheServer.class )
111         {
112             if ( remoteCacheServer != null )
113             {
114                 return;
115             }
116 
117             final RemoteCacheServerAttributes rcsa = configureRemoteCacheServerAttributes(props);
118 
119             // These should come from the file!
120             rcsa.setRemoteLocation( Objects.toString(host, ""), port );
121             log.info( "Creating server with these attributes: {0}", rcsa );
122 
123             setServiceName( rcsa.getRemoteServiceName() );
124 
125             final RMISocketFactory customRMISocketFactory = configureObjectSpecificCustomFactory( props );
126 
127             RemoteUtils.configureGlobalCustomSocketFactory( rcsa.getRmiSocketFactoryTimeoutMillis() );
128 
129             // CONFIGURE THE EVENT LOGGER
130             final ICacheEventLogger cacheEventLogger = configureCacheEventLogger( props );
131 
132             // CREATE SERVER
133             if ( customRMISocketFactory != null )
134             {
135                 remoteCacheServer = new RemoteCacheServer<>( rcsa, props, customRMISocketFactory );
136             }
137             else
138             {
139                 remoteCacheServer = new RemoteCacheServer<>( rcsa, props );
140             }
141 
142             remoteCacheServer.setCacheEventLogger( cacheEventLogger );
143 
144             // START THE REGISTRY
145         	registry = RemoteUtils.createRegistry(port);
146 
147             // REGISTER THE SERVER
148             registerServer( serviceName, remoteCacheServer );
149 
150             // KEEP THE REGISTRY ALIVE
151             if ( rcsa.isUseRegistryKeepAlive() )
152             {
153                 if ( keepAliveDaemon == null )
154                 {
155                     keepAliveDaemon = Executors.newScheduledThreadPool(1,
156                             new DaemonThreadFactory("JCS-RemoteCacheServerFactory-"));
157                 }
158                 keepAliveDaemon.scheduleAtFixedRate(() -> keepAlive(host, port, cacheEventLogger),
159                         0, rcsa.getRegistryKeepAliveDelayMillis(), TimeUnit.MILLISECONDS);
160             }
161         }
162     }
163 
164     /**
165      * Tries to lookup the server. If unsuccessful it will rebind the server using the factory
166      * rebind method.
167      *
168      * @param registryHost - Hostname of the registry
169      * @param registryPort - the port on which to start the registry
170      * @param cacheEventLogger the event logger for error messages
171      * @since 3.1
172      */
173     protected static void keepAlive(String registryHost, int registryPort, ICacheEventLogger cacheEventLogger)
174     {
175         String namingURL = RemoteUtils.getNamingURL(registryHost, registryPort, serviceName);
176         log.debug( "looking up server {0}", namingURL );
177 
178         try
179         {
180             final Object obj = Naming.lookup( namingURL );
181 
182             // Successful connection to the remote server.
183             final String message = "RMI registry looks fine.  Found [" + obj + "] in registry [" + namingURL + "]";
184             if ( cacheEventLogger != null )
185             {
186                 cacheEventLogger.logApplicationEvent( "RegistryKeepAliveRunner", "Naming.lookup", message );
187             }
188             log.debug( message );
189         }
190         catch ( final Exception ex )
191         {
192             // Failed to connect to the remote server.
193             final String message = "Problem finding server at [" + namingURL
194                 + "].  Will attempt to start registry and rebind.";
195             log.error( message, ex );
196             if ( cacheEventLogger != null )
197             {
198                 cacheEventLogger.logError( "RegistryKeepAliveRunner", "Naming.lookup", message + ":" + ex.getMessage() );
199             }
200 
201             registry = RemoteUtils.createRegistry(registryPort);
202 
203             if ( cacheEventLogger != null )
204             {
205                 if (registry != null)
206                 {
207                     cacheEventLogger.logApplicationEvent( "RegistryKeepAliveRunner", "createRegistry",
208                             "Successfully created registry [" + serviceName + "]." );
209                 }
210                 else
211                 {
212                     cacheEventLogger.logError( "RegistryKeepAliveRunner", "createRegistry",
213                             "Could not start registry [" + serviceName + "]." );
214                 }
215             }
216         }
217 
218         try
219         {
220             registerServer(serviceName, remoteCacheServer);
221 
222             final String message = "Successfully rebound server to registry [" + serviceName + "].";
223             if ( cacheEventLogger != null )
224             {
225                 cacheEventLogger.logApplicationEvent( "RegistryKeepAliveRunner", "registerServer", message );
226             }
227             log.info( message );
228         }
229         catch ( final RemoteException e )
230         {
231             final String message = "Could not rebind server to registry [" + serviceName + "].";
232             log.error( message, e );
233             if ( cacheEventLogger != null )
234             {
235                 cacheEventLogger.logError( "RegistryKeepAliveRunner", "registerServer", message + ":"
236                     + e.getMessage() );
237             }
238         }
239     }
240 
241     /**
242      * Tries to get the event logger.
243      * <p>
244      * @param props configuration properties
245      * @return ICacheEventLogger, may be null
246      */
247     protected static ICacheEventLogger configureCacheEventLogger( final Properties props )
248     {
249         return AuxiliaryCacheConfigurator
250                 .parseCacheEventLogger( props, IRemoteCacheConstants.CACHE_SERVER_PREFIX );
251     }
252 
253     /**
254      * This configures an object specific custom factory. This will be configured for just this
255      * object in the registry. This can be null.
256      * <p>
257      * @param props
258      * @return RMISocketFactory
259      */
260     protected static RMISocketFactory configureObjectSpecificCustomFactory( final Properties props )
261     {
262         final RMISocketFactory customRMISocketFactory =
263             OptionConverter.instantiateByKey( props, CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX, null );
264 
265         if ( customRMISocketFactory != null )
266         {
267             PropertySetter.setProperties( customRMISocketFactory, props, CUSTOM_RMI_SOCKET_FACTORY_PROPERTY_PREFIX
268                 + "." );
269             log.info( "Will use server specific custom socket factory. {0}",
270                     customRMISocketFactory );
271         }
272         else
273         {
274             log.info( "No server specific custom socket factory defined." );
275         }
276         return customRMISocketFactory;
277     }
278 
279     /**
280      * Registers the server with the registry. I broke this off because we might want to have code
281      * that will restart a dead registry. It will need to rebind the server.
282      * <p>
283      * @param serviceName the name of the service
284      * @param server the server object to bind
285      * @throws RemoteException
286      */
287     protected static void registerServer(final String serviceName, final Remote server )
288         throws RemoteException
289     {
290         if ( server == null )
291         {
292             throw new RemoteException( "Cannot register the server until it is created." );
293         }
294 
295         if ( registry == null )
296         {
297             throw new RemoteException( "Cannot register the server: Registry is null." );
298         }
299 
300         log.info( "Binding server to {0}", serviceName );
301 
302         registry.rebind( serviceName, server );
303     }
304 
305     /**
306      * Configure.
307      * <p>
308      * jcs.remotecache.serverattributes.ATTRIBUTENAME=ATTRIBUTEVALUE
309      * <p>
310      * @param prop
311      * @return RemoteCacheServerAttributesconfigureRemoteCacheServerAttributes
312      */
313     protected static RemoteCacheServerAttributes configureRemoteCacheServerAttributes( final Properties prop )
314     {
315         final RemoteCacheServerAttributes rcsa = new RemoteCacheServerAttributes();
316 
317         // configure automatically
318         PropertySetter.setProperties( rcsa, prop, CACHE_SERVER_ATTRIBUTES_PROPERTY_PREFIX + "." );
319 
320         return rcsa;
321     }
322 
323     /**
324      * Unbinds the remote server.
325      * <p>
326      * @param host
327      * @param port
328      * @throws IOException
329      */
330     static void shutdownImpl( final String host, final int port )
331         throws IOException
332     {
333         synchronized ( RemoteCacheServer.class )
334         {
335             if ( remoteCacheServer == null )
336             {
337                 return;
338             }
339             log.info( "Unbinding host={0}, port={1}, serviceName={2}",
340                     host, port, getServiceName() );
341             try
342             {
343                 Naming.unbind( RemoteUtils.getNamingURL(host, port, getServiceName()) );
344             }
345             catch ( final MalformedURLException ex )
346             {
347                 // impossible case.
348                 throw new IllegalArgumentException( ex.getMessage() + "; host=" + host + ", port=" + port
349                     + ", serviceName=" + getServiceName() );
350             }
351             catch ( final NotBoundException ex )
352             {
353                 // ignore.
354             }
355             remoteCacheServer.release();
356             remoteCacheServer = null;
357 
358             // Shut down keepalive scheduler
359             if ( keepAliveDaemon != null )
360             {
361                 keepAliveDaemon.shutdownNow();
362                 keepAliveDaemon = null;
363             }
364 
365             // Try to release registry
366             if (registry != null)
367             {
368             	UnicastRemoteObject.unexportObject(registry, true);
369             	registry = null;
370             }
371         }
372     }
373 
374     /**
375      * Creates an local RMI registry on the default port, starts up the remote cache server, and
376      * binds it to the registry.
377      * <p>
378      * A remote cache is either a local cache or a cluster cache.
379      * <p>
380      * @param args The command line arguments
381      * @throws Exception
382      */
383     public static void main( final String[] args )
384         throws Exception
385     {
386         final Properties prop = args.length > 0 ? RemoteUtils.loadProps( args[args.length - 1] ) : new Properties();
387 
388         int port;
389         try
390         {
391             port = Integer.parseInt( prop.getProperty( "registry.port" ) );
392         }
393         catch ( final NumberFormatException ex )
394         {
395             port = Registry.REGISTRY_PORT;
396         }
397 
398         // shutdown
399         if ( args.length > 0 && args[0].toLowerCase().indexOf( "-shutdown" ) != -1 )
400         {
401             try
402             {
403                 final ICacheServiceAdmin admin = lookupCacheServiceAdmin(prop, port);
404                 admin.shutdown();
405             }
406             catch ( final Exception ex )
407             {
408                 log.error( "Problem calling shutdown.", ex );
409             }
410             log.debug( "done." );
411             System.exit( 0 );
412         }
413 
414         // STATS
415         if ( args.length > 0 && args[0].toLowerCase().indexOf( "-stats" ) != -1 )
416         {
417             log.debug( "getting cache stats" );
418 
419             try
420             {
421                 final ICacheServiceAdmin admin = lookupCacheServiceAdmin(prop, port);
422 
423                 try
424                 {
425 //                    System.out.println( admin.getStats().toString() );
426                     log.debug( admin.getStats() );
427                 }
428                 catch ( final IOException es )
429                 {
430                     log.error( es );
431                 }
432             }
433             catch ( final Exception ex )
434             {
435                 log.error( "Problem getting stats.", ex );
436             }
437             log.debug( "done." );
438             System.exit( 0 );
439         }
440 
441         // startup.
442         final String hostName = prop.getProperty( "registry.host" );
443         final InetAddress host = InetAddress.getByName(hostName);
444 
445         if (host.isLoopbackAddress())
446         {
447             log.debug( "main> creating registry on the localhost" );
448             RemoteUtils.createRegistry( port );
449         }
450         log.debug( "main> starting up RemoteCacheServer" );
451         startup( host.getHostName(), port, prop);
452         log.debug( "main> done" );
453     }
454 
455     /**
456      * Look up the remote cache service admin instance
457      *
458      * @param config the configuration properties
459      * @param port the local port
460      * @return the admin object instance
461      *
462      * @throws Exception if lookup fails
463      */
464     private static ICacheServiceAdmin lookupCacheServiceAdmin(final Properties config, final int port) throws Exception
465     {
466         final String remoteServiceName = config.getProperty( REMOTE_CACHE_SERVICE_NAME, REMOTE_CACHE_SERVICE_VAL ).trim();
467         final String registry = RemoteUtils.getNamingURL("", port, remoteServiceName);
468 
469         log.debug( "looking up server {0}", registry );
470         final Object obj = Naming.lookup( registry );
471         log.debug( "server found" );
472 
473         return (ICacheServiceAdmin) obj;
474     }
475 
476     /**
477      * @param serviceName the serviceName to set
478      */
479     protected static void setServiceName( final String serviceName )
480     {
481         RemoteCacheServerFactory.serviceName = serviceName;
482     }
483 
484     /**
485      * @return the serviceName
486      */
487     protected static String getServiceName()
488     {
489         return serviceName;
490     }
491 }