package org.apache.commons.jcs.auxiliary.lateral.socket.tcp; /* * 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.util.ArrayList; import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.jcs.auxiliary.AbstractAuxiliaryCacheFactory; import org.apache.commons.jcs.auxiliary.AuxiliaryCacheAttributes; import org.apache.commons.jcs.auxiliary.lateral.LateralCache; import org.apache.commons.jcs.auxiliary.lateral.LateralCacheMonitor; import org.apache.commons.jcs.auxiliary.lateral.LateralCacheNoWait; import org.apache.commons.jcs.auxiliary.lateral.LateralCacheNoWaitFacade; import org.apache.commons.jcs.auxiliary.lateral.behavior.ILateralCacheListener; import org.apache.commons.jcs.auxiliary.lateral.socket.tcp.behavior.ITCPLateralCacheAttributes; 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.ICache; 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.behavior.IShutdownObserver; import org.apache.commons.jcs.engine.logging.behavior.ICacheEventLogger; import org.apache.commons.jcs.utils.discovery.UDPDiscoveryManager; import org.apache.commons.jcs.utils.discovery.UDPDiscoveryService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Constructs a LateralCacheNoWaitFacade for the given configuration. Each lateral service / local * relationship is managed by one manager. This manager can have multiple caches. The remote * relationships are consolidated and restored via these managers. * <p> * The facade provides a front to the composite cache so the implementation is transparent. */ public class LateralTCPCacheFactory extends AbstractAuxiliaryCacheFactory { /** The logger */ private static final Log log = LogFactory.getLog( LateralTCPCacheFactory.class ); /** Address to service map. */ private ConcurrentHashMap<String, ICacheServiceNonLocal<?, ?>> csnlInstances; /** Lock for initialization of address to service map */ private ReentrantLock csnlLock; /** Map of available discovery listener instances, keyed by port. */ private ConcurrentHashMap<String, LateralTCPDiscoveryListener> lTCPDLInstances; /** Monitor thread */ private LateralCacheMonitor monitor; /** * Wrapper of the lateral cache watch service; or wrapper of a zombie * service if failed to connect. */ private CacheWatchRepairable lateralWatch; /** * Creates a TCP lateral. * <p> * @param iaca * @param cacheMgr * @param cacheEventLogger * @param elementSerializer * @return LateralCacheNoWaitFacade */ @Override public <K, V> LateralCacheNoWaitFacade<K, V> createCache( AuxiliaryCacheAttributes iaca, ICompositeCacheManager cacheMgr, ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer ) { ITCPLateralCacheAttributes lac = (ITCPLateralCacheAttributes) iaca; ArrayList<ICache<K, V>> noWaits = new ArrayList<ICache<K, V>>(); // pairs up the tcp servers and set the tcpServer value and // get the manager and then get the cache // no servers are required. if ( lac.getTcpServers() != null ) { StringTokenizer it = new StringTokenizer( lac.getTcpServers(), "," ); if ( log.isDebugEnabled() ) { log.debug( "Configured for [" + it.countTokens() + "] servers." ); } while ( it.hasMoreElements() ) { String server = (String) it.nextElement(); if ( log.isDebugEnabled() ) { log.debug( "tcp server = " + server ); } ITCPLateralCacheAttributes lacC = (ITCPLateralCacheAttributes) lac.clone(); lacC.setTcpServer( server ); LateralCacheNoWait<K, V> lateralNoWait = createCacheNoWait(lacC, cacheEventLogger, elementSerializer); addListenerIfNeeded( lacC, cacheMgr ); monitor.addCache(lateralNoWait); noWaits.add( lateralNoWait ); } } ILateralCacheListener<K, V> listener = createListener( lac, cacheMgr ); // create the no wait facade. @SuppressWarnings("unchecked") // No generic arrays in java LateralCacheNoWait<K, V>[] lcnwArray = noWaits.toArray( new LateralCacheNoWait[0] ); LateralCacheNoWaitFacade<K, V> lcnwf = new LateralCacheNoWaitFacade<K, V>(listener, lcnwArray, lac ); // create udp discovery if available. createDiscoveryService( lac, lcnwf, cacheMgr, cacheEventLogger, elementSerializer ); return lcnwf; } protected <K, V> LateralCacheNoWait<K, V> createCacheNoWait( ITCPLateralCacheAttributes lca, ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer ) { ICacheServiceNonLocal<K, V> lateralService = getCSNLInstance(lca); LateralCache<K, V> cache = new LateralCache<K, V>( lca, lateralService, this.monitor ); cache.setCacheEventLogger( cacheEventLogger ); cache.setElementSerializer( elementSerializer ); if ( log.isDebugEnabled() ) { log.debug( "Created cache for noWait, cache [" + cache + "]" ); } LateralCacheNoWait<K, V> lateralNoWait = new LateralCacheNoWait<K, V>( cache ); lateralNoWait.setCacheEventLogger( cacheEventLogger ); lateralNoWait.setElementSerializer( elementSerializer ); if ( log.isInfoEnabled() ) { log.info( "Created LateralCacheNoWait for [" + lca + "] LateralCacheNoWait = [" + lateralNoWait + "]" ); } return lateralNoWait; } /** * Initialize this factory */ @Override public void initialize() { this.csnlInstances = new ConcurrentHashMap<String, ICacheServiceNonLocal<?, ?>>(); this.csnlLock = new ReentrantLock(); this.lTCPDLInstances = new ConcurrentHashMap<String, LateralTCPDiscoveryListener>(); // Create the monitoring daemon thread this.monitor = new LateralCacheMonitor(this); this.monitor.setDaemon( true ); this.monitor.start(); this.lateralWatch = new CacheWatchRepairable(); this.lateralWatch.setCacheWatch( new ZombieCacheWatch() ); } /** * Dispose of this factory, clean up shared resources */ @Override public void dispose() { for (ICacheServiceNonLocal<?, ?> service : this.csnlInstances.values()) { try { service.dispose(""); } catch (IOException e) { log.error("Could not dispose service " + service, e); } } this.csnlInstances.clear(); // TODO: shut down discovery listeners this.lTCPDLInstances.clear(); if (this.monitor != null) { this.monitor.notifyShutdown(); try { this.monitor.join(5000); } catch (InterruptedException e) { // swallow } this.monitor = null; } } /** * Returns an instance of the cache service. * <p> * @param lca configuration for the creation of a new service instance * * @return ICacheServiceNonLocal<K, V> */ // Need to cast because of common map for all cache services @SuppressWarnings("unchecked") public <K, V> ICacheServiceNonLocal<K, V> getCSNLInstance( ITCPLateralCacheAttributes lca ) { String key = lca.getTcpServer(); ICacheServiceNonLocal<K, V> service = (ICacheServiceNonLocal<K, V>)csnlInstances.get( key ); if ( service == null || service instanceof ZombieCacheServiceNonLocal ) { csnlLock.lock(); try { // double check service = (ICacheServiceNonLocal<K, V>)csnlInstances.get( key ); // If service creation did not succeed last time, force retry if ( service instanceof ZombieCacheServiceNonLocal) { service = null; log.info("Disposing of zombie service instance for [" + key + "]"); } if ( service == null ) { log.info( "Instance for [" + key + "] is null, creating" ); // Create the service try { if ( log.isInfoEnabled() ) { log.info( "Creating TCP service, lca = " + lca ); } service = new LateralTCPService<K, V>( lca ); } catch ( IOException ex ) { // Failed to connect to the lateral server. // Configure this LateralCacheManager instance to use the // "zombie" services. log.error( "Failure, lateral instance will use zombie service", ex ); service = new ZombieCacheServiceNonLocal<K, V>( lca.getZombieQueueMaxSize() ); // Notify the cache monitor about the error, and kick off // the recovery process. monitor.notifyError(); } csnlInstances.put( key, service ); } } finally { csnlLock.unlock(); } } return service; } /** * Gets the instance attribute of the LateralCacheTCPListener class. * <p> * @param ilca ITCPLateralCacheAttributes * @param cacheManager a reference to the global cache manager * * @return The instance value */ private LateralTCPDiscoveryListener getDiscoveryListener( ITCPLateralCacheAttributes ilca, ICompositeCacheManager cacheManager ) { String key = ilca.getUdpDiscoveryAddr() + ":" + ilca.getUdpDiscoveryPort(); LateralTCPDiscoveryListener ins = null; LateralTCPDiscoveryListener newListener = new LateralTCPDiscoveryListener( this.getName(), cacheManager); ins = lTCPDLInstances.putIfAbsent(key, newListener ); if ( ins == null ) { ins = newListener; if ( log.isInfoEnabled() ) { log.info( "Created new discovery listener for " + key + " cacheName for request " + ilca.getCacheName() ); } } return ins; } /** * Add listener for receivers * <p> * @param iaca cache configuration attributes * @param cacheMgr the composite cache manager */ private void addListenerIfNeeded( ITCPLateralCacheAttributes iaca, ICompositeCacheManager cacheMgr ) { // don't create a listener if we are not receiving. if ( iaca.isReceive() ) { try { addLateralCacheListener( iaca.getCacheName(), LateralTCPListener.getInstance( iaca, cacheMgr ) ); } catch ( IOException ioe ) { log.error( "Problem creating lateral listener", ioe ); } } else { if ( log.isDebugEnabled() ) { log.debug( "Not creating a listener since we are not receiving." ); } } } /** * Adds the lateral cache listener to the underlying cache-watch service. * <p> * @param cacheName The feature to be added to the LateralCacheListener attribute * @param listener The feature to be added to the LateralCacheListener attribute * @throws IOException */ private <K, V> void addLateralCacheListener( String cacheName, ILateralCacheListener<K, V> listener ) throws IOException { synchronized ( this.lateralWatch ) { lateralWatch.addCacheListener( cacheName, listener ); } } /** * Makes sure a listener gets created. It will get monitored as soon as it * is used. * <p> * This should be called by create cache. * <p> * @param attr ITCPLateralCacheAttributes * @param cacheMgr * * @return the listener if created, else null */ private <K, V> ILateralCacheListener<K, V> createListener( ITCPLateralCacheAttributes attr, ICompositeCacheManager cacheMgr ) { ILateralCacheListener<K, V> listener = null; // don't create a listener if we are not receiving. if ( attr.isReceive() ) { if ( log.isInfoEnabled() ) { log.info( "Getting listener for " + attr ); } // make a listener. if one doesn't exist listener = LateralTCPListener.getInstance( attr, cacheMgr ); // register for shutdown notification cacheMgr.registerShutdownObserver( (IShutdownObserver) listener ); } else { if ( log.isDebugEnabled() ) { log.debug( "Not creating a listener since we are not receiving." ); } } return listener; } /** * Creates the discovery service. Only creates this for tcp laterals right now. * <p> * @param lac ITCPLateralCacheAttributes * @param lcnwf * @param cacheMgr * @param cacheEventLogger * @param elementSerializer * @return null if none is created. */ private synchronized <K, V> UDPDiscoveryService createDiscoveryService( ITCPLateralCacheAttributes lac, LateralCacheNoWaitFacade<K, V> lcnwf, ICompositeCacheManager cacheMgr, ICacheEventLogger cacheEventLogger, IElementSerializer elementSerializer ) { UDPDiscoveryService discovery = null; // create the UDP discovery for the TCP lateral if ( lac.isUdpDiscoveryEnabled() ) { // One can be used for all regions LateralTCPDiscoveryListener discoveryListener = getDiscoveryListener( lac, cacheMgr ); discoveryListener.addNoWaitFacade( lac.getCacheName(), lcnwf ); // need a factory for this so it doesn't // get dereferenced, also we don't want one for every region. discovery = UDPDiscoveryManager.getInstance().getService( lac.getUdpDiscoveryAddr(), lac.getUdpDiscoveryPort(), lac.getTcpListenerPort(), cacheMgr); discovery.addParticipatingCacheName( lac.getCacheName() ); discovery.addDiscoveryListener( discoveryListener ); if ( log.isInfoEnabled() ) { log.info( "Registered TCP lateral cache [" + lac.getCacheName() + "] with UDPDiscoveryService." ); } } return discovery; } }