001package org.apache.commons.jcs3.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.net.InetAddress;
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.Objects;
033import java.util.Properties;
034import java.util.concurrent.Executors;
035import java.util.concurrent.ScheduledExecutorService;
036import java.util.concurrent.TimeUnit;
037
038import org.apache.commons.jcs3.auxiliary.AuxiliaryCacheConfigurator;
039import org.apache.commons.jcs3.auxiliary.remote.RemoteUtils;
040import org.apache.commons.jcs3.auxiliary.remote.behavior.IRemoteCacheConstants;
041import org.apache.commons.jcs3.engine.behavior.ICacheServiceAdmin;
042import org.apache.commons.jcs3.engine.logging.behavior.ICacheEventLogger;
043import org.apache.commons.jcs3.log.Log;
044import org.apache.commons.jcs3.log.LogManager;
045import org.apache.commons.jcs3.utils.config.OptionConverter;
046import org.apache.commons.jcs3.utils.config.PropertySetter;
047import org.apache.commons.jcs3.utils.threadpool.DaemonThreadFactory;
048
049/**
050 * Provides remote cache services. This creates remote cache servers and can proxy command line
051 * requests to a running server.
052 */
053public class RemoteCacheServerFactory
054    implements IRemoteCacheConstants
055{
056    /** The logger */
057    private static final Log log = LogManager.getLog( RemoteCacheServerFactory.class );
058
059    /** The single instance of the RemoteCacheServer object. */
060    private static RemoteCacheServer<?, ?> remoteCacheServer;
061
062    /** The name of the service. */
063    private static String serviceName = IRemoteCacheConstants.REMOTE_CACHE_SERVICE_VAL;
064
065    /** Executes the registry keep alive. */
066    private static ScheduledExecutorService keepAliveDaemon;
067
068    /** A reference to the registry. */
069    private static Registry registry;
070
071    /** Constructor for the RemoteCacheServerFactory object. */
072    private RemoteCacheServerFactory()
073    {
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
098     * @param port
099     * @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}