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.api;
028    import org.opends.messages.Message;
029    
030    
031    
032    import java.util.List;
033    import java.util.ArrayList;
034    import java.util.HashSet;
035    import java.util.Set;
036    import java.util.concurrent.locks.Lock;
037    import java.util.concurrent.atomic.AtomicLong;
038    
039    import org.opends.server.config.ConfigException;
040    import org.opends.server.types.DN;
041    import org.opends.server.types.Entry;
042    import org.opends.server.types.InitializationException;
043    import org.opends.server.types.LockType;
044    import org.opends.server.types.LockManager;
045    import org.opends.server.types.SearchFilter;
046    import org.opends.server.types.DebugLogLevel;
047    import org.opends.server.admin.std.server.EntryCacheCfg;
048    import org.opends.server.loggers.debug.DebugTracer;
049    import org.opends.server.monitors.EntryCacheMonitorProvider;
050    import org.opends.server.types.Attribute;
051    import static org.opends.server.loggers.debug.DebugLogger.*;
052    
053    
054    
055    /**
056     * This class defines the set of methods that must be implemented by a
057     * Directory Server entry cache.  Note that components accessing the
058     * entry cache must not depend on any particular behavior.  For
059     * example, if a call is made to {@code putEntry} to store an entry in
060     * the cache, there is no guarantee that immediately calling
061     * {@code getEntry} will be able to retrieve it.  There are several
062     * potential reasons for this, including:
063     * <UL>
064     *   <LI>The entry may have been deleted or replaced by another thread
065     *       between the {@code putEntry} and {@code getEntry} calls.</LI>
066     *   <LI>The entry cache may implement a purging mechanism and the
067     *       entry added may have been purged between the
068     *       {@code putEntry} and {@code getEntry} calls.</LI>
069     *   <LI>The entry cache may implement some kind of filtering
070     *       mechanism to determine which entries to store, and entries
071     *       not matching the appropriate criteria may not be stored.</LI>
072     *   <LI>The entry cache may not actually store any entries (this is
073     *       the behavior of the default cache if no implementation
074     *       specific entry cache is available).</LI>
075     * </UL>
076     *
077     * @param  <T>  The type of configuration handled by this entry
078     *              cache.
079     */
080    @org.opends.server.types.PublicAPI(
081         stability=org.opends.server.types.StabilityLevel.VOLATILE,
082         mayInstantiate=false,
083         mayExtend=true,
084         mayInvoke=true,
085         notes="Entry cache methods may only be invoked by backends")
086    public abstract class EntryCache
087           <T extends EntryCacheCfg>
088    {
089      /**
090       * The tracer object for the debug logger.
091       */
092      private static final DebugTracer TRACER = getTracer();
093    
094      // The set of filters that define the entries that should be
095      // excluded from the cache.
096      private Set<SearchFilter> excludeFilters =
097           new HashSet<SearchFilter>(0);
098    
099      // The set of filters that define the entries that should be
100      // included in the cache.
101      private Set<SearchFilter> includeFilters =
102           new HashSet<SearchFilter>(0);
103    
104      // The maximum length of time to try to obtain a lock before giving
105      // up.
106      private long lockTimeout = LockManager.DEFAULT_TIMEOUT;
107    
108      /**
109       * Arbitrary number of cache hits for monitoring.
110       */
111      protected AtomicLong cacheHits = new AtomicLong(0);
112    
113      /**
114       * Arbitrary number of cache misses for monitoring.
115       */
116      protected AtomicLong cacheMisses = new AtomicLong(0);
117    
118      // The monitor associated with this entry cache.
119      private EntryCacheMonitorProvider entryCacheMonitor = null;
120    
121    
122      /**
123       * Default constructor which is implicitly called from all entry
124       * cache implementations.
125       */
126      public EntryCache()
127      {
128        // No implementation required.
129      }
130    
131    
132    
133      /**
134       * Initializes this entry cache implementation so that it will be
135       * available for storing and retrieving entries.
136       *
137       * @param  configuration  The configuration to use to initialize
138       *                        the entry cache.
139       *
140       * @throws  ConfigException  If there is a problem with the provided
141       *                           configuration entry that would prevent
142       *                           this entry cache from being used.
143       *
144       * @throws  InitializationException  If a problem occurs during the
145       *                                   initialization process that is
146       *                                   not related to the
147       *                                   configuration.
148       */
149      public abstract void initializeEntryCache(T configuration)
150             throws ConfigException, InitializationException;
151    
152    
153    
154      /**
155       * Indicates whether the provided configuration is acceptable for
156       * this entry cache.  It should be possible to call this method on
157       * an uninitialized entry cache instance in order to determine
158       * whether the entry cache would be able to use the provided
159       * configuration.
160       * <BR><BR>
161       * Note that implementations which use a subclass of the provided
162       * configuration class will likely need to cast the configuration
163       * to the appropriate subclass type.
164       *
165       * @param  configuration        The entry cache configuration for
166       *                              which to make the determination.
167       * @param  unacceptableReasons  A list that may be used to hold the
168       *                              reasons that the provided
169       *                              configuration is not acceptable.
170       *
171       * @return  {@code true} if the provided configuration is acceptable
172       *          for this entry cache, or {@code false} if not.
173       */
174      public boolean isConfigurationAcceptable(
175                          EntryCacheCfg configuration,
176                          List<Message> unacceptableReasons)
177      {
178        // This default implementation does not perform any special
179        // validation.  It should be overridden by entry cache
180        // implementations that wish to perform more detailed validation.
181        return true;
182      }
183    
184    
185    
186      /**
187       * Performs any necessary cleanup work (e.g., flushing all cached
188       * entries and releasing any other held resources) that should be
189       * performed when the server is to be shut down or the entry cache
190       * destroyed or replaced.
191       */
192      public abstract void finalizeEntryCache();
193    
194    
195    
196      /**
197       * Indicates whether the entry cache currently contains the entry
198       * with the specified DN.  This method may be called without holding
199       * any locks if a point-in-time check is all that is required.
200       * Note that this method is called from @see #getEntry(DN entryDN,
201       * LockType lockType, List lockList)
202       *
203       * @param  entryDN  The DN for which to make the determination.
204       *
205       * @return  {@code true} if the entry cache currently contains the
206       *          entry with the specified DN, or {@code false} if not.
207       */
208      public abstract boolean containsEntry(DN entryDN);
209    
210    
211    
212      /**
213       * Retrieves the entry with the specified DN from the cache.  The
214       * caller should have already acquired a read or write lock for the
215       * entry if such protection is needed.
216       * Note that this method is called from @see #getEntry(DN entryDN,
217       * LockType lockType, List lockList)
218       *
219       * @param  entryDN  The DN of the entry to retrieve.
220       *
221       * @return  The requested entry if it is present in the cache, or
222       *          {@code null} if it is not present.
223       */
224      public abstract Entry getEntry(DN entryDN);
225    
226    
227    
228      /**
229       * Retrieves the entry with the specified DN from the cache,
230       * obtaining a lock on the entry before it is returned.  If the
231       * entry is present in the cache, then a lock will be obtained for
232       * that entry and appended to the provided list before the entry is
233       * returned.  If the entry is not present, then no lock will be
234       * obtained.  Note that although this method is declared non-final
235       * it is not recommended for subclasses to implement this method.
236       *
237       * @param  entryDN   The DN of the entry to retrieve.
238       * @param  lockType  The type of lock to obtain (it may be
239       *                   {@code NONE}).
240       * @param  lockList  The list to which the obtained lock will be
241       *                   added (note that no lock will be added if the
242       *                   lock type was {@code NONE}).
243       *
244       * @return  The requested entry if it is present in the cache, or
245       *          {@code null} if it is not present.
246       */
247      public Entry getEntry(DN entryDN,
248                            LockType lockType,
249                            List<Lock> lockList) {
250    
251        if (!containsEntry(entryDN)) {
252          // Indicate cache miss.
253          cacheMisses.getAndIncrement();
254    
255          return null;
256        }
257    
258        // Obtain a lock for the entry before actually retrieving the
259        // entry itself thus preventing any stale entries being returned,
260        // see Issue #1589 for more details.  If an error occurs, then
261        // make sure no lock is held and return null. Otherwise, return
262        // the entry.
263        switch (lockType)
264        {
265          case READ:
266            // Try to obtain a read lock for the entry.
267            Lock readLock = LockManager.lockRead(entryDN, lockTimeout);
268            if (readLock == null)
269            {
270              // We couldn't get the lock, so we have to return null.
271              return null;
272            }
273            else
274            {
275              try
276              {
277                lockList.add(readLock);
278                // and load.
279                Entry entry = getEntry(entryDN);
280                if (entry == null)
281                {
282                  lockList.remove(readLock);
283                  LockManager.unlock(entryDN, readLock);
284                  return null;
285                }
286                return entry;
287              }
288              catch (Exception e)
289              {
290                if (debugEnabled())
291                {
292                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
293                }
294    
295                // The attempt to add the lock to the list failed,
296                // so we need to release the lock and return null.
297                try
298                {
299                  LockManager.unlock(entryDN, readLock);
300                }
301                catch (Exception e2)
302                {
303                  if (debugEnabled())
304                  {
305                    TRACER.debugCaught(DebugLogLevel.ERROR, e2);
306                  }
307                }
308    
309                return null;
310              }
311            }
312    
313          case WRITE:
314            // Try to obtain a write lock for the entry.
315            Lock writeLock = LockManager.lockWrite(entryDN, lockTimeout);
316            if (writeLock == null)
317            {
318              // We couldn't get the lock, so we have to return null.
319              return null;
320            }
321            else
322            {
323              try
324              {
325                lockList.add(writeLock);
326                // and load.
327                Entry entry = getEntry(entryDN);
328                if (entry == null)
329                {
330                  lockList.remove(writeLock);
331                  LockManager.unlock(entryDN, writeLock);
332                  return null;
333                }
334                return entry;
335              }
336              catch (Exception e)
337              {
338                if (debugEnabled())
339                {
340                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
341                }
342    
343                // The attempt to add the lock to the list failed,
344                // so we need to release the lock and return null.
345                try
346                {
347                  LockManager.unlock(entryDN, writeLock);
348                }
349                catch (Exception e2)
350                {
351                  if (debugEnabled())
352                  {
353                    TRACER.debugCaught(DebugLogLevel.ERROR, e2);
354                  }
355                }
356    
357                return null;
358              }
359            }
360    
361          case NONE:
362            // We don't need to obtain a lock, so just return the entry.
363            Entry entry = getEntry(entryDN);
364            if (entry == null)
365            {
366              return null;
367            }
368            return entry;
369    
370          default:
371            // This is an unknown type of lock, so we'll return null.
372            return null;
373        }
374      }
375    
376    
377    
378      /**
379       * Retrieves the requested entry if it is present in the cache,
380       * obtaining a lock on the entry before it is returned.  If the
381       * entry is present in the cache, then a lock  will be obtained for
382       * that entry and appended to the provided list before the entry is
383       * returned.  If the entry is not present, then no lock will be
384       * obtained.  Note that although this method is declared non-final
385       * it is not recommended for subclasses to implement this method.
386       *
387       * @param  backend   The backend associated with the entry to
388       *                   retrieve.
389       * @param  entryID   The entry ID within the provided backend for
390       *                   the specified entry.
391       * @param  lockType  The type of lock to obtain (it may be
392       *                   {@code NONE}).
393       * @param  lockList  The list to which the obtained lock will be
394       *                   added (note that no lock will be added if the
395       *                   lock type was {@code NONE}).
396       *
397       * @return  The requested entry if it is present in the cache, or
398       *          {@code null} if it is not present.
399       */
400      public Entry getEntry(Backend backend, long entryID,
401                                     LockType lockType,
402                                     List<Lock> lockList) {
403    
404        // Translate given backend/entryID pair to entryDN.
405        DN entryDN = getEntryDN(backend, entryID);
406        if (entryDN == null) {
407          // Indicate cache miss.
408          cacheMisses.getAndIncrement();
409    
410          return null;
411        }
412    
413        // Delegate to by DN lock and load method.
414        return getEntry(entryDN, lockType, lockList);
415      }
416    
417    
418    
419      /**
420       * Retrieves the entry ID for the entry with the specified DN from
421       * the cache.  The caller should have already acquired a read or
422       * write lock for the entry if such protection is needed.
423       *
424       * @param  entryDN  The DN of the entry for which to retrieve the
425       *                  entry ID.
426       *
427       * @return  The entry ID for the requested entry, or -1 if it is
428       *          not present in the cache.
429       */
430      public abstract long getEntryID(DN entryDN);
431    
432    
433    
434      /**
435       * Retrieves the entry DN for the entry with the specified ID on
436       * the specific backend from the cache.  The caller should have
437       * already acquired a read or write lock for the entry if such
438       * protection is needed.
439       * Note that this method is called from @see #getEntry(Backend
440       * backend, long entryID, LockType lockType, List lockList)
441       *
442       * @param  backend  The backend associated with the entry for
443       *                  which to retrieve the entry DN.
444       * @param  entryID  The entry ID within the provided backend
445       *                  for which to retrieve the entry DN.
446       *
447       * @return  The entry DN for the requested entry, or
448       *          {@code null} if it is not present in the cache.
449       */
450      public abstract DN getEntryDN(Backend backend, long entryID);
451    
452    
453    
454      /**
455       * Stores the provided entry in the cache.  Note that the mechanism
456       * that it uses to achieve this is implementation-dependent, and it
457       * is acceptable for the entry to not actually be stored in any
458       * cache.
459       *
460       * @param  entry    The entry to store in the cache.
461       * @param  backend  The backend with which the entry is associated.
462       * @param  entryID  The entry ID within the provided backend that
463       *                  uniquely identifies the specified entry.
464       */
465      public abstract void putEntry(Entry entry, Backend backend,
466                                    long entryID);
467    
468    
469    
470      /**
471       * Stores the provided entry in the cache only if it does not
472       * conflict with an entry that already exists.  Note that the
473       * mechanism that it uses to achieve this is
474       * implementation-dependent, and it is acceptable for the entry to
475       * not actually be stored in any cache.  However, this method must
476       * not overwrite an existing version of the entry.
477       *
478       * @param  entry    The entry to store in the cache.
479       * @param  backend  The backend with which the entry is associated.
480       * @param  entryID  The entry ID within the provided backend that
481       *                  uniquely identifies the specified entry.
482       *
483       * @return  {@code false} if an existing entry or some other problem
484       *          prevented the method from completing successfully, or
485       *          {@code true} if there was no conflict and the entry was
486       *          either stored or the cache determined that this entry
487       *          should never be cached for some reason.
488       */
489      public abstract boolean putEntryIfAbsent(Entry entry,
490                                               Backend backend,
491                                               long entryID);
492    
493    
494    
495      /**
496       * Removes the specified entry from the cache.
497       *
498       * @param  entryDN  The DN of the entry to remove from the cache.
499       */
500      public abstract void removeEntry(DN entryDN);
501    
502    
503    
504      /**
505       * Removes all entries from the cache.  The cache should still be
506       * available for future use.
507       */
508      public abstract void clear();
509    
510    
511    
512      /**
513       * Removes all entries from the cache that are associated with the
514       * provided backend.
515       *
516       * @param  backend  The backend for which to flush the associated
517       *                  entries.
518       */
519      public abstract void clearBackend(Backend backend);
520    
521    
522    
523      /**
524       * Removes all entries from the cache that are below the provided
525       * DN.
526       *
527       * @param  baseDN  The base DN below which all entries should be
528       *                 flushed.
529       */
530      public abstract void clearSubtree(DN baseDN);
531    
532    
533    
534      /**
535       * Attempts to react to a scenario in which it is determined that
536       * the system is running low on available memory.  In this case, the
537       * entry cache should attempt to free some memory if possible to try
538       * to avoid out of memory errors.
539       */
540      public abstract void handleLowMemory();
541    
542    
543    
544      /**
545       * Retrieves the monitor that is associated with this entry
546       * cache.
547       *
548       * @return  The monitor that is associated with this entry
549       *          cache, or {@code null} if none has been assigned.
550       */
551      public final EntryCacheMonitorProvider getEntryCacheMonitor()
552      {
553        return entryCacheMonitor;
554      }
555    
556    
557    
558      /**
559       * Sets the monitor for this entry cache.
560       *
561       * @param  entryCacheMonitor  The monitor for this entry cache.
562       */
563      public final void setEntryCacheMonitor(
564        EntryCacheMonitorProvider entryCacheMonitor)
565      {
566        this.entryCacheMonitor = entryCacheMonitor;
567      }
568    
569    
570    
571      /**
572       * Retrieves a set of attributes containing monitor data that should
573       * be returned to the client if the corresponding monitor entry is
574       * requested.
575       *
576       * @return  A set of attributes containing monitor data that should
577       *          be returned to the client if the corresponding monitor
578       *          entry is requested.
579       */
580      public abstract ArrayList<Attribute> getMonitorData();
581    
582    
583    
584      /**
585       * Retrieves the current number of entries stored within the cache.
586       *
587       * @return  The current number of entries stored within the cache.
588       */
589      public abstract Long getCacheCount();
590    
591    
592    
593      /**
594       * Retrieves the current number of cache hits for this cache.
595       *
596       * @return  The current number of cache hits for this cache.
597       */
598      public Long getCacheHits()
599      {
600        return new Long(cacheHits.longValue());
601      }
602    
603    
604    
605      /**
606       * Retrieves the current number of cache misses for this cache.
607       *
608       * @return  The current number of cache misses for this cache.
609       */
610      public Long getCacheMisses()
611      {
612        return new Long(cacheMisses.longValue());
613      }
614    
615    
616    
617      /**
618       * Retrieves the maximum length of time in milliseconds to wait for
619       * a lock before giving up.
620       *
621       * @return  The maximum length of time in milliseconds to wait for a
622       *          lock before giving up.
623       */
624      public long getLockTimeout()
625      {
626        return lockTimeout;
627      }
628    
629    
630    
631      /**
632       * Specifies the maximum length of time in milliseconds to wait for
633       * a lock before giving up.
634       *
635       * @param  lockTimeout  The maximum length of time in milliseconds
636       *                      to wait for a lock before giving up.
637       */
638      public void setLockTimeout(long lockTimeout)
639      {
640        this.lockTimeout = lockTimeout;
641      }
642    
643    
644    
645      /**
646       * Retrieves the set of search filters that may be used to determine
647       * whether an entry should be excluded from the cache.
648       *
649       * @return  The set of search filters that may be used to determine
650       *          whether an entry should be excluded from the cache.
651       */
652      public Set<SearchFilter> getExcludeFilters()
653      {
654        return excludeFilters;
655      }
656    
657    
658    
659      /**
660       * Specifies the set of search filters that may be used to determine
661       * whether an entry should be excluded from the cache.
662       *
663       * @param  excludeFilters  The set of search filters that may be
664       *                         used to determine whether an entry should
665       *                         be excluded from the cache.
666       */
667      public void setExcludeFilters(Set<SearchFilter> excludeFilters)
668      {
669        if (excludeFilters == null)
670        {
671          this.excludeFilters = new HashSet<SearchFilter>(0);
672        }
673        else
674        {
675          this.excludeFilters = excludeFilters;
676        }
677      }
678    
679    
680    
681      /**
682       * Retrieves the set of search filters that may be used to determine
683       * whether an entry should be included in the cache.
684       *
685       * @return  The set of search filters that may be used to determine
686       *          whether an entry should be included in the cache.
687       */
688      public Set<SearchFilter> getIncludeFilters()
689      {
690        return includeFilters;
691      }
692    
693    
694    
695      /**
696       * Specifies the set of search filters that may be used to determine
697       * whether an entry should be included in the cache.
698       *
699       * @param  includeFilters  The set of search filters that may be
700       *                         used to determine whether an entry should
701       *                         be included in the cache.
702       */
703      public void setIncludeFilters(Set<SearchFilter> includeFilters)
704      {
705        if (includeFilters == null)
706        {
707          this.includeFilters = new HashSet<SearchFilter>(0);
708        }
709        else
710        {
711          this.includeFilters = includeFilters;
712        }
713      }
714    
715    
716    
717      /**
718       * Indicates whether the current set of exclude and include filters
719       * allow caching of the specified entry.
720       *
721       * @param  entry  The entry to evaluate against exclude and include
722       *                filter sets.
723       *
724       * @return  {@code true} if current set of filters allow caching the
725       *          entry and {@code false} otherwise.
726       */
727      public boolean filtersAllowCaching(Entry entry)
728      {
729        // If there is a set of exclude filters, then make sure that the
730        // provided entry doesn't match any of them.
731        if (! excludeFilters.isEmpty())
732        {
733          for (SearchFilter f : excludeFilters)
734          {
735            try
736            {
737              if (f.matchesEntry(entry))
738              {
739                return false;
740              }
741            }
742            catch (Exception e)
743            {
744              if (debugEnabled())
745              {
746                TRACER.debugCaught(DebugLogLevel.ERROR, e);
747              }
748    
749              // This shouldn't happen, but if it does then we can't be
750              // sure whether the entry should be excluded, so we will
751              // by default.
752              return false;
753            }
754          }
755        }
756    
757        // If there is a set of include filters, then make sure that the
758        // provided entry matches at least one of them.
759        if (! includeFilters.isEmpty())
760        {
761          boolean matchFound = false;
762          for (SearchFilter f : includeFilters)
763          {
764            try
765            {
766              if (f.matchesEntry(entry))
767              {
768                matchFound = true;
769                break;
770              }
771            }
772            catch (Exception e)
773            {
774              if (debugEnabled())
775              {
776                TRACER.debugCaught(DebugLogLevel.ERROR, e);
777              }
778    
779              // This shouldn't happen, but if it does, then
780              // just ignore it.
781            }
782          }
783    
784          if (! matchFound)
785          {
786            return false;
787          }
788        }
789    
790        return true;
791      }
792    }