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