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 2013-2015 ForgeRock AS.
026 */
027package org.opends.server.api;
028
029import java.util.HashSet;
030import java.util.List;
031import java.util.Set;
032import java.util.concurrent.atomic.AtomicLong;
033
034import org.forgerock.i18n.LocalizableMessage;
035import org.forgerock.i18n.slf4j.LocalizedLogger;
036import org.forgerock.opendj.config.server.ConfigException;
037import org.opends.server.admin.std.server.EntryCacheCfg;
038import org.opends.server.monitors.EntryCacheMonitorProvider;
039import org.opends.server.types.Attribute;
040import org.opends.server.types.DN;
041import org.opends.server.types.Entry;
042import org.opends.server.types.InitializationException;
043import org.opends.server.types.SearchFilter;
044
045/**
046 * This class defines the set of methods that must be implemented by a
047 * Directory Server entry cache.  Note that components accessing the
048 * entry cache must not depend on any particular behavior.  For
049 * example, if a call is made to {@code putEntry} to store an entry in
050 * the cache, there is no guarantee that immediately calling
051 * {@code getEntry} will be able to retrieve it.  There are several
052 * potential reasons for this, including:
053 * <UL>
054 *   <LI>The entry may have been deleted or replaced by another thread
055 *       between the {@code putEntry} and {@code getEntry} calls.</LI>
056 *   <LI>The entry cache may implement a purging mechanism and the
057 *       entry added may have been purged between the
058 *       {@code putEntry} and {@code getEntry} calls.</LI>
059 *   <LI>The entry cache may implement some kind of filtering
060 *       mechanism to determine which entries to store, and entries
061 *       not matching the appropriate criteria may not be stored.</LI>
062 *   <LI>The entry cache may not actually store any entries (this is
063 *       the behavior of the default cache if no implementation
064 *       specific entry cache is available).</LI>
065 * </UL>
066 *
067 * @param  <T>  The type of configuration handled by this entry
068 *              cache.
069 */
070@org.opends.server.types.PublicAPI(
071     stability=org.opends.server.types.StabilityLevel.VOLATILE,
072     mayInstantiate=false,
073     mayExtend=true,
074     mayInvoke=true,
075     notes="Entry cache methods may only be invoked by backends")
076public abstract class EntryCache<T extends EntryCacheCfg>
077{
078  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
079
080  /**
081   * The set of filters that define the entries that should be
082   * excluded from the cache.
083   */
084  private Set<SearchFilter> excludeFilters = new HashSet<>(0);
085
086  /**
087   * The set of filters that define the entries that should be
088   * included in the cache.
089   */
090  private Set<SearchFilter> includeFilters = new HashSet<>(0);
091
092  /**
093   * Arbitrary number of cache hits for monitoring.
094   */
095  protected AtomicLong cacheHits = new AtomicLong(0);
096
097  /**
098   * Arbitrary number of cache misses for monitoring.
099   */
100  protected AtomicLong cacheMisses = new AtomicLong(0);
101
102  /** The monitor associated with this entry cache. */
103  private EntryCacheMonitorProvider entryCacheMonitor;
104
105
106  /**
107   * Default constructor which is implicitly called from all entry
108   * cache implementations.
109   */
110  public EntryCache()
111  {
112    // No implementation required.
113  }
114
115  /**
116   * Initializes this entry cache implementation so that it will be
117   * available for storing and retrieving entries.
118   *
119   * @param  configuration  The configuration to use to initialize
120   *                        the entry cache.
121   *
122   * @throws  ConfigException  If there is a problem with the provided
123   *                           configuration entry that would prevent
124   *                           this entry cache from being used.
125   *
126   * @throws  InitializationException  If a problem occurs during the
127   *                                   initialization process that is
128   *                                   not related to the
129   *                                   configuration.
130   */
131  public abstract void initializeEntryCache(T configuration)
132         throws ConfigException, InitializationException;
133
134  /**
135   * Indicates whether the provided configuration is acceptable for
136   * this entry cache.  It should be possible to call this method on
137   * an uninitialized entry cache instance in order to determine
138   * whether the entry cache would be able to use the provided
139   * configuration.
140   * <BR><BR>
141   * Note that implementations which use a subclass of the provided
142   * configuration class will likely need to cast the configuration
143   * to the appropriate subclass type.
144   *
145   * @param  configuration        The entry cache configuration for
146   *                              which to make the determination.
147   * @param  unacceptableReasons  A list that may be used to hold the
148   *                              reasons that the provided
149   *                              configuration is not acceptable.
150   *
151   * @return  {@code true} if the provided configuration is acceptable
152   *          for this entry cache, or {@code false} if not.
153   */
154  public boolean isConfigurationAcceptable(
155                      EntryCacheCfg configuration,
156                      List<LocalizableMessage> unacceptableReasons)
157  {
158    // This default implementation does not perform any special
159    // validation.  It should be overridden by entry cache
160    // implementations that wish to perform more detailed validation.
161    return true;
162  }
163
164  /**
165   * Performs any necessary cleanup work (e.g., flushing all cached
166   * entries and releasing any other held resources) that should be
167   * performed when the server is to be shut down or the entry cache
168   * destroyed or replaced.
169   */
170  public abstract void finalizeEntryCache();
171
172  /**
173   * Indicates whether the entry cache currently contains the entry
174   * with the specified DN.  This method may be called without holding
175   * any locks if a point-in-time check is all that is required.
176   * Note that this method is called from @see #getEntry(DN entryDN,
177   * LockType lockType, List lockList)
178   *
179   * @param  entryDN  The DN for which to make the determination.
180   *
181   * @return  {@code true} if the entry cache currently contains the
182   *          entry with the specified DN, or {@code false} if not.
183   */
184  public abstract boolean containsEntry(DN entryDN);
185
186  /**
187   * Retrieves the entry with the specified DN from the cache.
188   *
189   * @param  entryDN   The DN of the entry to retrieve.
190   *
191   * @return  The requested entry if it is present in the cache, or
192   *          {@code null} if it is not present.
193   */
194  public abstract Entry getEntry(DN entryDN);
195
196  /**
197   * Retrieves the requested entry if it is present in the cache.
198   *
199   * @param  backendID   ID of the backend associated with the entry
200   *                     to retrieve.
201   * @param  entryID   The entry ID within the provided backend for
202   *                   the specified entry.
203   *
204   * @return  The requested entry if it is present in the cache, or
205   *          {@code null} if it is not present.
206   */
207  public Entry getEntry(String backendID, long entryID)
208  {
209    // Translate given backend/entryID pair to entryDN.
210    DN entryDN = getEntryDN(backendID, entryID);
211    if (entryDN == null)
212    {
213      // Indicate cache miss.
214      cacheMisses.getAndIncrement();
215      return null;
216    }
217    // Delegate to by DN lock and load method.
218    return getEntry(entryDN);
219  }
220
221  /**
222   * Retrieves the entry ID for the entry with the specified DN from
223   * the cache.  The caller should have already acquired a read or
224   * write lock for the entry if such protection is needed.
225   *
226   * @param  entryDN  The DN of the entry for which to retrieve the
227   *                  entry ID.
228   *
229   * @return  The entry ID for the requested entry, or -1 if it is
230   *          not present in the cache.
231   */
232  public abstract long getEntryID(DN entryDN);
233
234  /**
235   * Retrieves the entry DN for the entry with the specified ID on
236   * the specific backend from the cache.  The caller should have
237   * already acquired a read or write lock for the entry if such
238   * protection is needed.
239   * Note that this method is called from @see #getEntry(Backend
240   * backend, long entryID, LockType lockType, List lockList)
241   *
242   * @param  backendID  ID of the backend associated with the
243   *                    entry for which to retrieve the entry DN.
244   * @param  entryID    The entry ID within the provided backend
245   *                    for which to retrieve the entry DN.
246   *
247   * @return  The entry DN for the requested entry, or
248   *          {@code null} if it is not present in the cache.
249   */
250  public abstract DN getEntryDN(String backendID, long entryID);
251
252  /**
253   * Stores the provided entry in the cache.  Note that the mechanism
254   * that it uses to achieve this is implementation-dependent, and it
255   * is acceptable for the entry to not actually be stored in any
256   * cache.
257   *
258   * @param  entry      The entry to store in the cache.
259   * @param  backendID  ID of the backend with which the entry is
260   *                    associated.
261   * @param  entryID    The entry ID within the provided backend that
262   *                    uniquely identifies the specified entry.
263   */
264  public abstract void putEntry(Entry entry, String backendID, long entryID);
265
266  /**
267   * Stores the provided entry in the cache only if it does not
268   * conflict with an entry that already exists.  Note that the
269   * mechanism that it uses to achieve this is
270   * implementation-dependent, and it is acceptable for the entry to
271   * not actually be stored in any cache.  However, this method must
272   * not overwrite an existing version of the entry.
273   *
274   * @param  entry      The entry to store in the cache.
275   * @param  backendID  ID of the backend with which the entry is
276   *                    associated.
277   * @param  entryID    The entry ID within the provided backend that
278   *                    uniquely identifies the specified entry.
279   *
280   * @return  {@code false} if an existing entry or some other problem
281   *          prevented the method from completing successfully, or
282   *          {@code true} if there was no conflict and the entry was
283   *          either stored or the cache determined that this entry
284   *          should never be cached for some reason.
285   */
286  public abstract boolean putEntryIfAbsent(Entry entry,
287                                           String backendID,
288                                           long entryID);
289
290  /**
291   * Removes the specified entry from the cache.
292   *
293   * @param  entryDN  The DN of the entry to remove from the cache.
294   */
295  public abstract void removeEntry(DN entryDN);
296
297  /**
298   * Removes all entries from the cache.  The cache should still be
299   * available for future use.
300   */
301  public abstract void clear();
302
303  /**
304   * Removes all entries from the cache that are associated with the
305   * provided backend.
306   *
307   * @param  backendID  ID of the backend for which to flush the
308   *                    associated entries.
309   */
310  public abstract void clearBackend(String backendID);
311
312  /**
313   * Removes all entries from the cache that are below the provided
314   * DN.
315   *
316   * @param  baseDN  The base DN below which all entries should be
317   *                 flushed.
318   */
319  public abstract void clearSubtree(DN baseDN);
320
321  /**
322   * Attempts to react to a scenario in which it is determined that
323   * the system is running low on available memory.  In this case, the
324   * entry cache should attempt to free some memory if possible to try
325   * to avoid out of memory errors.
326   */
327  public abstract void handleLowMemory();
328
329  /**
330   * Retrieves the monitor that is associated with this entry
331   * cache.
332   *
333   * @return  The monitor that is associated with this entry
334   *          cache, or {@code null} if none has been assigned.
335   */
336  public final EntryCacheMonitorProvider getEntryCacheMonitor()
337  {
338    return entryCacheMonitor;
339  }
340
341  /**
342   * Sets the monitor for this entry cache.
343   *
344   * @param  entryCacheMonitor  The monitor for this entry cache.
345   */
346  public final void setEntryCacheMonitor(
347    EntryCacheMonitorProvider entryCacheMonitor)
348  {
349    this.entryCacheMonitor = entryCacheMonitor;
350  }
351
352  /**
353   * Retrieves a set of attributes containing monitor data that should
354   * be returned to the client if the corresponding monitor entry is
355   * requested.
356   *
357   * @return  A list of attributes containing monitor data that should
358   *          be returned to the client if the corresponding monitor
359   *          entry is requested.
360   */
361  public abstract List<Attribute> getMonitorData();
362
363  /**
364   * Retrieves the current number of entries stored within the cache.
365   *
366   * @return  The current number of entries stored within the cache.
367   */
368  public abstract Long getCacheCount();
369
370  /**
371   * Retrieves the current number of cache hits for this cache.
372   *
373   * @return  The current number of cache hits for this cache.
374   */
375  public long getCacheHits()
376  {
377    return cacheHits.longValue();
378  }
379
380  /**
381   * Retrieves the current number of cache misses for this cache.
382   *
383   * @return  The current number of cache misses for this cache.
384   */
385  public long getCacheMisses()
386  {
387    return cacheMisses.longValue();
388  }
389
390  /**
391   * Retrieves the set of search filters that may be used to determine
392   * whether an entry should be excluded from the cache.
393   *
394   * @return  The set of search filters that may be used to determine
395   *          whether an entry should be excluded from the cache.
396   */
397  public Set<SearchFilter> getExcludeFilters()
398  {
399    return excludeFilters;
400  }
401
402  /**
403   * Specifies the set of search filters that may be used to determine
404   * whether an entry should be excluded from the cache.
405   *
406   * @param  excludeFilters  The set of search filters that may be
407   *                         used to determine whether an entry should
408   *                         be excluded from the cache.
409   */
410  public void setExcludeFilters(Set<SearchFilter> excludeFilters)
411  {
412    if (excludeFilters == null)
413    {
414      this.excludeFilters = new HashSet<>(0);
415    }
416    else
417    {
418      this.excludeFilters = excludeFilters;
419    }
420  }
421
422  /**
423   * Retrieves the set of search filters that may be used to determine
424   * whether an entry should be included in the cache.
425   *
426   * @return  The set of search filters that may be used to determine
427   *          whether an entry should be included in the cache.
428   */
429  public Set<SearchFilter> getIncludeFilters()
430  {
431    return includeFilters;
432  }
433
434  /**
435   * Specifies the set of search filters that may be used to determine
436   * whether an entry should be included in the cache.
437   *
438   * @param  includeFilters  The set of search filters that may be
439   *                         used to determine whether an entry should
440   *                         be included in the cache.
441   */
442  public void setIncludeFilters(Set<SearchFilter> includeFilters)
443  {
444    if (includeFilters == null)
445    {
446      this.includeFilters = new HashSet<>(0);
447    }
448    else
449    {
450      this.includeFilters = includeFilters;
451    }
452  }
453
454  /**
455   * Indicates whether the current set of exclude and include filters
456   * allow caching of the specified entry.
457   *
458   * @param  entry  The entry to evaluate against exclude and include
459   *                filter sets.
460   *
461   * @return  {@code true} if current set of filters allow caching the
462   *          entry and {@code false} otherwise.
463   */
464  public boolean filtersAllowCaching(Entry entry)
465  {
466    // If there is a set of exclude filters, then make sure that the
467    // provided entry doesn't match any of them.
468    if (! excludeFilters.isEmpty())
469    {
470      for (SearchFilter f : excludeFilters)
471      {
472        try
473        {
474          if (f.matchesEntry(entry))
475          {
476            return false;
477          }
478        }
479        catch (Exception e)
480        {
481          logger.traceException(e);
482
483          // This shouldn't happen, but if it does then we can't be
484          // sure whether the entry should be excluded, so we will
485          // by default.
486          return false;
487        }
488      }
489    }
490
491    // If there is a set of include filters, then make sure that the
492    // provided entry matches at least one of them.
493    if (! includeFilters.isEmpty())
494    {
495      boolean matchFound = false;
496      for (SearchFilter f : includeFilters)
497      {
498        try
499        {
500          if (f.matchesEntry(entry))
501          {
502            matchFound = true;
503            break;
504          }
505        }
506        catch (Exception e)
507        {
508          logger.traceException(e);
509
510          // This shouldn't happen, but if it does, then
511          // just ignore it.
512        }
513      }
514
515      if (! matchFound)
516      {
517        return false;
518      }
519    }
520
521    return true;
522  }
523
524  /**
525   * Return a verbose string representation of the current cache maps. This is
526   * useful primary for debugging and diagnostic purposes such as in the entry
527   * cache unit tests.
528   * <p>
529   * This method is invoked by unit tests for debugging.
530   *
531   * @return String verbose string representation of the current cache maps in
532   *         the following format: dn:id:backend one cache entry map
533   *         representation per line or <CODE>null</CODE> if all maps are empty.
534   */
535  public abstract String toVerboseString();
536}