001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.extensions;
028    import org.opends.messages.Message;
029    
030    
031    
032    import java.util.ArrayList;
033    import java.util.HashMap;
034    import java.util.HashSet;
035    import java.util.Iterator;
036    import java.util.LinkedHashMap;
037    import java.util.List;
038    import java.util.Map;
039    import java.util.concurrent.TimeUnit;
040    import java.util.concurrent.locks.Lock;
041    import java.util.concurrent.locks.ReentrantReadWriteLock;
042    
043    import org.opends.server.admin.server.ConfigurationChangeListener;
044    import org.opends.server.admin.std.server.EntryCacheCfg;
045    import org.opends.server.admin.std.server.FIFOEntryCacheCfg;
046    import org.opends.server.api.Backend;
047    import org.opends.server.api.EntryCache;
048    import org.opends.server.config.ConfigException;
049    import org.opends.server.core.DirectoryServer;
050    import org.opends.server.loggers.debug.DebugTracer;
051    import org.opends.server.types.CacheEntry;
052    import org.opends.server.types.ConfigChangeResult;
053    import org.opends.server.types.DebugLogLevel;
054    import org.opends.server.types.DN;
055    import org.opends.server.types.Entry;
056    import org.opends.server.types.InitializationException;
057    import org.opends.server.types.SearchFilter;
058    import org.opends.server.types.Attribute;
059    import org.opends.server.util.ServerConstants;
060    import org.opends.messages.MessageBuilder;
061    
062    import static org.opends.server.loggers.debug.DebugLogger.*;
063    import static org.opends.messages.ExtensionMessages.*;
064    
065    
066    
067    /**
068     * This class defines a Directory Server entry cache that uses a FIFO to keep
069     * track of the entries.  Entries that have been in the cache the longest are
070     * the most likely candidates for purging if space is needed.  In contrast to
071     * other cache structures, the selection of entries to purge is not based on
072     * how frequently or recently the entries have been accessed.  This requires
073     * significantly less locking (it will only be required when an entry is added
074     * or removed from the cache, rather than each time an entry is accessed).
075     * <BR><BR>
076     * Cache sizing is based on the percentage of free memory within the JVM, such
077     * that if enough memory is free, then adding an entry to the cache will not
078     * require purging, but if more than a specified percentage of the available
079     * memory within the JVM is already consumed, then one or more entries will need
080     * to be removed in order to make room for a new entry.  It is also possible to
081     * configure a maximum number of entries for the cache.  If this is specified,
082     * then the number of entries will not be allowed to exceed this value, but it
083     * may not be possible to hold this many entries if the available memory fills
084     * up first.
085     * <BR><BR>
086     * Other configurable parameters for this cache include the maximum length of
087     * time to block while waiting to acquire a lock, and a set of filters that may
088     * be used to define criteria for determining which entries are stored in the
089     * cache.  If a filter list is provided, then only entries matching at least one
090     * of the given filters will be stored in the cache.
091     */
092    public class FIFOEntryCache
093           extends EntryCache <FIFOEntryCacheCfg>
094           implements ConfigurationChangeListener<FIFOEntryCacheCfg>
095    {
096      /**
097       * The tracer object for the debug logger.
098       */
099      private static final DebugTracer TRACER = getTracer();
100    
101      /**
102       * The reference to the Java runtime used to determine the amount of memory
103       * currently in use.
104       */
105      private static final Runtime runtime = Runtime.getRuntime();
106    
107      // The mapping between entry backends/IDs and entries.
108      private HashMap<Backend,HashMap<Long,CacheEntry>> idMap;
109    
110      // The mapping between DNs and entries.
111      private LinkedHashMap<DN,CacheEntry> dnMap;
112    
113      // The lock used to provide threadsafe access when changing the contents of
114      // the cache.
115      private ReentrantReadWriteLock cacheLock;
116      private Lock cacheWriteLock;
117      private Lock cacheReadLock;
118    
119      // The maximum amount of memory in bytes that the JVM will be allowed to use
120      // before we need to start purging entries.
121      private long maxAllowedMemory;
122    
123      // The maximum number of entries that may be held in the cache.
124      private long maxEntries;
125    
126      // Currently registered configuration object.
127      private FIFOEntryCacheCfg registeredConfiguration;
128    
129    
130    
131      /**
132       * Creates a new instance of this FIFO entry cache.
133       */
134      public FIFOEntryCache()
135      {
136        super();
137    
138    
139        // All initialization should be performed in the initializeEntryCache.
140      }
141    
142    
143    
144      /**
145       * {@inheritDoc}
146       */
147      public void initializeEntryCache(
148          FIFOEntryCacheCfg configuration
149          )
150          throws ConfigException, InitializationException
151      {
152        registeredConfiguration = configuration;
153        configuration.addFIFOChangeListener (this);
154    
155        // Initialize the cache structures.
156        idMap     = new HashMap<Backend,HashMap<Long,CacheEntry>>();
157        dnMap     = new LinkedHashMap<DN,CacheEntry>();
158    
159        // Initialize locks.
160        cacheLock = new ReentrantReadWriteLock(true);
161        cacheWriteLock = cacheLock.writeLock();
162        cacheReadLock = cacheLock.readLock();
163    
164        // Read configuration and apply changes.
165        boolean applyChanges = true;
166        ArrayList<Message> errorMessages = new ArrayList<Message>();
167        EntryCacheCommon.ConfigErrorHandler errorHandler =
168          EntryCacheCommon.getConfigErrorHandler (
169              EntryCacheCommon.ConfigPhase.PHASE_INIT, null, errorMessages
170              );
171        if (!processEntryCacheConfig(configuration, applyChanges, errorHandler)) {
172          MessageBuilder buffer = new MessageBuilder();
173          if (!errorMessages.isEmpty()) {
174            Iterator<Message> iterator = errorMessages.iterator();
175            buffer.append(iterator.next());
176            while (iterator.hasNext()) {
177              buffer.append(".  ");
178              buffer.append(iterator.next());
179            }
180          }
181          Message message = ERR_FIFOCACHE_CANNOT_INITIALIZE.get(buffer.toString());
182          throw new ConfigException(message);
183        }
184      }
185    
186    
187    
188      /**
189       * {@inheritDoc}
190       */
191      public void finalizeEntryCache()
192      {
193        cacheWriteLock.lock();
194    
195        try {
196          registeredConfiguration.removeFIFOChangeListener(this);
197    
198          // Release all memory currently in use by this cache.
199          try {
200            idMap.clear();
201            dnMap.clear();
202          } catch (Exception e) {
203            // This should never happen.
204            if (debugEnabled()) {
205              TRACER.debugCaught(DebugLogLevel.ERROR, e);
206            }
207          }
208        } finally {
209          cacheWriteLock.unlock();
210        }
211      }
212    
213    
214    
215      /**
216       * {@inheritDoc}
217       */
218      public boolean containsEntry(DN entryDN)
219      {
220        if (entryDN == null) {
221          return false;
222        }
223    
224        // Indicate whether the DN map contains the specified DN.
225        cacheReadLock.lock();
226        try {
227          return dnMap.containsKey(entryDN);
228        } finally {
229          cacheReadLock.unlock();
230        }
231      }
232    
233    
234    
235      /**
236       * {@inheritDoc}
237       */
238      public Entry getEntry(DN entryDN)
239      {
240        // Simply return the entry from the DN map.
241        cacheReadLock.lock();
242        try {
243          CacheEntry e = dnMap.get(entryDN);
244          if (e == null) {
245            // Indicate cache miss.
246            cacheMisses.getAndIncrement();
247            return null;
248          } else {
249            // Indicate cache hit.
250            cacheHits.getAndIncrement();
251            return e.getEntry();
252          }
253        } finally {
254          cacheReadLock.unlock();
255        }
256      }
257    
258    
259    
260      /**
261       * {@inheritDoc}
262       */
263      public long getEntryID(DN entryDN)
264      {
265        // Simply return the ID from the DN map.
266        cacheReadLock.lock();
267        try {
268          CacheEntry e = dnMap.get(entryDN);
269          if (e == null) {
270            return -1;
271          } else {
272            return e.getEntryID();
273          }
274        } finally {
275          cacheReadLock.unlock();
276        }
277      }
278    
279    
280    
281      /**
282       * {@inheritDoc}
283       */
284      public DN getEntryDN(Backend backend, long entryID)
285      {
286        // Locate specific backend map and return the entry DN by ID.
287        cacheReadLock.lock();
288        try {
289          HashMap<Long, CacheEntry> backendMap = idMap.get(backend);
290          if (backendMap != null) {
291            CacheEntry e = backendMap.get(entryID);
292            if (e != null) {
293              return e.getDN();
294            }
295          }
296          return null;
297        } finally {
298          cacheReadLock.unlock();
299        }
300      }
301    
302    
303    
304      /**
305       * {@inheritDoc}
306       */
307      public void putEntry(Entry entry, Backend backend, long entryID)
308      {
309        // Create the cache entry based on the provided information.
310        CacheEntry cacheEntry = new CacheEntry(entry, backend, entryID);
311    
312    
313        // Obtain a lock on the cache.  If this fails, then don't do anything.
314        try
315        {
316          if (!cacheWriteLock.tryLock(getLockTimeout(), TimeUnit.MILLISECONDS))
317          {
318            return;
319          }
320        }
321        catch (Exception e)
322        {
323          if (debugEnabled())
324          {
325            TRACER.debugCaught(DebugLogLevel.ERROR, e);
326          }
327    
328          return;
329        }
330    
331    
332        // At this point, we hold the lock.  No matter what, we must release the
333        // lock before leaving this method, so do that in a finally block.
334        try
335        {
336          // See if the current memory usage is within acceptable constraints.  If
337          // so, then add the entry to the cache (or replace it if it is already
338          // present).  If not, then remove an existing entry and don't add the new
339          // entry.
340          long usedMemory = runtime.totalMemory() - runtime.freeMemory();
341          if (usedMemory > maxAllowedMemory)
342          {
343            Iterator<CacheEntry> iterator = dnMap.values().iterator();
344            if (iterator.hasNext())
345            {
346              CacheEntry ce = iterator.next();
347              iterator.remove();
348    
349              HashMap<Long,CacheEntry> m = idMap.get(ce.getBackend());
350              if (m != null)
351              {
352                m.remove(ce.getEntryID());
353              }
354            }
355          }
356          else
357          {
358            // Add the entry to the cache.  This will replace it if it is already
359            // present and add it if it isn't.
360            dnMap.put(entry.getDN(), cacheEntry);
361    
362            HashMap<Long,CacheEntry> map = idMap.get(backend);
363            if (map == null)
364            {
365              map = new HashMap<Long,CacheEntry>();
366              map.put(entryID, cacheEntry);
367              idMap.put(backend, map);
368            }
369            else
370            {
371              map.put(entryID, cacheEntry);
372            }
373    
374    
375            // See if a cap has been placed on the maximum number of entries in the
376            // cache.  If so, then see if we have exceeded it and we need to purge
377            // entries until we're within the limit.
378            int entryCount = dnMap.size();
379            if ((maxEntries > 0) && (entryCount > maxEntries))
380            {
381              Iterator<CacheEntry> iterator = dnMap.values().iterator();
382              while (iterator.hasNext() && (entryCount > maxEntries))
383              {
384                CacheEntry ce = iterator.next();
385                iterator.remove();
386    
387                HashMap<Long,CacheEntry> m = idMap.get(ce.getBackend());
388                if (m != null)
389                {
390                  m.remove(ce.getEntryID());
391                }
392    
393                entryCount--;
394              }
395            }
396          }
397        }
398        catch (Exception e)
399        {
400          if (debugEnabled())
401          {
402            TRACER.debugCaught(DebugLogLevel.ERROR, e);
403          }
404    
405          return;
406        }
407        finally
408        {
409          cacheWriteLock.unlock();
410        }
411      }
412    
413    
414    
415      /**
416       * {@inheritDoc}
417       */
418      public boolean putEntryIfAbsent(Entry entry, Backend backend, long entryID)
419      {
420        // Create the cache entry based on the provided information.
421        CacheEntry cacheEntry = new CacheEntry(entry, backend, entryID);
422    
423    
424        // Obtain a lock on the cache.  If this fails, then don't do anything.
425        try
426        {
427          if (!cacheWriteLock.tryLock(getLockTimeout(), TimeUnit.MILLISECONDS))
428          {
429            // We can't rule out the possibility of a conflict, so return false.
430            return false;
431          }
432        }
433        catch (Exception e)
434        {
435          if (debugEnabled())
436          {
437            TRACER.debugCaught(DebugLogLevel.ERROR, e);
438          }
439    
440          // We can't rule out the possibility of a conflict, so return false.
441          return false;
442        }
443    
444    
445        // At this point, we hold the lock.  No matter what, we must release the
446        // lock before leaving this method, so do that in a finally block.
447        try
448        {
449          // See if the entry already exists in the cache.  If it does, then we will
450          // fail and not actually store the entry.
451          if (dnMap.containsKey(entry.getDN()))
452          {
453            return false;
454          }
455    
456          // See if the current memory usage is within acceptable constraints.  If
457          // so, then add the entry to the cache (or replace it if it is already
458          // present).  If not, then remove an existing entry and don't add the new
459          // entry.
460          long usedMemory = runtime.totalMemory() - runtime.freeMemory();
461          if (usedMemory > maxAllowedMemory)
462          {
463            Iterator<CacheEntry> iterator = dnMap.values().iterator();
464            if (iterator.hasNext())
465            {
466              CacheEntry ce = iterator.next();
467              iterator.remove();
468    
469              HashMap<Long,CacheEntry> m = idMap.get(ce.getBackend());
470              if (m != null)
471              {
472                m.remove(ce.getEntryID());
473              }
474            }
475          }
476          else
477          {
478            // Add the entry to the cache.  This will replace it if it is already
479            // present and add it if it isn't.
480            dnMap.put(entry.getDN(), cacheEntry);
481    
482            HashMap<Long,CacheEntry> map = idMap.get(backend);
483            if (map == null)
484            {
485              map = new HashMap<Long,CacheEntry>();
486              map.put(entryID, cacheEntry);
487              idMap.put(backend, map);
488            }
489            else
490            {
491              map.put(entryID, cacheEntry);
492            }
493    
494    
495            // See if a cap has been placed on the maximum number of entries in the
496            // cache.  If so, then see if we have exceeded it and we need to purge
497            // entries until we're within the limit.
498            int entryCount = dnMap.size();
499            if ((maxEntries > 0) && (entryCount > maxEntries))
500            {
501              Iterator<CacheEntry> iterator = dnMap.values().iterator();
502              while (iterator.hasNext() && (entryCount > maxEntries))
503              {
504                CacheEntry ce = iterator.next();
505                iterator.remove();
506    
507                HashMap<Long,CacheEntry> m = idMap.get(ce.getBackend());
508                if (m != null)
509                {
510                  m.remove(ce.getEntryID());
511                }
512    
513                entryCount--;
514              }
515            }
516          }
517    
518    
519          // We'll always return true in this case, even if we didn't actually add
520          // the entry due to memory constraints.
521          return true;
522        }
523        catch (Exception e)
524        {
525          if (debugEnabled())
526          {
527            TRACER.debugCaught(DebugLogLevel.ERROR, e);
528          }
529    
530          // We can't be sure there wasn't a conflict, so return false.
531          return false;
532        }
533        finally
534        {
535          cacheWriteLock.unlock();
536        }
537      }
538    
539    
540    
541      /**
542       * {@inheritDoc}
543       */
544      public void removeEntry(DN entryDN)
545      {
546        // Acquire the lock on the cache.  We should not return until the entry is
547        // removed, so we will block until we can obtain the lock.
548        // FIXME -- An alternate approach could be to block for a maximum length of
549        // time and then if it fails then put it in a queue for processing by some
550        // other thread before it releases the lock.
551        cacheWriteLock.lock();
552    
553    
554        // At this point, it is absolutely critical that we always release the lock
555        // before leaving this method, so do so in a finally block.
556        try
557        {
558          // Check the DN cache to see if the entry exists.  If not, then don't do
559          // anything.
560          CacheEntry entry = dnMap.remove(entryDN);
561          if (entry == null)
562          {
563            return;
564          }
565    
566          Backend backend = entry.getBackend();
567    
568          // Try to remove the entry from the ID list as well.
569          Map<Long,CacheEntry> map = idMap.get(backend);
570          if (map == null)
571          {
572            // This should't happen, but the entry isn't cached in the ID map so
573            // we can return.
574            return;
575          }
576    
577          map.remove(entry.getEntryID());
578    
579          // If this backend becomes empty now remove it from the idMap map.
580          if (map.isEmpty())
581          {
582            idMap.remove(backend);
583          }
584        }
585        catch (Exception e)
586        {
587          if (debugEnabled())
588          {
589            TRACER.debugCaught(DebugLogLevel.ERROR, e);
590          }
591    
592          // This shouldn't happen, but there's not much that we can do if it does.
593        }
594        finally
595        {
596          cacheWriteLock.unlock();
597        }
598      }
599    
600    
601    
602      /**
603       * {@inheritDoc}
604       */
605      public void clear()
606      {
607        // Acquire a lock on the cache.  We should not return until the cache has
608        // been cleared, so we will block until we can obtain the lock.
609        cacheWriteLock.lock();
610    
611    
612        // At this point, it is absolutely critical that we always release the lock
613        // before leaving this method, so do so in a finally block.
614        try
615        {
616          // Clear the DN cache.
617          dnMap.clear();
618    
619          // Clear the ID cache.
620          idMap.clear();
621        }
622        catch (Exception e)
623        {
624          if (debugEnabled())
625          {
626            TRACER.debugCaught(DebugLogLevel.ERROR, e);
627          }
628    
629          // This shouldn't happen, but there's not much that we can do if it does.
630        }
631        finally
632        {
633          cacheWriteLock.unlock();
634        }
635      }
636    
637    
638    
639      /**
640       * {@inheritDoc}
641       */
642      public void clearBackend(Backend backend)
643      {
644        // Acquire a lock on the cache.  We should not return until the cache has
645        // been cleared, so we will block until we can obtain the lock.
646        cacheWriteLock.lock();
647    
648    
649        // At this point, it is absolutely critical that we always release the lock
650        // before leaving this method, so do so in a finally block.
651        try
652        {
653          // Remove all references to entries for this backend from the ID cache.
654          HashMap<Long,CacheEntry> map = idMap.remove(backend);
655          if (map == null)
656          {
657            // No entries were in the cache for this backend, so we can return
658            // without doing anything.
659            return;
660          }
661    
662    
663          // Unfortunately, there is no good way to dump the entries from the DN
664          // cache based on their backend, so we will need to iterate through the
665          // entries in the ID map and do it manually.  Since this could take a
666          // while, we'll periodically release and re-acquire the lock in case
667          // anyone else is waiting on it so this doesn't become a stop-the-world
668          // event as far as the cache is concerned.
669          int entriesDeleted = 0;
670          for (CacheEntry e : map.values())
671          {
672            dnMap.remove(e.getEntry().getDN());
673            entriesDeleted++;
674    
675            if ((entriesDeleted % 1000)  == 0)
676            {
677              cacheWriteLock.unlock();
678              Thread.currentThread().yield();
679              cacheWriteLock.lock();
680            }
681          }
682        }
683        catch (Exception e)
684        {
685          if (debugEnabled())
686          {
687            TRACER.debugCaught(DebugLogLevel.ERROR, e);
688          }
689    
690          // This shouldn't happen, but there's not much that we can do if it does.
691        }
692        finally
693        {
694          cacheWriteLock.unlock();
695        }
696      }
697    
698    
699    
700      /**
701       * {@inheritDoc}
702       */
703      public void clearSubtree(DN baseDN)
704      {
705        // Determine which backend should be used for the provided base DN.  If
706        // there is none, then we don't need to do anything.
707        Backend backend = DirectoryServer.getBackend(baseDN);
708        if (backend == null)
709        {
710          return;
711        }
712    
713    
714        // Acquire a lock on the cache.  We should not return until the cache has
715        // been cleared, so we will block until we can obtain the lock.
716        cacheWriteLock.lock();
717    
718    
719        // At this point, it is absolutely critical that we always release the lock
720        // before leaving this method, so do so in a finally block.
721        try
722        {
723          clearSubtree(baseDN, backend);
724        }
725        catch (Exception e)
726        {
727          if (debugEnabled())
728          {
729            TRACER.debugCaught(DebugLogLevel.ERROR, e);
730          }
731    
732          // This shouldn't happen, but there's not much that we can do if it does.
733        }
734        finally
735        {
736          cacheWriteLock.unlock();
737        }
738      }
739    
740    
741    
742      /**
743       * Clears all entries at or below the specified base DN that are associated
744       * with the given backend.  The caller must already hold the cache lock.
745       *
746       * @param  baseDN   The base DN below which all entries should be flushed.
747       * @param  backend  The backend for which to remove the appropriate entries.
748       */
749      private void clearSubtree(DN baseDN, Backend backend)
750      {
751        // See if there are any entries for the provided backend in the cache.  If
752        // not, then return.
753        HashMap<Long,CacheEntry> map = idMap.get(backend);
754        if (map == null)
755        {
756          // No entries were in the cache for this backend, so we can return without
757          // doing anything.
758          return;
759        }
760    
761    
762        // Since the provided base DN could hold a subset of the information in the
763        // specified backend, we will have to do this by iterating through all the
764        // entries for that backend.  Since this could take a while, we'll
765        // periodically release and re-acquire the lock in case anyone else is
766        // waiting on it so this doesn't become a stop-the-world event as far as the
767        // cache is concerned.
768        int entriesExamined = 0;
769        Iterator<CacheEntry> iterator = map.values().iterator();
770        while (iterator.hasNext())
771        {
772          CacheEntry e = iterator.next();
773          DN entryDN = e.getEntry().getDN();
774          if (entryDN.isDescendantOf(baseDN))
775          {
776            iterator.remove();
777            dnMap.remove(entryDN);
778          }
779    
780          entriesExamined++;
781          if ((entriesExamined % 1000) == 0)
782          {
783            cacheWriteLock.unlock();
784            Thread.currentThread().yield();
785            cacheWriteLock.lock();
786          }
787        }
788    
789    
790        // See if the backend has any subordinate backends.  If so, then process
791        // them recursively.
792        for (Backend subBackend : backend.getSubordinateBackends())
793        {
794          boolean isAppropriate = false;
795          for (DN subBase : subBackend.getBaseDNs())
796          {
797            if (subBase.isDescendantOf(baseDN))
798            {
799              isAppropriate = true;
800              break;
801            }
802          }
803    
804          if (isAppropriate)
805          {
806            clearSubtree(baseDN, subBackend);
807          }
808        }
809      }
810    
811    
812    
813      /**
814       * {@inheritDoc}
815       */
816      public void handleLowMemory()
817      {
818        // Grab the lock on the cache and wait until we have it.
819        cacheWriteLock.lock();
820    
821    
822        // At this point, it is absolutely critical that we always release the lock
823        // before leaving this method, so do so in a finally block.
824        try
825        {
826          // See how many entries are in the cache.  If there are less than 1000,
827          // then we'll dump all of them.  Otherwise, we'll dump 10% of the entries.
828          int numEntries = dnMap.size();
829          if (numEntries < 1000)
830          {
831            dnMap.clear();
832            idMap.clear();
833          }
834          else
835          {
836            int numToDrop = numEntries / 10;
837            Iterator<CacheEntry> iterator = dnMap.values().iterator();
838            while (iterator.hasNext() && (numToDrop > 0))
839            {
840              CacheEntry entry = iterator.next();
841              iterator.remove();
842    
843              HashMap<Long,CacheEntry> m = idMap.get(entry.getBackend());
844              if (m != null)
845              {
846                m.remove(entry.getEntryID());
847              }
848    
849              numToDrop--;
850            }
851          }
852        }
853        catch (Exception e)
854        {
855          if (debugEnabled())
856          {
857            TRACER.debugCaught(DebugLogLevel.ERROR, e);
858          }
859    
860          // This shouldn't happen, but there's not much that we can do if it does.
861        }
862        finally
863        {
864          cacheWriteLock.unlock();
865        }
866      }
867    
868    
869    
870      /**
871       * {@inheritDoc}
872       */
873      @Override()
874      public boolean isConfigurationAcceptable(EntryCacheCfg configuration,
875                                               List<Message> unacceptableReasons)
876      {
877        FIFOEntryCacheCfg config = (FIFOEntryCacheCfg) configuration;
878        return isConfigurationChangeAcceptable(config, unacceptableReasons);
879      }
880    
881    
882    
883      /**
884       * {@inheritDoc}
885       */
886      public boolean isConfigurationChangeAcceptable(
887          FIFOEntryCacheCfg configuration,
888          List<Message> unacceptableReasons
889          )
890      {
891        boolean applyChanges = false;
892        EntryCacheCommon.ConfigErrorHandler errorHandler =
893          EntryCacheCommon.getConfigErrorHandler (
894              EntryCacheCommon.ConfigPhase.PHASE_ACCEPTABLE,
895              unacceptableReasons,
896              null
897            );
898        processEntryCacheConfig (configuration, applyChanges, errorHandler);
899    
900        return errorHandler.getIsAcceptable();
901      }
902    
903    
904    
905      /**
906       * {@inheritDoc}
907       */
908      public ConfigChangeResult applyConfigurationChange(
909          FIFOEntryCacheCfg configuration
910          )
911      {
912        boolean applyChanges = true;
913        ArrayList<Message> errorMessages = new ArrayList<Message>();
914        EntryCacheCommon.ConfigErrorHandler errorHandler =
915          EntryCacheCommon.getConfigErrorHandler (
916              EntryCacheCommon.ConfigPhase.PHASE_APPLY, null, errorMessages
917              );
918    
919        // Do not apply changes unless this cache is enabled.
920        if (configuration.isEnabled()) {
921          processEntryCacheConfig (configuration, applyChanges, errorHandler);
922        }
923    
924        boolean adminActionRequired = errorHandler.getIsAdminActionRequired();
925        ConfigChangeResult changeResult = new ConfigChangeResult(
926            errorHandler.getResultCode(),
927            adminActionRequired,
928            errorHandler.getErrorMessages()
929            );
930    
931        return changeResult;
932      }
933    
934    
935    
936      /**
937       * Parses the provided configuration and configure the entry cache.
938       *
939       * @param configuration  The new configuration containing the changes.
940       * @param applyChanges   If true then take into account the new configuration.
941       * @param errorHandler   An handler used to report errors.
942       *
943       * @return  <CODE>true</CODE> if configuration is acceptable,
944       *          or <CODE>false</CODE> otherwise.
945       */
946      public boolean processEntryCacheConfig(
947          FIFOEntryCacheCfg                   configuration,
948          boolean                             applyChanges,
949          EntryCacheCommon.ConfigErrorHandler errorHandler
950          )
951      {
952        // Local variables to read configuration.
953        DN                    newConfigEntryDN;
954        long                  newLockTimeout;
955        long                  newMaxEntries;
956        int                   newMaxMemoryPercent;
957        long                  newMaxAllowedMemory;
958        HashSet<SearchFilter> newIncludeFilters = null;
959        HashSet<SearchFilter> newExcludeFilters = null;
960    
961        // Read configuration.
962        newConfigEntryDN = configuration.dn();
963        newLockTimeout   = configuration.getLockTimeout();
964        newMaxEntries    = configuration.getMaxEntries();
965    
966        // Maximum memory the cache can use.
967        newMaxMemoryPercent = configuration.getMaxMemoryPercent();
968        long maxJvmHeapSize = Runtime.getRuntime().maxMemory();
969        newMaxAllowedMemory = (maxJvmHeapSize / 100) * newMaxMemoryPercent;
970    
971        // Get include and exclude filters.
972        switch (errorHandler.getConfigPhase())
973        {
974        case PHASE_INIT:
975        case PHASE_ACCEPTABLE:
976        case PHASE_APPLY:
977          newIncludeFilters = EntryCacheCommon.getFilters (
978              configuration.getIncludeFilter(),
979              ERR_CACHE_INVALID_INCLUDE_FILTER,
980              errorHandler,
981              newConfigEntryDN
982              );
983          newExcludeFilters = EntryCacheCommon.getFilters (
984              configuration.getExcludeFilter(),
985              ERR_CACHE_INVALID_EXCLUDE_FILTER,
986              errorHandler,
987              newConfigEntryDN
988              );
989          break;
990        }
991    
992        if (applyChanges && errorHandler.getIsAcceptable())
993        {
994          maxEntries       = newMaxEntries;
995          maxAllowedMemory = newMaxAllowedMemory;
996    
997          setLockTimeout(newLockTimeout);
998          setIncludeFilters(newIncludeFilters);
999          setExcludeFilters(newExcludeFilters);
1000    
1001          registeredConfiguration = configuration;
1002        }
1003    
1004        return errorHandler.getIsAcceptable();
1005      }
1006    
1007    
1008    
1009      /**
1010       * {@inheritDoc}
1011       */
1012      public ArrayList<Attribute> getMonitorData()
1013      {
1014        ArrayList<Attribute> attrs = new ArrayList<Attribute>();
1015    
1016        try {
1017          attrs = EntryCacheCommon.getGenericMonitorData(
1018            new Long(cacheHits.longValue()),
1019            // If cache misses is maintained by default cache
1020            // get it from there and if not point to itself.
1021            DirectoryServer.getEntryCache().getCacheMisses(),
1022            null,
1023            new Long(maxAllowedMemory),
1024            new Long(dnMap.size()),
1025            (((maxEntries != Integer.MAX_VALUE) &&
1026              (maxEntries != Long.MAX_VALUE)) ?
1027               new Long(maxEntries) : new Long(0))
1028            );
1029        } catch (Exception e) {
1030          if (debugEnabled()) {
1031            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1032          }
1033        }
1034    
1035        return attrs;
1036      }
1037    
1038    
1039    
1040      /**
1041       * {@inheritDoc}
1042       */
1043      public Long getCacheCount()
1044      {
1045        return new Long(dnMap.size());
1046      }
1047    
1048    
1049    
1050      /**
1051       * Return a verbose string representation of the current cache maps.
1052       * This is useful primary for debugging and diagnostic purposes such
1053       * as in the entry cache unit tests.
1054       * @return String verbose string representation of the current cache
1055       *                maps in the following format: dn:id:backend
1056       *                one cache entry map representation per line
1057       *                or <CODE>null</CODE> if all maps are empty.
1058       */
1059      private String toVerboseString()
1060      {
1061        String verboseString = new String();
1062        StringBuilder sb = new StringBuilder();
1063    
1064        Map<DN,CacheEntry> dnMapCopy;
1065        Map<Backend,HashMap<Long,CacheEntry>> idMapCopy;
1066    
1067        // Grab cache lock to prevent any modifications
1068        // to the cache maps until a snapshot is taken.
1069        cacheWriteLock.lock();
1070        try {
1071          // Examining the real maps will hold the lock and can cause map
1072          // modifications in case of any access order maps, make copies
1073          // instead.
1074          dnMapCopy = new LinkedHashMap<DN,CacheEntry>(dnMap);
1075          idMapCopy = new HashMap<Backend,HashMap<Long,CacheEntry>>(idMap);
1076        } finally {
1077          cacheWriteLock.unlock();
1078        }
1079    
1080        // Check dnMap first.
1081        for (DN dn : dnMapCopy.keySet()) {
1082          sb.append(dn.toString());
1083          sb.append(":");
1084          sb.append((dnMapCopy.get(dn) != null ?
1085              Long.toString(dnMapCopy.get(dn).getEntryID()) : null));
1086          sb.append(":");
1087          sb.append((dnMapCopy.get(dn) != null ?
1088              dnMapCopy.get(dn).getBackend().getBackendID() : null));
1089          sb.append(ServerConstants.EOL);
1090        }
1091    
1092        // See if there is anything on idMap that isnt reflected on
1093        // dnMap in case maps went out of sync.
1094        for (Backend backend : idMapCopy.keySet()) {
1095          for (Long id : idMapCopy.get(backend).keySet()) {
1096            if ((idMapCopy.get(backend).get(id) == null) ||
1097                !dnMapCopy.containsKey(
1098                  idMapCopy.get(backend).get(id).getDN())) {
1099              sb.append((idMapCopy.get(backend).get(id) != null ?
1100                  idMapCopy.get(backend).get(id).getDN().toString() : null));
1101              sb.append(":");
1102              sb.append(id.toString());
1103              sb.append(":");
1104              sb.append(backend.getBackendID());
1105              sb.append(ServerConstants.EOL);
1106            }
1107          }
1108        }
1109    
1110        verboseString = sb.toString();
1111    
1112        return (verboseString.length() > 0 ? verboseString : null);
1113      }
1114    
1115    }
1116