View Javadoc

1   package org.apache.jcs.auxiliary.disk;
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.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.concurrent.locks.ReentrantReadWriteLock;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.jcs.auxiliary.AbstractAuxiliaryCacheEventLogging;
35  import org.apache.jcs.auxiliary.AuxiliaryCache;
36  import org.apache.jcs.auxiliary.disk.behavior.IDiskCacheAttributes;
37  import org.apache.jcs.engine.CacheConstants;
38  import org.apache.jcs.engine.CacheEventQueueFactory;
39  import org.apache.jcs.engine.CacheInfo;
40  import org.apache.jcs.engine.behavior.ICache;
41  import org.apache.jcs.engine.behavior.ICacheElement;
42  import org.apache.jcs.engine.behavior.ICacheEventQueue;
43  import org.apache.jcs.engine.behavior.ICacheListener;
44  import org.apache.jcs.engine.stats.StatElement;
45  import org.apache.jcs.engine.stats.Stats;
46  import org.apache.jcs.engine.stats.behavior.IStatElement;
47  import org.apache.jcs.engine.stats.behavior.IStats;
48  
49  /**
50   * Abstract class providing a base implementation of a disk cache, which can be easily extended to
51   * implement a disk cache for a specific persistence mechanism.
52   * <p>
53   * When implementing the abstract methods note that while this base class handles most things, it
54   * does not acquire or release any locks. Implementations should do so as necessary. This is mainly
55   * done to minimize the time spent in critical sections.
56   * <p>
57   * Error handling in this class needs to be addressed. Currently if an exception is thrown by the
58   * persistence mechanism, this class destroys the event queue. Should it also destroy purgatory?
59   * Should it dispose itself?
60   */
61  public abstract class AbstractDiskCache
62      extends AbstractAuxiliaryCacheEventLogging
63      implements AuxiliaryCache, Serializable
64  {
65      /** Don't change. */
66      private static final long serialVersionUID = 6541664080877628324L;
67  
68      /** The logger */
69      protected static final Log log = LogFactory.getLog( AbstractDiskCache.class );
70  
71      /** Generic disk cache attributes */
72      private IDiskCacheAttributes diskCacheAttributes = null;
73  
74      /**
75       * Map where elements are stored between being added to this cache and actually spooled to disk.
76       * This allows puts to the disk cache to return quickly, and the more expensive operation of
77       * serializing the elements to persistent storage queued for later.
78       * <p>
79       * If the elements are pulled into the memory cache while the are still in purgatory, writing to
80       * disk can be canceled.
81       */
82      protected Map<Serializable, PurgatoryElement> purgatory = new HashMap<Serializable, PurgatoryElement>();
83  
84      /**
85       * The CacheEventQueue where changes will be queued for asynchronous updating of the persistent
86       * storage.
87       */
88      protected ICacheEventQueue cacheEventQueue;
89  
90      /**
91       * Indicates whether the cache is 'alive': initialized, but not yet disposed. Child classes must
92       * set this to true.
93       */
94      protected boolean alive = false;
95  
96      /** Every cache will have a name, subclasses must set this when they are initialized. */
97      protected String cacheName;
98  
99      /** DEBUG: Keeps a count of the number of purgatory hits for debug messages */
100     protected int purgHits = 0;
101 
102     /**
103      * We lock here, so that we cannot get an update after a remove all. an individual removal locks
104      * the item.
105      */
106     protected final ReentrantReadWriteLock removeAllLock = new ReentrantReadWriteLock();
107 
108     // ----------------------------------------------------------- constructors
109 
110     /**
111      * Construct the abstract disk cache, create event queues and purgatory. Child classes should
112      * set the alive flag to true after they are initialized.
113      * <p>
114      * @param attr
115      */
116     public AbstractDiskCache( IDiskCacheAttributes attr )
117     {
118         this.diskCacheAttributes = attr;
119 
120         this.cacheName = attr.getCacheName();
121 
122         // create queue
123         CacheEventQueueFactory fact = new CacheEventQueueFactory();
124         this.cacheEventQueue = fact.createCacheEventQueue( new MyCacheListener(), CacheInfo.listenerId, cacheName,
125                                                            diskCacheAttributes.getEventQueuePoolName(),
126                                                            diskCacheAttributes.getEventQueueType() );
127 
128         // create purgatory
129         initPurgatory();
130     }
131 
132     /**
133      * Purgatory size of -1 means to use a HashMap with no size limit. Anything greater will use an
134      * LRU map of some sort.
135      * <p>
136      * @TODO Currently setting this to 0 will cause nothing to be put to disk, since it will assume
137      *       that if an item is not in purgatory, then it must have been plucked. We should make 0
138      *       work, a way to not use purgatory.
139      */
140     private void initPurgatory()
141     {
142         // we need this so we can stop the updates from happening after a
143         // removeall
144         removeAllLock.writeLock().lock();
145 
146         try
147         {
148             if ( purgatory != null )
149             {
150                 synchronized ( purgatory )
151                 {
152                     if ( diskCacheAttributes.getMaxPurgatorySize() >= 0 )
153                     {
154                         purgatory = new LRUMapJCS<Serializable, PurgatoryElement>( diskCacheAttributes.getMaxPurgatorySize() );
155                     }
156                     else
157                     {
158                         purgatory = new HashMap<Serializable, PurgatoryElement>();
159                     }
160                 }
161             }
162             else
163             {
164                 if ( diskCacheAttributes.getMaxPurgatorySize() >= 0 )
165                 {
166                     purgatory = new LRUMapJCS<Serializable, PurgatoryElement>( diskCacheAttributes.getMaxPurgatorySize() );
167                 }
168                 else
169                 {
170                     purgatory = new HashMap<Serializable, PurgatoryElement>();
171                 }
172             }
173         }
174         finally
175         {
176             removeAllLock.writeLock().unlock();
177         }
178     }
179 
180     // ------------------------------------------------------- interface ICache
181 
182     /**
183      * Adds the provided element to the cache. Element will be added to purgatory, and then queued
184      * for later writing to the serialized storage mechanism.
185      * <p>
186      * An update results in a put event being created. The put event will call the handlePut method
187      * defined here. The handlePut method calls the implemented doPut on the child.
188      * <p>
189      * @param cacheElement
190      * @throws IOException
191      * @see org.apache.jcs.engine.behavior.ICache#update
192      */
193     @Override
194     public final void update( ICacheElement cacheElement )
195         throws IOException
196     {
197         if ( log.isDebugEnabled() )
198         {
199             log.debug( "Putting element in purgatory, cacheName: " + cacheName + ", key: " + cacheElement.getKey() );
200         }
201 
202         try
203         {
204             // Wrap the CacheElement in a PurgatoryElement
205             PurgatoryElement pe = new PurgatoryElement( cacheElement );
206 
207             // Indicates the the element is eligible to be spooled to disk,
208             // this will remain true unless the item is pulled back into
209             // memory.
210             pe.setSpoolable( true );
211 
212             // Add the element to purgatory
213             synchronized ( purgatory )
214             {
215                 purgatory.put( pe.getKey(), pe );
216             }
217 
218             // Queue element for serialization
219             cacheEventQueue.addPutEvent( pe );
220         }
221         catch ( IOException ex )
222         {
223             log.error( "Problem adding put event to queue.", ex );
224 
225             cacheEventQueue.destroy();
226         }
227     }
228 
229     /**
230      * Check to see if the item is in purgatory. If so, return it. If not, check to see if we have
231      * it on disk.
232      * <p>
233      * @param key
234      * @return ICacheElement or null
235      * @see AuxiliaryCache#get
236      */
237     @Override
238     public final ICacheElement get( Serializable key )
239     {
240         // If not alive, always return null.
241 
242         if ( !alive )
243         {
244             if ( log.isDebugEnabled() )
245             {
246                 log.debug( "get was called, but the disk cache is not alive." );
247             }
248             return null;
249         }
250 
251         PurgatoryElement pe = null;
252         synchronized ( purgatory )
253         {
254             pe = purgatory.get( key );
255         }
256 
257         // If the element was found in purgatory
258         if ( pe != null )
259         {
260             purgHits++;
261 
262             if ( log.isDebugEnabled() )
263             {
264                 if ( purgHits % 100 == 0 )
265                 {
266                     log.debug( "Purgatory hits = " + purgHits );
267                 }
268             }
269 
270             // Since the element will go back to the memory cache, we could set
271             // spoolable to false, which will prevent the queue listener from
272             // serializing the element. This would not match the disk cache
273             // behavior and the behavior of other auxiliaries. Gets never remove
274             // items from auxiliaries.
275             // Beyond consistency, the items should stay in purgatory and get
276             // spooled since the mem cache may be set to 0. If an item is
277             // active, it will keep getting put into purgatory and removed. The
278             // CompositeCache now does not put an item to memory from disk if
279             // the size is 0.
280             // Do not set spoolable to false. Just let it go to disk. This
281             // will allow the memory size = 0 setting to work well.
282 
283             if ( log.isDebugEnabled() )
284             {
285                 log.debug( "Found element in purgatory, cacheName: " + cacheName + ", key: " + key );
286             }
287 
288             return pe.cacheElement;
289         }
290 
291         // If we reach this point, element was not found in purgatory, so get
292         // it from the cache.
293         try
294         {
295             return doGet( key );
296         }
297         catch ( Exception e )
298         {
299             log.error( e );
300 
301             cacheEventQueue.destroy();
302         }
303 
304         return null;
305     }
306 
307     /**
308      * Gets items from the cache matching the given pattern. Items from memory will replace those
309      * from remote sources.
310      * <p>
311      * This only works with string keys. It's too expensive to do a toString on every key.
312      * <p>
313      * Auxiliaries will do their best to handle simple expressions. For instance, the JDBC disk
314      * cache will convert * to % and . to _
315      * <p>
316      * @param pattern
317      * @return a map of Serializable key to ICacheElement element, or an empty map if there is no
318      *         data matching the pattern.
319      * @throws IOException
320      */
321     @Override
322     public Map<Serializable, ICacheElement> getMatching( String pattern )
323         throws IOException
324     {
325         // Get the keys from purgatory
326         Object[] keyArray = null;
327 
328         // this avoids locking purgatory, but it uses more memory
329         synchronized ( purgatory )
330         {
331             keyArray = purgatory.keySet().toArray();
332         }
333 
334         Set<Serializable> matchingKeys = getKeyMatcher().getMatchingKeysFromArray( pattern, keyArray );
335 
336         // call getMultiple with the set
337         Map<Serializable, ICacheElement> result = processGetMultiple( matchingKeys );
338 
339         // Get the keys from disk
340         Map<Serializable, ICacheElement> diskMatches = doGetMatching( pattern );
341 
342         result.putAll( diskMatches );
343 
344         return result;
345     }
346 
347     /**
348      * Gets multiple items from the cache based on the given set of keys.
349      * <p>
350      * @param keys
351      * @return a map of Serializable key to ICacheElement element, or an empty map if there is no
352      *         data in cache for any of these keys
353      */
354     @Override
355     public Map<Serializable, ICacheElement> processGetMultiple(Set<Serializable> keys)
356     {
357         Map<Serializable, ICacheElement> elements = new HashMap<Serializable, ICacheElement>();
358 
359         if ( keys != null && !keys.isEmpty() )
360         {
361             for (Serializable key : keys)
362             {
363                 ICacheElement element = get( key );
364 
365                 if ( element != null )
366                 {
367                     elements.put( key, element );
368                 }
369             }
370         }
371 
372         return elements;
373     }
374 
375     /**
376      * The keys in a group.
377      * <p>
378      * (non-Javadoc)
379      * @see org.apache.jcs.auxiliary.AuxiliaryCache#getGroupKeys(java.lang.String)
380      */
381     public abstract Set<Serializable> getGroupKeys( String groupName );
382 
383     /**
384      * Removes are not queued. A call to remove is immediate.
385      * <p>
386      * @param key
387      * @return whether the item was present to be removed.
388      * @throws IOException
389      * @see org.apache.jcs.engine.behavior.ICache#remove
390      */
391     @Override
392     public final boolean remove( Serializable key )
393         throws IOException
394     {
395         PurgatoryElement pe = null;
396 
397         synchronized ( purgatory )
398         {
399             // I'm getting the object, so I can lock on the element
400             // Remove element from purgatory if it is there
401             pe = purgatory.get( key );
402         }
403 
404         if ( pe != null )
405         {
406             synchronized ( pe.getCacheElement() )
407             {
408                 synchronized ( purgatory )
409                 {
410                     purgatory.remove( key );
411                 }
412 
413                 // no way to remove from queue, just make sure it doesn't get on
414                 // disk and then removed right afterwards
415                 pe.setSpoolable( false );
416 
417                 // Remove from persistent store immediately
418                 doRemove( key );
419             }
420         }
421         else
422         {
423             // Remove from persistent store immediately
424             doRemove( key );
425         }
426 
427         return false;
428     }
429 
430     /**
431      * @throws IOException
432      * @see org.apache.jcs.engine.behavior.ICache#removeAll
433      */
434     @Override
435     public final void removeAll()
436         throws IOException
437     {
438         if ( this.diskCacheAttributes.isAllowRemoveAll() )
439         {
440             // Replace purgatory with a new empty hashtable
441             initPurgatory();
442 
443             // Remove all from persistent store immediately
444             doRemoveAll();
445         }
446         else
447         {
448             if ( log.isInfoEnabled() )
449             {
450                 log.info( "RemoveAll was requested but the request was not fulfilled: allowRemoveAll is set to false." );
451             }
452         }
453     }
454 
455     /**
456      * Adds a dispose request to the disk cache.
457      * <p>
458      * Disposal proceeds in several steps.
459      * <ol>
460      * <li>Prior to this call the Composite cache dumped the memory into the disk cache. If it is
461      * large then we need to wait for the event queue to finish.
462      * <li>Wait until the event queue is empty of until the configured ShutdownSpoolTimeLimit is
463      * reached.
464      * <li>Call doDispose on the concrete impl.
465      * </ol>
466      * @throws IOException
467      */
468     @Override
469     public final void dispose()
470         throws IOException
471     {
472         Runnable disR = new Runnable()
473         {
474             public void run()
475             {
476                 boolean keepGoing = true;
477                 long total = 0;
478                 long interval = 100;
479                 while ( keepGoing )
480                 {
481                     keepGoing = !cacheEventQueue.isEmpty();
482                     try
483                     {
484                         Thread.sleep( interval );
485                         total += interval;
486                         // log.info( "total = " + total );
487                     }
488                     catch ( InterruptedException e )
489                     {
490                         break;
491                     }
492                 }
493                 log.info( "No longer waiting for event queue to finish: " + cacheEventQueue.getStatistics() );
494             }
495         };
496         Thread t = new Thread( disR );
497         t.start();
498         // wait up to 60 seconds for dispose and then quit if not done.
499         try
500         {
501             t.join( this.diskCacheAttributes.getShutdownSpoolTimeLimit() * 1000 );
502         }
503         catch ( InterruptedException ex )
504         {
505             log.error( "The Shutdown Spool Process was interrupted.", ex );
506         }
507 
508         log.info( "In dispose, destroying event queue." );
509         // This stops the processor thread.
510         cacheEventQueue.destroy();
511 
512         // Invoke any implementation specific disposal code
513         // need to handle the disposal first.
514         doDispose();
515 
516         alive = false;
517     }
518 
519     /**
520      * @return the region name.
521      * @see ICache#getCacheName
522      */
523     public String getCacheName()
524     {
525         return cacheName;
526     }
527 
528     /**
529      * Gets basic stats for the abstract disk cache.
530      * <p>
531      * @return String
532      */
533     public String getStats()
534     {
535         return getStatistics().toString();
536     }
537 
538     /**
539      * Returns semi-structured data.
540      * <p>
541      * @see org.apache.jcs.auxiliary.AuxiliaryCache#getStatistics()
542      */
543     public IStats getStatistics()
544     {
545         IStats stats = new Stats();
546         stats.setTypeName( "Abstract Disk Cache" );
547 
548         ArrayList<IStatElement> elems = new ArrayList<IStatElement>();
549 
550         IStatElement se = null;
551 
552         se = new StatElement();
553         se.setName( "Purgatory Hits" );
554         se.setData( "" + purgHits );
555         elems.add( se );
556 
557         se = new StatElement();
558         se.setName( "Purgatory Size" );
559         se.setData( "" + purgatory.size() );
560         elems.add( se );
561 
562         // get the stats from the event queue too
563         // get as array, convert to list, add list to our outer list
564         IStats eqStats = this.cacheEventQueue.getStatistics();
565         IStatElement[] eqSEs = eqStats.getStatElements();
566         List<IStatElement> eqL = Arrays.asList( eqSEs );
567         elems.addAll( eqL );
568 
569         // get an array and put them in the Stats object
570         IStatElement[] ses = elems.toArray( new StatElement[0] );
571         stats.setStatElements( ses );
572 
573         return stats;
574     }
575 
576     /**
577      * @return the status -- alive or disposed from CacheConstants
578      * @see ICache#getStatus
579      */
580     public int getStatus()
581     {
582         return ( alive ? CacheConstants.STATUS_ALIVE : CacheConstants.STATUS_DISPOSED );
583     }
584 
585     /**
586      * Size cannot be determined without knowledge of the cache implementation, so subclasses will
587      * need to implement this method.
588      * <p>
589      * @return the number of items.
590      * @see ICache#getSize
591      */
592     public abstract int getSize();
593 
594     /**
595      * @see org.apache.jcs.engine.behavior.ICacheType#getCacheType
596      * @return Always returns DISK_CACHE since subclasses should all be of that type.
597      */
598     public int getCacheType()
599     {
600         return DISK_CACHE;
601     }
602 
603     /**
604      * Cache that implements the CacheListener interface, and calls appropriate methods in its
605      * parent class.
606      */
607     protected class MyCacheListener
608         implements ICacheListener
609     {
610         /** Id of the listener */
611         private long listenerId = 0;
612 
613         /**
614          * @return cacheElement.getElementAttributes();
615          * @throws IOException
616          * @see ICacheListener#getListenerId
617          */
618         public long getListenerId()
619             throws IOException
620         {
621             return this.listenerId;
622         }
623 
624         /**
625          * @param id
626          * @throws IOException
627          * @see ICacheListener#setListenerId
628          */
629         public void setListenerId( long id )
630             throws IOException
631         {
632             this.listenerId = id;
633         }
634 
635         /**
636          * @param element
637          * @throws IOException
638          * @see ICacheListener#handlePut NOTE: This checks if the element is a puratory element and
639          *      behaves differently depending. However since we have control over how elements are
640          *      added to the cache event queue, that may not be needed ( they are always
641          *      PurgatoryElements ).
642          */
643         public void handlePut( ICacheElement element )
644             throws IOException
645         {
646             if ( alive )
647             {
648                 // If the element is a PurgatoryElement we must check to see
649                 // if it is still spoolable, and remove it from purgatory.
650                 if ( element instanceof PurgatoryElement )
651                 {
652                     PurgatoryElement pe = (PurgatoryElement) element;
653 
654                     synchronized ( pe.getCacheElement() )
655                     {
656                         // TODO consider a timeout.
657                         // we need this so that we can have multiple update
658                         // threads and still have removeAll requests come in that
659                         // always win
660                         removeAllLock.readLock().lock();
661 
662                         try
663                         {
664                             // TODO consider changing purgatory sync
665                             // String keyAsString = element.getKey().toString();
666                             synchronized ( purgatory )
667                             {
668                                 // If the element has already been removed from
669                                 // purgatory do nothing
670                                 if ( !purgatory.containsKey( pe.getKey() ) )
671                                 {
672                                     return;
673                                 }
674 
675                                 element = pe.getCacheElement();
676                             }
677 
678                             // I took this out of the purgatory sync block.
679                             // If the element is still eligible, spool it.
680                             if ( pe.isSpoolable() )
681                             {
682                                 doUpdate( element );
683                             }
684                         }
685                         finally
686                         {
687                             removeAllLock.readLock().unlock();
688                         }
689 
690                         synchronized ( purgatory )
691                         {
692                             // After the update has completed, it is safe to
693                             // remove the element from purgatory.
694                             purgatory.remove( element.getKey() );
695                         }
696                     }
697                 }
698                 else
699                 {
700                     // call the child's implementation
701                     doUpdate( element );
702                 }
703             }
704             else
705             {
706                 /*
707                  * The cache is not alive, hence the element should be removed from purgatory. All
708                  * elements should be removed eventually. Perhaps, the alive check should have been
709                  * done before it went in the queue. This block handles the case where the disk
710                  * cache fails during normal operations.
711                  */
712                 synchronized ( purgatory )
713                 {
714                     purgatory.remove( element.getKey() );
715                 }
716             }
717         }
718 
719         /**
720          * @param cacheName
721          * @param key
722          * @throws IOException
723          * @see ICacheListener#handleRemove
724          */
725         public void handleRemove( String cacheName, Serializable key )
726             throws IOException
727         {
728             if ( alive )
729             {
730                 if ( doRemove( key ) )
731                 {
732                     log.debug( "Element removed, key: " + key );
733                 }
734             }
735         }
736 
737         /**
738          * @param cacheName
739          * @throws IOException
740          * @see ICacheListener#handleRemoveAll
741          */
742         public void handleRemoveAll( String cacheName )
743             throws IOException
744         {
745             if ( alive )
746             {
747                 doRemoveAll();
748             }
749         }
750 
751         /**
752          * @param cacheName
753          * @throws IOException
754          * @see ICacheListener#handleDispose
755          */
756         public void handleDispose( String cacheName )
757             throws IOException
758         {
759             if ( alive )
760             {
761                 doDispose();
762             }
763         }
764     }
765 
766     /**
767      * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
768      * methods call the *WithEventLogging method on the super. The *WithEventLogging methods call
769      * the abstract process* methods. The children implement the process methods.
770      * <p>
771      * ex. doGet calls getWithEventLogging, which calls processGet
772      */
773 
774     /**
775      * Get a value from the persistent store.
776      * <p>
777      * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
778      * methods call the *EventLogging method on the super. The *WithEventLogging methods call the
779      * abstract process* methods. The children implement the process methods.
780      * <p>
781      * @param key Key to locate value for.
782      * @return An object matching key, or null.
783      * @throws IOException
784      */
785     protected final ICacheElement doGet( Serializable key )
786         throws IOException
787     {
788         return super.getWithEventLogging( key );
789     }
790 
791     /**
792      * Get a value from the persistent store.
793      * <p>
794      * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
795      * methods call the *EventLogging method on the super. The *WithEventLogging methods call the
796      * abstract process* methods. The children implement the process methods.
797      * <p>
798      * @param pattern Used to match keys.
799      * @return A map of matches..
800      * @throws IOException
801      */
802     protected final Map<Serializable, ICacheElement> doGetMatching( String pattern )
803         throws IOException
804     {
805         return super.getMatchingWithEventLogging( pattern );
806     }
807 
808     /**
809      * Add a cache element to the persistent store.
810      * <p>
811      * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
812      * methods call the *EventLogging method on the super. The *WithEventLogging methods call the
813      * abstract process* methods. The children implement the process methods.
814      * <p>
815      * @param cacheElement
816      * @throws IOException
817      */
818     protected final void doUpdate( ICacheElement cacheElement )
819         throws IOException
820     {
821         super.updateWithEventLogging( cacheElement );
822     }
823 
824     /**
825      * Remove an object from the persistent store if found.
826      * <p>
827      * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
828      * methods call the *EventLogging method on the super. The *WithEventLogging methods call the
829      * abstract process* methods. The children implement the process methods.
830      * <p>
831      * @param key Key of object to remove.
832      * @return whether or no the item was present when removed
833      * @throws IOException
834      */
835     protected final boolean doRemove( Serializable key )
836         throws IOException
837     {
838         return super.removeWithEventLogging( key );
839     }
840 
841     /**
842      * Remove all objects from the persistent store.
843      * <p>
844      * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
845      * methods call the *EventLogging method on the super. The *WithEventLogging methods call the
846      * abstract process* methods. The children implement the process methods.
847      * <p>
848      * @throws IOException
849      */
850     protected final void doRemoveAll()
851         throws IOException
852     {
853         super.removeAllWithEventLogging();
854     }
855 
856     /**
857      * Dispose of the persistent store. Note that disposal of purgatory and setting alive to false
858      * does NOT need to be done by this method.
859      * <p>
860      * Before the event logging layer, the subclasses implemented the do* methods. Now the do*
861      * methods call the *EventLogging method on the super. The *WithEventLogging methods call the
862      * abstract process* methods. The children implement the process methods.
863      * <p>
864      * @throws IOException
865      */
866     protected final void doDispose()
867         throws IOException
868     {
869         super.disposeWithEventLogging();
870     }
871 
872     /**
873      * Gets the extra info for the event log.
874      * <p>
875      * @return disk location
876      */
877     @Override
878     public String getEventLoggingExtraInfo()
879     {
880         return getDiskLocation();
881     }
882 
883     /**
884      * This is used by the event logging.
885      * <p>
886      * @return the location of the disk, either path or ip.
887      */
888     protected abstract String getDiskLocation();
889 }