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