package org.apache.commons.jcs.auxiliary.remote; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import java.io.IOException; import java.rmi.Naming; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheAttributes; import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheClient; import org.apache.commons.jcs.auxiliary.remote.behavior.IRemoteCacheListener; import org.apache.commons.jcs.engine.CacheStatus; import org.apache.commons.jcs.engine.CacheWatchRepairable; import org.apache.commons.jcs.engine.ZombieCacheServiceNonLocal; import org.apache.commons.jcs.engine.ZombieCacheWatch; import org.apache.commons.jcs.engine.behavior.ICacheObserver; import org.apache.commons.jcs.engine.behavior.ICacheServiceNonLocal; import org.apache.commons.jcs.engine.behavior.ICompositeCacheManager; import org.apache.commons.jcs.engine.behavior.IElementSerializer; import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * An instance of RemoteCacheManager corresponds to one remote connection of a specific host and * port. All RemoteCacheManager instances are monitored by the singleton RemoteCacheMonitor * monitoring daemon for error detection and recovery. * <p> * Getting an instance of the remote cache has the effect of getting a handle on the remote server. * Listeners are not registered with the server until a cache is requested from the manager. */ public class RemoteCacheManager { /** The logger */ private static final Log log = LogFactory.getLog( RemoteCacheManager.class ); /** Contains instances of RemoteCacheNoWait managed by a RemoteCacheManager instance. */ private final ConcurrentMap<String, RemoteCacheNoWait<?, ?>> caches = new ConcurrentHashMap<String, RemoteCacheNoWait<?, ?>>(); /** Lock for initialization of caches */ private ReentrantLock cacheLock = new ReentrantLock(); /** The event logger. */ private final ICacheEventLogger cacheEventLogger; /** The serializer. */ private final IElementSerializer elementSerializer; /** Handle to the remote cache service; or a zombie handle if failed to connect. */ private ICacheServiceNonLocal<?, ?> remoteService; /** * Wrapper of the remote cache watch service; or wrapper of a zombie service if failed to * connect. */ private CacheWatchRepairable remoteWatch; /** The cache manager listeners will need to use to get a cache. */ private final ICompositeCacheManager cacheMgr; /** For error notification */ private final RemoteCacheMonitor monitor; /** The service found through lookup */ private final String registry; /** can it be restored */ private boolean canFix = true; /** * Constructs an instance to with the given remote connection parameters. If the connection * cannot be made, "zombie" services will be temporarily used until a successful re-connection * is made by the monitoring daemon. * <p> * @param cattr cache attributes * @param cacheMgr the cache hub * @param monitor the cache monitor thread for error notifications * @param cacheEventLogger * @param elementSerializer */ protected RemoteCacheManager( IRemoteCacheAttributes cattr, ICompositeCacheManager cacheMgr, RemoteCacheMonitor monitor, ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer) { this.cacheMgr = cacheMgr; this.monitor = monitor; this.cacheEventLogger = cacheEventLogger; this.elementSerializer = elementSerializer; this.remoteWatch = new CacheWatchRepairable(); this.registry = RemoteUtils.getNamingURL(cattr.getRemoteLocation(), cattr.getRemoteServiceName()); try { lookupRemoteService(); } catch (IOException e) { log.error("Could not find server", e); // Notify the cache monitor about the error, and kick off the // recovery process. monitor.notifyError(); } } /** * Lookup remote service from registry * @throws IOException if the remote service could not be found * */ protected void lookupRemoteService() throws IOException { if ( log.isInfoEnabled() ) { log.info( "Looking up server [" + registry + "]" ); } try { Object obj = Naming.lookup( registry ); if ( log.isInfoEnabled() ) { log.info( "Server found: " + obj ); } // Successful connection to the remote server. this.remoteService = (ICacheServiceNonLocal<?, ?>) obj; if ( log.isDebugEnabled() ) { log.debug( "Remote Service = " + remoteService ); } remoteWatch.setCacheWatch( (ICacheObserver) remoteService ); } catch ( Exception ex ) { // Failed to connect to the remote server. // Configure this RemoteCacheManager instance to use the "zombie" // services. this.remoteService = new ZombieCacheServiceNonLocal<String, String>(); remoteWatch.setCacheWatch( new ZombieCacheWatch() ); throw new IOException( "Problem finding server at [" + registry + "]", ex ); } } /** * Adds the remote cache listener to the underlying cache-watch service. * <p> * @param cattr The feature to be added to the RemoteCacheListener attribute * @param listener The feature to be added to the RemoteCacheListener attribute * @throws IOException */ public <K, V> void addRemoteCacheListener( IRemoteCacheAttributes cattr, IRemoteCacheListener<K, V> listener ) throws IOException { if ( cattr.isReceive() ) { if ( log.isInfoEnabled() ) { log.info( "The remote cache is configured to receive events from the remote server. " + "We will register a listener. remoteWatch = " + remoteWatch + " | IRemoteCacheListener = " + listener + " | cacheName " + cattr.getCacheName() ); } remoteWatch.addCacheListener( cattr.getCacheName(), listener ); } else { if ( log.isInfoEnabled() ) { log.info( "The remote cache is configured to NOT receive events from the remote server. " + "We will NOT register a listener." ); } } } /** * Removes a listener. When the primary recovers the failover must deregister itself for a * region. The failover runner will call this method to de-register. We do not want to deregister * all listeners to a remote server, in case a failover is a primary of another region. Having * one regions failover act as another servers primary is not currently supported. * <p> * @param cattr * @throws IOException */ public void removeRemoteCacheListener( IRemoteCacheAttributes cattr ) throws IOException { RemoteCacheNoWait<?, ?> cache = caches.get( cattr.getCacheName() ); if ( cache != null ) { removeListenerFromCache(cache); } else { if ( cattr.isReceive() ) { log.warn( "Trying to deregister Cache Listener that was never registered." ); } else if ( log.isDebugEnabled() ) { log.debug( "Since the remote cache is configured to not receive, " + "there is no listener to deregister." ); } } } // common helper method private void removeListenerFromCache(RemoteCacheNoWait<?, ?> cache) throws IOException { IRemoteCacheClient<?, ?> rc = cache.getRemoteCache(); if ( log.isDebugEnabled() ) { log.debug( "Found cache for [" + cache.getCacheName() + "], deregistering listener." ); } // could also store the listener for a server in the manager. IRemoteCacheListener<?, ?> listener = rc.getListener(); remoteWatch.removeCacheListener( cache.getCacheName(), listener ); } /** * Gets a RemoteCacheNoWait from the RemoteCacheManager. The RemoteCacheNoWait objects are * identified by the cache name value of the RemoteCacheAttributes object. * <p> * If the client is configured to register a listener, this call results on a listener being * created if one isn't already registered with the remote cache for this region. * <p> * @param cattr * @return The cache value */ @SuppressWarnings("unchecked") // Need to cast because of common map for all caches public <K, V> RemoteCacheNoWait<K, V> getCache( IRemoteCacheAttributes cattr ) { RemoteCacheNoWait<K, V> remoteCacheNoWait = (RemoteCacheNoWait<K, V>) caches.get( cattr.getCacheName() ); if ( remoteCacheNoWait == null ) { cacheLock.lock(); try { remoteCacheNoWait = (RemoteCacheNoWait<K, V>) caches.get( cattr.getCacheName() ); if (remoteCacheNoWait == null) { remoteCacheNoWait = newRemoteCacheNoWait(cattr); caches.put( cattr.getCacheName(), remoteCacheNoWait ); } } finally { cacheLock.unlock(); } } // might want to do some listener sanity checking here. return remoteCacheNoWait; } /** * Create new RemoteCacheNoWait instance * * @param cattr the cache configuration * @return the instance */ protected <K, V> RemoteCacheNoWait<K, V> newRemoteCacheNoWait(IRemoteCacheAttributes cattr) { RemoteCacheNoWait<K, V> remoteCacheNoWait; // create a listener first and pass it to the remotecache // sender. RemoteCacheListener<K, V> listener = null; try { listener = new RemoteCacheListener<K, V>( cattr, cacheMgr, elementSerializer ); addRemoteCacheListener( cattr, listener ); } catch ( IOException ioe ) { log.error( "Problem adding listener. Message: " + ioe.getMessage() + " | RemoteCacheListener = " + listener, ioe ); } catch ( Exception e ) { log.error( "Problem adding listener. Message: " + e.getMessage() + " | RemoteCacheListener = " + listener, e ); } IRemoteCacheClient<K, V> remoteCacheClient = new RemoteCache<K, V>( cattr, (ICacheServiceNonLocal<K, V>) remoteService, listener, monitor ); remoteCacheClient.setCacheEventLogger( cacheEventLogger ); remoteCacheClient.setElementSerializer( elementSerializer ); remoteCacheNoWait = new RemoteCacheNoWait<K, V>( remoteCacheClient ); remoteCacheNoWait.setCacheEventLogger( cacheEventLogger ); remoteCacheNoWait.setElementSerializer( elementSerializer ); return remoteCacheNoWait; } /** Shutdown all. */ public void release() { cacheLock.lock(); try { for (RemoteCacheNoWait<?, ?> c : caches.values()) { try { if ( log.isInfoEnabled() ) { log.info( "freeCache [" + c.getCacheName() + "]" ); } removeListenerFromCache(c); c.dispose(); } catch ( IOException ex ) { log.error( "Problem releasing " + c.getCacheName(), ex ); } } caches.clear(); } finally { cacheLock.unlock(); } } /** * Fixes up all the caches managed by this cache manager. */ public void fixCaches() { if ( !canFix ) { return; } if ( log.isInfoEnabled() ) { log.info( "Fixing caches. ICacheServiceNonLocal " + remoteService + " | IRemoteCacheObserver " + remoteWatch ); } for (RemoteCacheNoWait<?, ?> c : caches.values()) { if (c.getStatus() == CacheStatus.ERROR) { c.fixCache( remoteService ); } } if ( log.isInfoEnabled() ) { String msg = "Remote connection to " + registry + " resumed."; if ( cacheEventLogger != null ) { cacheEventLogger.logApplicationEvent( "RemoteCacheManager", "fix", msg ); } log.info( msg ); } } /** * Returns true if the connection to the remote host can be * successfully re-established. * <p> * @return true if we found a failover server */ public boolean canFixCaches() { try { lookupRemoteService(); } catch (IOException e) { log.error("Could not find server", e); canFix = false; } return canFix; } }