View Javadoc

1   package org.apache.jcs.auxiliary.remote;
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.io.Serializable;
24  import java.rmi.UnmarshalException;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Collections;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import org.apache.jcs.auxiliary.AbstractAuxiliaryCache;
36  import org.apache.jcs.auxiliary.AuxiliaryCacheAttributes;
37  import org.apache.jcs.auxiliary.remote.behavior.IRemoteCacheClient;
38  import org.apache.jcs.auxiliary.remote.behavior.IRemoteCacheService;
39  import org.apache.jcs.engine.CacheAdaptor;
40  import org.apache.jcs.engine.CacheConstants;
41  import org.apache.jcs.engine.CacheEventQueueFactory;
42  import org.apache.jcs.engine.behavior.ICacheElement;
43  import org.apache.jcs.engine.behavior.ICacheEventQueue;
44  import org.apache.jcs.engine.behavior.ICacheType;
45  import org.apache.jcs.engine.stats.StatElement;
46  import org.apache.jcs.engine.stats.Stats;
47  import org.apache.jcs.engine.stats.behavior.IStatElement;
48  import org.apache.jcs.engine.stats.behavior.IStats;
49  
50  /**
51   * The RemoteCacheNoWait wraps the RemoteCacheClient. The client holds a handle on the
52   * RemoteCacheService.
53   * <p>
54   * Used to queue up update requests to the underlying cache. These requests will be processed in
55   * their order of arrival via the cache event queue processor.
56   * <p>
57   * Typically errors will be handled down stream. We only need to kill the queue if an error makes it
58   * to this level from the queue. That can only happen if the queue is damaged, since the events are
59   * Processed asynchronously.
60   * <p>
61   * There is no reason to create a queue on startup if the remote is not healthy.
62   * <p>
63   * If the remote cache encounters an error it will zombie--create a balking facade for the service.
64   * The Zombie will queue up items until the connection is restored. An alternative way to accomplish
65   * the same thing would be to stop, not destroy the queue at this level. That way items would be
66   * added to the queue and then when the connection is restored, we could start the worker threads
67   * again. This is a better long term solution, but it requires some significant changes to the
68   * complicated worker queues.
69   */
70  public class RemoteCacheNoWait
71      extends AbstractAuxiliaryCache
72  {
73      /** For serialization. Don't change. */
74      private static final long serialVersionUID = -3104089136003714717L;
75  
76      /** log instance */
77      private final static Log log = LogFactory.getLog( RemoteCacheNoWait.class );
78  
79      /** The remote cache client */
80      private final IRemoteCacheClient remoteCacheClient;
81  
82      /** Event queue for queueing up calls like put and remove. */
83      private ICacheEventQueue cacheEventQueue;
84  
85      /** how many times get has been called. */
86      private int getCount = 0;
87  
88      /** how many times getMatching has been called. */
89      private int getMatchingCount = 0;
90  
91      /** how many times getMultiple has been called. */
92      private int getMultipleCount = 0;
93  
94      /** how many times remove has been called. */
95      private int removeCount = 0;
96  
97      /** how many times put has been called. */
98      private int putCount = 0;
99  
100     /**
101      * Constructs with the given remote cache, and fires up an event queue for asynchronous
102      * processing.
103      * <p>
104      * @param cache
105      */
106     public RemoteCacheNoWait( IRemoteCacheClient cache )
107     {
108         remoteCacheClient = cache;
109 
110         CacheEventQueueFactory factory = new CacheEventQueueFactory();
111         this.cacheEventQueue = factory.createCacheEventQueue( new CacheAdaptor( remoteCacheClient ), remoteCacheClient
112             .getListenerId(), remoteCacheClient.getCacheName(), remoteCacheClient.getAuxiliaryCacheAttributes()
113             .getEventQueuePoolName(), remoteCacheClient.getAuxiliaryCacheAttributes().getEventQueueType() );
114 
115         if ( remoteCacheClient.getStatus() == CacheConstants.STATUS_ERROR )
116         {
117             cacheEventQueue.destroy();
118         }
119     }
120 
121     /**
122      * Adds a put event to the queue.
123      * <p>
124      * @param element
125      * @throws IOException
126      */
127     public void update( ICacheElement element )
128         throws IOException
129     {
130         putCount++;
131         try
132         {
133             cacheEventQueue.addPutEvent( element );
134         }
135         catch ( IOException e )
136         {
137             log.error( "Problem adding putEvent to queue.", e );
138             cacheEventQueue.destroy();
139             throw e;
140         }
141     }
142 
143     /**
144      * Synchronously reads from the remote cache.
145      * <p>
146      * @param key
147      * @return element from the remote cache, or null if not present
148      * @throws IOException
149      */
150     public ICacheElement get( Serializable key )
151         throws IOException
152     {
153         getCount++;
154         try
155         {
156             return remoteCacheClient.get( key );
157         }
158         catch ( UnmarshalException ue )
159         {
160             if ( log.isDebugEnabled() )
161             {
162                 log.debug( "Retrying the get owing to UnmarshalException." );
163             }
164 
165             try
166             {
167                 return remoteCacheClient.get( key );
168             }
169             catch ( IOException ex )
170             {
171                 if ( log.isInfoEnabled() )
172                 {
173                     log.info( "Failed in retrying the get for the second time. " + ex.getMessage() );
174                 }
175             }
176         }
177         catch ( IOException ex )
178         {
179             // We don't want to destroy the queue on a get failure.
180             // The RemoteCache will Zombie and queue.
181             // Since get does not use the queue, I don't want to kill the queue.
182             throw ex;
183         }
184 
185         return null;
186     }
187 
188     /**
189      * @param pattern
190      * @return Map
191      * @throws IOException
192      *
193      */
194     public Map<Serializable, ICacheElement> getMatching( String pattern )
195         throws IOException
196     {
197         getMatchingCount++;
198         try
199         {
200             return remoteCacheClient.getMatching( pattern );
201         }
202         catch ( UnmarshalException ue )
203         {
204             if ( log.isDebugEnabled() )
205             {
206                 log.debug( "Retrying the getMatching owing to UnmarshalException." );
207             }
208 
209             try
210             {
211                 return remoteCacheClient.getMatching( pattern );
212             }
213             catch ( IOException ex )
214             {
215                 if ( log.isInfoEnabled() )
216                 {
217                     log.info( "Failed in retrying the getMatching for the second time. " + ex.getMessage() );
218                 }
219             }
220         }
221         catch ( IOException ex )
222         {
223             // We don't want to destroy the queue on a get failure.
224             // The RemoteCache will Zombie and queue.
225             // Since get does not use the queue, I don't want to kill the queue.
226             throw ex;
227         }
228 
229         return Collections.emptyMap();
230     }
231 
232     /**
233      * Gets multiple items from the cache based on the given set of keys. Sends the getMultiple
234      * request on to the server rather than looping through the requested keys.
235      * <p>
236      * @param keys
237      * @return a map of Serializable key to ICacheElement element, or an empty map if there is no
238      *         data in cache for any of these keys
239      * @throws IOException
240      */
241     public Map<Serializable, ICacheElement> getMultiple( Set<Serializable> keys )
242         throws IOException
243     {
244         getMultipleCount++;
245         try
246         {
247             return remoteCacheClient.getMultiple( keys );
248         }
249         catch ( UnmarshalException ue )
250         {
251             if ( log.isDebugEnabled() )
252             {
253                 log.debug( "Retrying the getMultiple owing to UnmarshalException..." );
254             }
255 
256             try
257             {
258                 return remoteCacheClient.getMultiple( keys );
259             }
260             catch ( IOException ex )
261             {
262                 if ( log.isInfoEnabled() )
263                 {
264                     log.info( "Failed in retrying the getMultiple for the second time. " + ex.getMessage() );
265                 }
266             }
267         }
268         catch ( IOException ex )
269         {
270             // We don't want to destroy the queue on a get failure.
271             // The RemoteCache will Zombie and queue.
272             // Since get does not use the queue, I don't want to kill the queue.
273             throw ex;
274         }
275 
276         return new HashMap<Serializable, ICacheElement>();
277     }
278 
279     /**
280      * @param groupName
281      * @return the keys for the group name
282      * @throws IOException
283      */
284     public Set<Serializable> getGroupKeys( String groupName )
285         throws IOException
286     {
287         return remoteCacheClient.getGroupKeys( groupName );
288     }
289 
290     /**
291      * Adds a remove request to the remote cache.
292      * <p>
293      * @param key
294      * @return if this was successful
295      * @throws IOException
296      */
297     public boolean remove( Serializable key )
298         throws IOException
299     {
300         removeCount++;
301         try
302         {
303             cacheEventQueue.addRemoveEvent( key );
304         }
305         catch ( IOException e )
306         {
307             log.error( "Problem adding RemoveEvent to queue.", e );
308             cacheEventQueue.destroy();
309             throw e;
310         }
311         return false;
312     }
313 
314     /**
315      * Adds a removeAll request to the remote cache.
316      * <p>
317      * @throws IOException
318      */
319     public void removeAll()
320         throws IOException
321     {
322         try
323         {
324             cacheEventQueue.addRemoveAllEvent();
325         }
326         catch ( IOException e )
327         {
328             log.error( "Problem adding RemoveAllEvent to queue.", e );
329             cacheEventQueue.destroy();
330             throw e;
331         }
332     }
333 
334     /** Adds a dispose request to the remote cache. */
335     public void dispose()
336     {
337         try
338         {
339             cacheEventQueue.addDisposeEvent();
340         }
341         catch ( IOException e )
342         {
343             log.error( "Problem adding DisposeEvent to queue.", e );
344             cacheEventQueue.destroy();
345         }
346     }
347 
348     /**
349      * No remote invocation.
350      * <p>
351      * @return The size value
352      */
353     public int getSize()
354     {
355         return remoteCacheClient.getSize();
356     }
357 
358     /**
359      * No remote invocation.
360      * <p>
361      * @return The cacheType value
362      */
363     public int getCacheType()
364     {
365         return ICacheType.REMOTE_CACHE;
366     }
367 
368     /**
369      * Returns the asyn cache status. An error status indicates either the remote connection is not
370      * available, or the asyn queue has been unexpectedly destroyed. No remote invocation.
371      * <p>
372      * @return The status value
373      */
374     public int getStatus()
375     {
376         return cacheEventQueue.isWorking() ? remoteCacheClient.getStatus() : CacheConstants.STATUS_ERROR;
377     }
378 
379     /**
380      * Gets the cacheName attribute of the RemoteCacheNoWait object
381      * <p>
382      * @return The cacheName value
383      */
384     public String getCacheName()
385     {
386         return remoteCacheClient.getCacheName();
387     }
388 
389     /**
390      * Replaces the remote cache service handle with the given handle and reset the event queue by
391      * starting up a new instance.
392      * <p>
393      * @param remote
394      */
395     public void fixCache( IRemoteCacheService remote )
396     {
397         remoteCacheClient.fixCache( remote );
398         resetEventQ();
399     }
400 
401     /**
402      * Resets the event q by first destroying the existing one and starting up new one.
403      * <p>
404      * There may be no good reason to kill the existing queue. We will sometimes need to set a new
405      * listener id, so we should create a new queue. We should let the old queue drain. If we were
406      * Connected to the failover, it would be best to finish sending items.
407      */
408     public void resetEventQ()
409     {
410         ICacheEventQueue previousQueue = cacheEventQueue;
411 
412         CacheEventQueueFactory fact = new CacheEventQueueFactory();
413         this.cacheEventQueue = fact.createCacheEventQueue( new CacheAdaptor( remoteCacheClient ), remoteCacheClient
414             .getListenerId(), remoteCacheClient.getCacheName(), remoteCacheClient.getAuxiliaryCacheAttributes()
415             .getEventQueuePoolName(), remoteCacheClient.getAuxiliaryCacheAttributes().getEventQueueType() );
416 
417         if ( previousQueue.isWorking() )
418         {
419             // we don't expect anything, it would have all gone to the zombie
420             if ( log.isInfoEnabled() )
421             {
422                 log.info( "resetEventQ, previous queue has [" + previousQueue.size() + "] items queued up." );
423             }
424             previousQueue.destroy();
425         }
426     }
427 
428     /**
429      * This is temporary. It allows the manager to get the lister.
430      * <p>
431      * @return the instance of the remote cache client used by this object
432      */
433     protected IRemoteCacheClient getRemoteCache()
434     {
435         return remoteCacheClient;
436     }
437 
438     /**
439      * @return Returns the AuxiliaryCacheAttributes.
440      */
441     public AuxiliaryCacheAttributes getAuxiliaryCacheAttributes()
442     {
443         return remoteCacheClient.getAuxiliaryCacheAttributes();
444     }
445 
446     /**
447      * This is for testing only. It allows you to take a look at the event queue.
448      * <p>
449      * @return ICacheEventQueue
450      */
451     protected ICacheEventQueue getCacheEventQueue()
452     {
453         return this.cacheEventQueue;
454     }
455 
456     /**
457      * Returns the stats and the cache.toString().
458      * <p>
459      * (non-Javadoc)
460      * @see java.lang.Object#toString()
461      */
462     @Override
463     public String toString()
464     {
465         return getStats() + "\n" + remoteCacheClient.toString();
466     }
467 
468     /**
469      * Returns the statistics in String form.
470      * <p>
471      * @return String
472      */
473     public String getStats()
474     {
475         return getStatistics().toString();
476     }
477 
478     /**
479      * @return statistics about this communication
480      */
481     public IStats getStatistics()
482     {
483         IStats stats = new Stats();
484         stats.setTypeName( "Remote Cache No Wait" );
485 
486         ArrayList<IStatElement> elems = new ArrayList<IStatElement>();
487 
488         IStatElement se = null;
489 
490         se = new StatElement();
491         se.setName( "Status" );
492         int status = this.getStatus();
493         if ( status == CacheConstants.STATUS_ERROR )
494         {
495             se.setData( "ERROR" );
496         }
497         else if ( status == CacheConstants.STATUS_ALIVE )
498         {
499             se.setData( "ALIVE" );
500         }
501         else if ( status == CacheConstants.STATUS_DISPOSED )
502         {
503             se.setData( "DISPOSED" );
504         }
505         else
506         {
507             se.setData( "" + status );
508         }
509         elems.add( se );
510 
511         // no data gathered here
512 
513         // get the stats from the cache queue too
514         // get as array, convert to list, add list to our outer list
515         IStats cStats = this.remoteCacheClient.getStatistics();
516         if ( cStats != null )
517         {
518             IStatElement[] cSEs = cStats.getStatElements();
519             List<IStatElement> cL = Arrays.asList( cSEs );
520             elems.addAll( cL );
521         }
522 
523         // get the stats from the event queue too
524         // get as array, convert to list, add list to our outer list
525         IStats eqStats = this.cacheEventQueue.getStatistics();
526         IStatElement[] eqSEs = eqStats.getStatElements();
527         List<IStatElement> eqL = Arrays.asList( eqSEs );
528         elems.addAll( eqL );
529 
530         se = new StatElement();
531         se.setName( "Get Count" );
532         se.setData( "" + this.getCount );
533         elems.add( se );
534 
535         se = new StatElement();
536         se.setName( "GetMatching Count" );
537         se.setData( "" + this.getMatchingCount );
538         elems.add( se );
539 
540         se = new StatElement();
541         se.setName( "GetMultiple Count" );
542         se.setData( "" + this.getMultipleCount );
543         elems.add( se );
544 
545         se = new StatElement();
546         se.setName( "Remove Count" );
547         se.setData( "" + this.removeCount );
548         elems.add( se );
549 
550         se = new StatElement();
551         se.setName( "Put Count" );
552         se.setData( "" + this.putCount );
553         elems.add( se );
554 
555         // get an array and put them in the Stats object
556         IStatElement[] ses = elems.toArray( new StatElement[elems.size()] );
557         stats.setStatElements( ses );
558 
559         return stats;
560     }
561 
562     /**
563      * this won't be called since we don't do ICache logging here.
564      * <p>
565      * @return String
566      */
567     @Override
568     public String getEventLoggingExtraInfo()
569     {
570         return "Remote Cache No Wait";
571     }
572 }