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