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 2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.extensions;
028    import org.opends.messages.Message;
029    
030    import java.util.ArrayList;
031    import java.util.HashSet;
032    import java.util.HashMap;
033    import java.util.LinkedHashMap;
034    import java.util.List;
035    import java.util.Iterator;
036    import java.util.Map;
037    import java.util.Set;
038    import java.util.SortedSet;
039    import java.util.StringTokenizer;
040    import java.util.concurrent.TimeUnit;
041    import java.util.concurrent.locks.Lock;
042    import java.util.concurrent.locks.ReentrantReadWriteLock;
043    import java.util.concurrent.atomic.AtomicLong;
044    import java.io.File;
045    import com.sleepycat.bind.EntryBinding;
046    import com.sleepycat.bind.serial.SerialBinding;
047    import com.sleepycat.bind.serial.StoredClassCatalog;
048    import com.sleepycat.je.Environment;
049    import com.sleepycat.je.EnvironmentConfig;
050    import com.sleepycat.je.EnvironmentMutableConfig;
051    import com.sleepycat.je.Database;
052    import com.sleepycat.je.DatabaseConfig;
053    import com.sleepycat.je.DatabaseEntry;
054    import com.sleepycat.je.DatabaseNotFoundException;
055    import com.sleepycat.je.LockMode;
056    import com.sleepycat.je.OperationStatus;
057    import com.sleepycat.je.StatsConfig;
058    import com.sleepycat.je.config.ConfigParam;
059    import com.sleepycat.je.config.EnvironmentParams;
060    import org.opends.messages.MessageBuilder;
061    import org.opends.server.api.Backend;
062    import org.opends.server.api.EntryCache;
063    import org.opends.server.admin.std.server.EntryCacheCfg;
064    import org.opends.server.admin.std.server.FileSystemEntryCacheCfg;
065    import org.opends.server.admin.server.ConfigurationChangeListener;
066    import org.opends.server.admin.server.ServerManagementContext;
067    import org.opends.server.admin.std.server.RootCfg;
068    import org.opends.server.backends.jeb.ConfigurableEnvironment;
069    import org.opends.server.config.ConfigException;
070    import org.opends.server.core.DirectoryServer;
071    import org.opends.server.types.ConfigChangeResult;
072    import org.opends.server.types.DN;
073    import org.opends.server.types.Entry;
074    import org.opends.server.types.EntryEncodeConfig;
075    import org.opends.server.types.InitializationException;
076    import org.opends.server.types.ResultCode;
077    import org.opends.server.types.SearchFilter;
078    import org.opends.server.types.FilePermission;
079    import org.opends.server.types.DebugLogLevel;
080    import org.opends.server.types.OpenDsException;
081    import org.opends.server.loggers.debug.DebugTracer;
082    import org.opends.server.types.Attribute;
083    import org.opends.server.util.ServerConstants;
084    
085    import static org.opends.server.loggers.debug.DebugLogger.*;
086    import static org.opends.server.loggers.ErrorLogger.logError;
087    import static org.opends.server.config.ConfigConstants.*;
088    import static org.opends.messages.ExtensionMessages.*;
089    import static org.opends.server.util.StaticUtils.*;
090    import static org.opends.messages.ConfigMessages.*;
091    
092    /**
093     * This class defines a Directory Server entry cache that uses JE database to
094     * keep track of the entries. Intended use is when JE database resides in the
095     * memory based file system which has obvious performance benefits, although
096     * any file system will do for this cache to function. Entries are maintained
097     * either by FIFO (default) or LRU (configurable) based list implementation.
098     * <BR><BR>
099     * Cache sizing is based on the size of free space available in the file
100     * system, such that if enough memory is free, then adding an entry to the
101     * cache will not require purging, but if more than a specified size of the
102     * file system available space is already consumed, then one or more entries
103     * will need to be removed in order to make room for a new entry. It is also
104     * possible to configure a maximum number of entries for the cache. If this
105     * is specified, then the number of entries will not be allowed to exceed
106     * this value, but it may not be possible to hold this many entries if the
107     * available memory fills up first.
108     * <BR><BR>
109     * Other configurable parameters for this cache include the maximum length of
110     * time to block while waiting to acquire a lock, and a set of filters that may
111     * be used to define criteria for determining which entries are stored in the
112     * cache.  If a filter list is provided, then only entries matching at least
113     * one of the given filters will be stored in the cache.
114     * <BR><BR>
115     * JE environment cache size can also be configured either as percentage of
116     * the free memory available in the JVM or as explicit size in bytes.
117     * <BR><BR>
118     * This cache has a persistence property which, if enabled, allows for the
119     * contents of the cache to stay persistent across server or cache restarts.
120     */
121    public class FileSystemEntryCache
122            extends EntryCache <FileSystemEntryCacheCfg>
123            implements ConfigurationChangeListener <FileSystemEntryCacheCfg> {
124      /**
125       * The tracer object for the debug logger.
126       */
127      private static final DebugTracer TRACER = getTracer();
128    
129      // Permissions for cache db environment.
130      private static final FilePermission CACHE_HOME_PERMISSIONS =
131          new FilePermission(0700);
132    
133      // The maximum amount of space in bytes that can be consumed in the filesystem
134      // before we need to start purging entries.
135      private long maxAllowedMemory;
136    
137      // The maximum number of entries that may be held in the cache.
138      // Atomic for additional safety and in case we decide to push
139      // some locks further down later. Does not inhere in additional
140      // overhead, via blocking on synchronization primitive, on most
141      // modern platforms being implemented via cpu instruction set.
142      private AtomicLong maxEntries;
143    
144      // The entry cache home folder to host db environment.
145      private String cacheHome;
146    
147      // The type of this cache.
148      // It can be either FIFO (default) or LRU (configurable).
149      private String cacheType;
150    
151      // This regulates whether we persist the cache across restarts or not.
152      private boolean persistentCache;
153    
154      // The lock used to provide threadsafe access when changing the contents
155      // of the cache maps.
156      private ReentrantReadWriteLock cacheLock;
157      private Lock cacheReadLock;
158      private Lock cacheWriteLock;
159    
160      // Entry Cache Index.
161      FileSystemEntryCacheIndex entryCacheIndex;
162    
163      // Access order for this cache. FIFO by default.
164      boolean accessOrder = false;
165    
166      // JE environment and database related fields for this cache.
167      private Environment entryCacheEnv;
168      private EnvironmentConfig entryCacheEnvConfig;
169      private EnvironmentMutableConfig entryCacheEnvMutableConfig;
170      private DatabaseConfig entryCacheDBConfig;
171    
172      // Statistics retrieval operation config for this JE environment.
173      private StatsConfig entryCacheEnvStatsConfig = new StatsConfig();
174    
175      // The main entry cache database.
176      private Database entryCacheDB;
177    
178      // Class database, catalog and binding for serialization.
179      private Database entryCacheClassDB;
180      private StoredClassCatalog classCatalog;
181      private EntryBinding entryCacheDataBinding;
182    
183      // JE naming constants.
184      private static final String ENTRYCACHEDBNAME = "EntryCacheDB";
185      private static final String INDEXCLASSDBNAME = "IndexClassDB";
186      private static final String INDEXKEY = "EntryCacheIndex";
187    
188      // The configuration to use when encoding entries in the database.
189      private EntryEncodeConfig encodeConfig =
190        new EntryEncodeConfig(true, true, true);
191    
192      // JE native properties to configuration attributes map.
193      private HashMap<String, String> configAttrMap =
194        new HashMap<String, String>();
195    
196      // Currently registered configuration object.
197      private FileSystemEntryCacheCfg registeredConfiguration;
198    
199      /**
200       * Creates a new instance of this entry cache.
201       */
202      public FileSystemEntryCache() {
203        super();
204    
205        // Register all JE native properties that map to
206        // corresponding config attributes.
207        configAttrMap.put("je.maxMemoryPercent",
208          ConfigurableEnvironment.ATTR_DATABASE_CACHE_PERCENT);
209        configAttrMap.put("je.maxMemory",
210          ConfigurableEnvironment.ATTR_DATABASE_CACHE_SIZE);
211    
212        // All initialization should be performed in the initializeEntryCache.
213      }
214    
215      /**
216       * {@inheritDoc}
217       */
218      public void initializeEntryCache(FileSystemEntryCacheCfg configuration)
219              throws ConfigException, InitializationException {
220    
221        registeredConfiguration = configuration;
222        configuration.addFileSystemChangeListener (this);
223    
224        // Read and apply configuration.
225        boolean applyChanges = true;
226        ArrayList<Message> errorMessages = new ArrayList<Message>();
227        EntryCacheCommon.ConfigErrorHandler errorHandler =
228          EntryCacheCommon.getConfigErrorHandler (
229              EntryCacheCommon.ConfigPhase.PHASE_INIT, null, errorMessages
230              );
231        if (!processEntryCacheConfig(configuration, applyChanges, errorHandler)) {
232          MessageBuilder buffer = new MessageBuilder();
233          if (!errorMessages.isEmpty()) {
234            Iterator<Message> iterator = errorMessages.iterator();
235            buffer.append(iterator.next());
236            while (iterator.hasNext()) {
237              buffer.append(".  ");
238              buffer.append(iterator.next());
239            }
240          }
241          Message message = ERR_FSCACHE_CANNOT_INITIALIZE.get(buffer.toString());
242          throw new ConfigException(message);
243        }
244    
245        // Set the cache type.
246        if (cacheType.equalsIgnoreCase("LRU")) {
247          accessOrder = true;
248        } else {
249          // Admin framework should only allow for either FIFO or LRU but
250          // we set the type to default here explicitly if it is not LRU.
251          cacheType = DEFAULT_FSCACHE_TYPE;
252          accessOrder = false;
253        }
254    
255        // Initialize the index.
256        entryCacheIndex = new FileSystemEntryCacheIndex(this, accessOrder);
257    
258        // Initialize locks.
259        cacheLock = new ReentrantReadWriteLock(true);
260        if (accessOrder) {
261          // In access-ordered linked hash maps, merely querying the map
262          // with get() is a structural modification.
263          cacheReadLock = cacheLock.writeLock();
264        } else {
265          cacheReadLock = cacheLock.readLock();
266        }
267        cacheWriteLock = cacheLock.writeLock();
268    
269        // Setup the cache home.
270        try {
271          checkAndSetupCacheHome(cacheHome);
272        } catch (Exception e) {
273          if (debugEnabled()) {
274            TRACER.debugCaught(DebugLogLevel.ERROR, e);
275          }
276    
277          // Not having any home directory for the cache db environment is a
278          // fatal error as we are unable to continue any further without it.
279          Message message =
280              ERR_FSCACHE_HOMELESS.get();
281          throw new InitializationException(message, e);
282        }
283    
284        // Configure and open JE environment and cache database.
285        try {
286          entryCacheEnvConfig.setAllowCreate(true);
287          entryCacheEnv = new Environment(new File(cacheHome), entryCacheEnvConfig);
288          entryCacheEnv.setMutableConfig(entryCacheEnvMutableConfig);
289          entryCacheDBConfig = new DatabaseConfig();
290          entryCacheDBConfig.setAllowCreate(true);
291    
292          // Configure the JE environment statistics to return only
293          // the values which do not incur some performance penalty.
294          entryCacheEnvStatsConfig.setFast(true);
295    
296          // Remove old cache databases if this cache is not persistent.
297          if ( !persistentCache ) {
298            try {
299              entryCacheEnv.removeDatabase(null, INDEXCLASSDBNAME);
300            } catch (DatabaseNotFoundException e) {}
301            try {
302              entryCacheEnv.removeDatabase(null, ENTRYCACHEDBNAME);
303            } catch (DatabaseNotFoundException e) {}
304          }
305    
306          entryCacheDB = entryCacheEnv.openDatabase(null,
307                  ENTRYCACHEDBNAME, entryCacheDBConfig);
308          entryCacheClassDB =
309            entryCacheEnv.openDatabase(null, INDEXCLASSDBNAME, entryCacheDBConfig);
310          // Instantiate the class catalog
311          classCatalog = new StoredClassCatalog(entryCacheClassDB);
312          entryCacheDataBinding =
313              new SerialBinding(classCatalog,
314              FileSystemEntryCacheIndex.class);
315    
316          // Get the root configuration object.
317          ServerManagementContext managementContext =
318            ServerManagementContext.getInstance();
319          RootCfg rootConfiguration =
320            managementContext.getRootConfiguration();
321    
322          // Restoration is static and not subject to the current configuration
323          // constraints so that the persistent state is truly preserved and
324          // restored to the exact same state where we left off when the cache
325          // has been made persistent. The only exception to this is the backend
326          // offline state matching where entries that belong to backend which
327          // we cannot match offline state for are discarded from the cache.
328          if ( persistentCache &&
329              // If preload is requested there is no point restoring the cache.
330              !rootConfiguration.getGlobalConfiguration(
331              ).isEntryCachePreload()) {
332            // Retrieve cache index.
333            try {
334              DatabaseEntry indexData = new DatabaseEntry();
335              DatabaseEntry indexKey = new DatabaseEntry(
336                  INDEXKEY.getBytes("UTF-8"));
337    
338              // Persistent state report.
339              Message message = NOTE_FSCACHE_RESTORE.get();
340              logError(message);
341    
342              if (OperationStatus.SUCCESS ==
343                  entryCacheDB.get(null, indexKey, indexData, LockMode.DEFAULT)) {
344                entryCacheIndex =
345                    (FileSystemEntryCacheIndex)
346                    entryCacheDataBinding.entryToObject(indexData);
347              } else {
348                throw new CacheIndexNotFoundException();
349              }
350              // Check cache index state.
351              if ((entryCacheIndex.dnMap.isEmpty()) ||
352                  (entryCacheIndex.backendMap.isEmpty()) ||
353                  (entryCacheIndex.offlineState.isEmpty())) {
354                throw new CacheIndexImpairedException();
355              } else {
356                // Restore entry cache maps from this index.
357    
358                // Push maxEntries and make it unlimited til restoration complete.
359                AtomicLong currentMaxEntries = maxEntries;
360                maxEntries.set(DEFAULT_FSCACHE_MAX_ENTRIES);
361    
362                // Compare last known offline states to offline states on startup.
363                Map<String,Long> currentBackendsState =
364                    DirectoryServer.getOfflineBackendsStateIDs();
365                Set<String> offlineBackendSet =
366                    entryCacheIndex.offlineState.keySet();
367                Iterator<String> offlineBackendIterator =
368                    offlineBackendSet.iterator();
369                while (offlineBackendIterator.hasNext()) {
370                  String backend = offlineBackendIterator.next();
371                  Long offlineId = entryCacheIndex.offlineState.get(backend);
372                  Long currentId = currentBackendsState.get(backend);
373                  if ( !(offlineId.equals(currentId)) ) {
374                    // Remove cache entries specific to this backend.
375                    clearBackend(DirectoryServer.getBackend(backend));
376                    // Log an error message.
377                    logError(WARN_FSCACHE_OFFLINE_STATE_FAIL.get(backend));
378                  }
379                }
380                // Pop max entries limit.
381                maxEntries = currentMaxEntries;
382              }
383    
384              // Persistent state report.
385              message = NOTE_FSCACHE_RESTORE_REPORT.get(
386                entryCacheIndex.dnMap.size());
387              logError(message);
388    
389            } catch (CacheIndexNotFoundException e) {
390              if (debugEnabled()) {
391                TRACER.debugCaught(DebugLogLevel.ERROR, e);
392              }
393    
394              // Log an error message.
395              logError(NOTE_FSCACHE_INDEX_NOT_FOUND.get());
396    
397              // Clear the entry cache.
398              clear();
399            } catch (CacheIndexImpairedException e) {
400              if (debugEnabled()) {
401                TRACER.debugCaught(DebugLogLevel.ERROR, e);
402              }
403    
404              // Log an error message.
405              logError(ERR_FSCACHE_INDEX_IMPAIRED.get());
406    
407              // Clear the entry cache.
408              clear();
409            } catch (Exception e) {
410              if (debugEnabled()) {
411                TRACER.debugCaught(DebugLogLevel.ERROR, e);
412              }
413    
414              // Log an error message.
415              logError(ERR_FSCACHE_CANNOT_LOAD_PERSISTENT_DATA.get());
416    
417              // Clear the entry cache.
418              clear();
419            }
420          }
421        } catch (Exception e) {
422          // If we got here it means we have failed to have a proper backend
423          // for this entry cache and there is absolutely no point going any
424          // farther from here.
425          if (debugEnabled()) {
426            TRACER.debugCaught(DebugLogLevel.ERROR, e);
427          }
428    
429          Message message =
430              ERR_FSCACHE_CANNOT_INITIALIZE.get(
431              (e.getCause() != null ? e.getCause().getMessage() :
432                stackTraceToSingleLineString(e)));
433          throw new InitializationException(message, e);
434        }
435    
436      }
437    
438      /**
439       * {@inheritDoc}
440       */
441      public void finalizeEntryCache() {
442    
443        cacheWriteLock.lock();
444    
445        try {
446          registeredConfiguration.removeFileSystemChangeListener(this);
447    
448          // Store index/maps in case of persistent cache. Since the cache database
449          // already exist at this point all we have to do is to serialize cache
450          // index maps @see FileSystemEntryCacheIndex and put them under indexkey
451          // allowing for the index to be restored and cache contents reused upon
452          // the next initialization. If this cache is empty skip persisting phase.
453          if (persistentCache && !entryCacheIndex.dnMap.isEmpty()) {
454            // There must be at least one backend at this stage.
455            entryCacheIndex.offlineState =
456              DirectoryServer.getOfflineBackendsStateIDs();
457    
458            // Store the index.
459            try {
460              DatabaseEntry indexData = new DatabaseEntry();
461    
462              // Persistent state save report.
463              Message message = NOTE_FSCACHE_SAVE.get();
464              logError(message);
465    
466              entryCacheDataBinding.objectToEntry(entryCacheIndex, indexData);
467              DatabaseEntry indexKey =
468                new DatabaseEntry(INDEXKEY.getBytes("UTF-8"));
469              if (OperationStatus.SUCCESS != entryCacheDB.put(null, indexKey,
470                  indexData)) {
471                throw new Exception();
472              }
473            } catch (Exception e) {
474              if (debugEnabled()) {
475                TRACER.debugCaught(DebugLogLevel.ERROR, e);
476              }
477    
478              // Log an error message.
479              logError(ERR_FSCACHE_CANNOT_STORE_PERSISTENT_DATA.get());
480            }
481    
482            // Persistent state save report.
483            Message message = NOTE_FSCACHE_SAVE_REPORT.get(
484              entryCacheIndex.dnMap.size());
485            logError(message);
486          }
487    
488          // Close JE databases and environment and clear all the maps.
489          try {
490            entryCacheIndex.backendMap.clear();
491            entryCacheIndex.dnMap.clear();
492            if (entryCacheDB != null) {
493              entryCacheDB.close();
494            }
495            if (entryCacheClassDB != null) {
496              entryCacheClassDB.close();
497            }
498            if (entryCacheEnv != null) {
499              // Remove cache and index dbs if this cache is not persistent.
500              if (!persistentCache) {
501                try {
502                  entryCacheEnv.removeDatabase(null, INDEXCLASSDBNAME);
503                } catch (DatabaseNotFoundException e) {}
504                try {
505                  entryCacheEnv.removeDatabase(null, ENTRYCACHEDBNAME);
506                } catch (DatabaseNotFoundException e) {}
507              }
508              entryCacheEnv.cleanLog();
509              entryCacheEnv.close();
510            }
511          } catch (Exception e) {
512            if (debugEnabled()) {
513              TRACER.debugCaught(DebugLogLevel.ERROR, e);
514            }
515    
516            // That is ok, JE verification and repair on startup should take care of
517            // this so if there are any unrecoverable errors during next startup
518            // and we are unable to handle and cleanup them we will log errors then.
519          }
520        } finally {
521          cacheWriteLock.unlock();
522        }
523      }
524    
525      /**
526       * {@inheritDoc}
527       */
528      public boolean containsEntry(DN entryDN)
529      {
530        if (entryDN == null) {
531          return false;
532        }
533    
534        // Indicate whether the DN map contains the specified DN.
535        boolean containsEntry = false;
536        cacheReadLock.lock();
537        try {
538          containsEntry = entryCacheIndex.dnMap.containsKey(
539            entryDN.toNormalizedString());
540        } finally {
541          cacheReadLock.unlock();
542        }
543        return containsEntry;
544      }
545    
546      /**
547       * {@inheritDoc}
548       */
549      public Entry getEntry(DN entryDN) {
550        // Get the entry from the DN map if it is present.  If not, then return
551        // null.
552        Entry entry = null;
553        cacheReadLock.lock();
554        try {
555          // Use get to generate entry access.
556          if (entryCacheIndex.dnMap.get(entryDN.toNormalizedString()) != null) {
557            entry = getEntryFromDB(entryDN);
558            // Indicate cache hit.
559            cacheHits.getAndIncrement();
560          } else {
561            // Indicate cache miss.
562            cacheMisses.getAndIncrement();
563          }
564        } finally {
565          cacheReadLock.unlock();
566        }
567        return entry;
568      }
569    
570      /**
571       * {@inheritDoc}
572       */
573      public long getEntryID(DN entryDN) {
574        long entryID = -1;
575        cacheReadLock.lock();
576        try {
577          Long eid = entryCacheIndex.dnMap.get(entryDN.toNormalizedString());
578          if (eid != null) {
579            entryID = eid.longValue();
580          }
581        } finally {
582          cacheReadLock.unlock();
583        }
584        return entryID;
585      }
586    
587      /**
588       * {@inheritDoc}
589       */
590      public DN getEntryDN(Backend backend, long entryID) {
591    
592        DN entryDN = null;
593        cacheReadLock.lock();
594        try {
595          // Get the map for the provided backend.  If it isn't present, then
596          // return null.
597          Map map = entryCacheIndex.backendMap.get(backend.getBackendID());
598          if ( !(map == null) ) {
599            // Get the entry DN from the map by its ID.  If it isn't present,
600            // then return null.
601            entryDN = DN.decode((String) map.get(entryID));
602          }
603        } catch (Exception e) {
604          // Ignore.
605        } finally {
606          cacheReadLock.unlock();
607        }
608        return entryDN;
609      }
610    
611      /**
612       * {@inheritDoc}
613       */
614      public void putEntry(Entry entry, Backend backend, long entryID)
615      {
616        try {
617          byte[] entryBytes = entry.encode(encodeConfig);
618          putEntryToDB(entry.getDN().toNormalizedString(),
619            backend, entryID, entryBytes);
620        } catch (Exception e) {
621          if (debugEnabled()) {
622            TRACER.debugCaught(DebugLogLevel.ERROR, e);
623          }
624        }
625      }
626    
627      /**
628       * {@inheritDoc}
629       */
630      public boolean putEntryIfAbsent(Entry entry, Backend backend, long entryID)
631      {
632        cacheReadLock.lock();
633        try {
634          // See if the entry already exists in the cache. If it does, then we
635          // will fail and not actually store the entry.
636          if (entryCacheIndex.dnMap.containsKey(
637            entry.getDN().toNormalizedString())) {
638            return false;
639          }
640        } finally {
641          cacheReadLock.unlock();
642        }
643        try {
644          byte[] entryBytes = entry.encode(encodeConfig);
645          return putEntryToDB(entry.getDN().toNormalizedString(),
646            backend, entryID, entryBytes);
647        } catch (Exception e) {
648          if (debugEnabled()) {
649            TRACER.debugCaught(DebugLogLevel.ERROR, e);
650          }
651          // We can't rule out the possibility of a conflict, so return false.
652          return false;
653        }
654      }
655    
656      /**
657       * {@inheritDoc}
658       */
659      public void removeEntry(DN entryDN) {
660    
661        cacheWriteLock.lock();
662    
663        try {
664          Long entryID = entryCacheIndex.dnMap.get(entryDN.toNormalizedString());
665          if (entryID == null) {
666            return;
667          }
668          Set<String> backendSet = entryCacheIndex.backendMap.keySet();
669          Iterator<String> backendIterator = backendSet.iterator();
670          while (backendIterator.hasNext()) {
671            Map<Long,String> map = entryCacheIndex.backendMap.get(
672              backendIterator.next());
673            if ((map.get(entryID) != null) &&
674                (map.get(entryID).equals(entryDN.toNormalizedString()))) {
675              map.remove(entryID);
676              // If this backend becomes empty now
677              // remove it from the backend map.
678              if (map.isEmpty()) {
679                backendIterator.remove();
680              }
681              break;
682            }
683          }
684          entryCacheIndex.dnMap.remove(entryDN.toNormalizedString());
685          entryCacheDB.delete(null,
686            new DatabaseEntry(entryDN.toNormalizedString().getBytes("UTF-8")));
687        } catch (Exception e) {
688          if (debugEnabled()) {
689            TRACER.debugCaught(DebugLogLevel.ERROR, e);
690          }
691        } finally {
692          cacheWriteLock.unlock();
693        }
694      }
695    
696      /**
697       * {@inheritDoc}
698       */
699      public void clear() {
700    
701        cacheWriteLock.lock();
702    
703        try {
704          entryCacheIndex.dnMap.clear();
705          entryCacheIndex.backendMap.clear();
706    
707          try {
708            if ((entryCacheDB != null) && (entryCacheEnv != null) &&
709                (entryCacheClassDB != null) && (entryCacheDBConfig != null)) {
710              entryCacheDBConfig = entryCacheDB.getConfig();
711              entryCacheDB.close();
712              entryCacheClassDB.close();
713              entryCacheEnv.truncateDatabase(null, ENTRYCACHEDBNAME, false);
714              entryCacheEnv.truncateDatabase(null, INDEXCLASSDBNAME, false);
715              entryCacheEnv.cleanLog();
716              entryCacheDB = entryCacheEnv.openDatabase(null, ENTRYCACHEDBNAME,
717                entryCacheDBConfig);
718              entryCacheClassDB = entryCacheEnv.openDatabase(null,
719                INDEXCLASSDBNAME, entryCacheDBConfig);
720              // Instantiate the class catalog
721              classCatalog = new StoredClassCatalog(entryCacheClassDB);
722              entryCacheDataBinding = new SerialBinding(classCatalog,
723                FileSystemEntryCacheIndex.class);
724            }
725          } catch (Exception e) {
726            if (debugEnabled()) {
727              TRACER.debugCaught(DebugLogLevel.ERROR, e);
728            }
729          }
730        } finally {
731          cacheWriteLock.unlock();
732        }
733      }
734    
735      /**
736       * {@inheritDoc}
737       */
738      public void clearBackend(Backend backend) {
739    
740        cacheWriteLock.lock();
741    
742        try {
743          Map<Long, String> backendEntriesMap =
744            entryCacheIndex.backendMap.get(backend.getBackendID());
745    
746          try {
747            if (backendEntriesMap == null) {
748              // No entries were in the cache for this backend,
749              // so we can return without doing anything.
750              return;
751            }
752            int entriesExamined = 0;
753            Iterator<Long> backendEntriesIterator =
754              backendEntriesMap.keySet().iterator();
755            while (backendEntriesIterator.hasNext()) {
756              Long entryID = backendEntriesIterator.next();
757              DN entryDN = DN.decode(backendEntriesMap.get(entryID));
758              entryCacheDB.delete(null, new DatabaseEntry(
759                entryDN.toNormalizedString().getBytes("UTF-8")));
760              backendEntriesIterator.remove();
761              entryCacheIndex.dnMap.remove(entryDN.toNormalizedString());
762    
763              // This can take a while, so we'll periodically release and
764              // re-acquire the lock in case anyone else is waiting on it
765              // so this doesn't become a stop-the-world event as far as
766              // the cache is concerned.
767              entriesExamined++;
768              if ((entriesExamined % 1000) == 0) {
769                cacheWriteLock.unlock();
770                Thread.currentThread().yield();
771                cacheWriteLock.lock();
772              }
773            }
774    
775            // This backend is empty now, remove it from the backend map.
776            entryCacheIndex.backendMap.remove(backend.getBackendID());
777          } catch (Exception e) {
778            if (debugEnabled()) {
779              TRACER.debugCaught(DebugLogLevel.ERROR, e);
780            }
781          }
782        } finally {
783          cacheWriteLock.unlock();
784        }
785      }
786    
787      /**
788       * {@inheritDoc}
789       */
790      public void clearSubtree(DN baseDN) {
791        // Determine which backend should be used for the provided base DN.  If
792        // there is none, then we don't need to do anything.
793        Backend backend = DirectoryServer.getBackend(baseDN);
794        if (backend == null)
795        {
796          return;
797        }
798    
799        // Acquire a lock on the cache.  We should not return until the cache has
800        // been cleared, so we will block until we can obtain the lock.
801        cacheWriteLock.lock();
802    
803        // At this point, it is absolutely critical that we always release the lock
804        // before leaving this method, so do so in a finally block.
805        try
806        {
807          clearSubtree(baseDN, backend);
808        }
809        catch (Exception e)
810        {
811          if (debugEnabled())
812          {
813            TRACER.debugCaught(DebugLogLevel.ERROR, e);
814          }
815          // This shouldn't happen, but there's not much that we can do if it does.
816        }
817        finally
818        {
819          cacheWriteLock.unlock();
820        }
821      }
822    
823      /**
824       * Clears all entries at or below the specified base DN that are associated
825       * with the given backend.  The caller must already hold the cache lock.
826       *
827       * @param  baseDN   The base DN below which all entries should be flushed.
828       * @param  backend  The backend for which to remove the appropriate entries.
829       */
830      private void clearSubtree(DN baseDN, Backend backend) {
831        // See if there are any entries for the provided backend in the cache.  If
832        // not, then return.
833        Map<Long,String> map =
834          entryCacheIndex.backendMap.get(backend.getBackendID());
835        if (map == null)
836        {
837          // No entries were in the cache for this backend, so we can return without
838          // doing anything.
839          return;
840        }
841    
842        // Since the provided base DN could hold a subset of the information in the
843        // specified backend, we will have to do this by iterating through all the
844        // entries for that backend.  Since this could take a while, we'll
845        // periodically release and re-acquire the lock in case anyone else is
846        // waiting on it so this doesn't become a stop-the-world event as far as the
847        // cache is concerned.
848        int entriesExamined = 0;
849        Iterator<String> iterator = map.values().iterator();
850        while (iterator.hasNext())
851        {
852          try {
853            DN entryDN = DN.decode(iterator.next());
854            if (entryDN.isDescendantOf(baseDN)) {
855              iterator.remove();
856              entryCacheIndex.dnMap.remove(entryDN);
857              try {
858                entryCacheDB.delete(null,
859                  new DatabaseEntry(
860                  entryDN.toNormalizedString().getBytes("UTF-8")));
861              } catch (Exception e) {
862                if (debugEnabled()) {
863                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
864                }
865              }
866            }
867    
868            entriesExamined++;
869            if ((entriesExamined % 1000) == 0) {
870              cacheWriteLock.unlock();
871              Thread.currentThread().yield();
872              cacheWriteLock.lock();
873            }
874          } catch (Exception e) {
875            // Ignore.
876          }
877        }
878    
879        // If this backend becomes empty now
880        // remove it from the backend map.
881        if (map.isEmpty()) {
882          entryCacheIndex.backendMap.remove(backend.getBackendID());
883        }
884    
885        // See if the backend has any subordinate backends.  If so, then process
886        // them recursively.
887        for (Backend subBackend : backend.getSubordinateBackends())
888        {
889          boolean isAppropriate = false;
890          for (DN subBase : subBackend.getBaseDNs())
891          {
892            if (subBase.isDescendantOf(baseDN))
893            {
894              isAppropriate = true;
895              break;
896            }
897          }
898    
899          if (isAppropriate)
900          {
901            clearSubtree(baseDN, subBackend);
902          }
903        }
904      }
905    
906      /**
907       * {@inheritDoc}
908       */
909      public void handleLowMemory() {
910        // This is about all we can do.
911        if (entryCacheEnv != null) {
912          try {
913            // Free some JVM memory.
914            entryCacheEnv.evictMemory();
915            // Free some main memory/space.
916            entryCacheEnv.cleanLog();
917          } catch (Exception e) {
918            if (debugEnabled()) {
919              TRACER.debugCaught(DebugLogLevel.ERROR, e);
920            }
921          }
922        }
923      }
924    
925      /**
926       * {@inheritDoc}
927       */
928      @Override()
929      public boolean isConfigurationAcceptable(EntryCacheCfg configuration,
930                                               List<Message> unacceptableReasons)
931      {
932        FileSystemEntryCacheCfg config = (FileSystemEntryCacheCfg) configuration;
933        return isConfigurationChangeAcceptable(config, unacceptableReasons);
934      }
935    
936      /**
937       * {@inheritDoc}
938       */
939      public boolean isConfigurationChangeAcceptable(
940          FileSystemEntryCacheCfg configuration,
941          List<Message> unacceptableReasons
942          )
943      {
944        boolean applyChanges = false;
945        EntryCacheCommon.ConfigErrorHandler errorHandler =
946          EntryCacheCommon.getConfigErrorHandler (
947              EntryCacheCommon.ConfigPhase.PHASE_ACCEPTABLE,
948              unacceptableReasons,
949              null
950            );
951        processEntryCacheConfig (configuration, applyChanges, errorHandler);
952    
953        return errorHandler.getIsAcceptable();
954      }
955    
956      /**
957       * {@inheritDoc}
958       */
959      public ConfigChangeResult applyConfigurationChange(
960          FileSystemEntryCacheCfg configuration
961          )
962      {
963        boolean applyChanges = true;
964        ArrayList<Message> errorMessages = new ArrayList<Message>();
965        EntryCacheCommon.ConfigErrorHandler errorHandler =
966          EntryCacheCommon.getConfigErrorHandler (
967              EntryCacheCommon.ConfigPhase.PHASE_APPLY, null, errorMessages
968              );
969    
970        // Do not apply changes unless this cache is enabled.
971        if (configuration.isEnabled()) {
972          processEntryCacheConfig (configuration, applyChanges, errorHandler);
973        }
974    
975        boolean adminActionRequired = errorHandler.getIsAdminActionRequired();
976        ConfigChangeResult changeResult = new ConfigChangeResult(
977            errorHandler.getResultCode(),
978            adminActionRequired,
979            errorHandler.getErrorMessages()
980            );
981    
982        return changeResult;
983      }
984    
985      /**
986       * Parses the provided configuration and configure the entry cache.
987       *
988       * @param configuration  The new configuration containing the changes.
989       * @param applyChanges   If true then take into account the new configuration.
990       * @param errorHandler   An handler used to report errors.
991       *
992       * @return  <CODE>true</CODE> if configuration is acceptable,
993       *          or <CODE>false</CODE> otherwise.
994       */
995      public boolean processEntryCacheConfig(
996          FileSystemEntryCacheCfg             configuration,
997          boolean                             applyChanges,
998          EntryCacheCommon.ConfigErrorHandler errorHandler
999          )
1000      {
1001        // Local variables to read configuration.
1002        DN                       newConfigEntryDN;
1003        long                     newLockTimeout;
1004        long                     newMaxEntries;
1005        long                     newMaxAllowedMemory;
1006        HashSet<SearchFilter>    newIncludeFilters = null;
1007        HashSet<SearchFilter>    newExcludeFilters = null;
1008        int                      newJECachePercent;
1009        long                     newJECacheSize;
1010        boolean                  newPersistentCache;
1011        boolean                  newCompactEncoding;
1012        String                   newCacheType = DEFAULT_FSCACHE_TYPE;
1013        String                   newCacheHome = DEFAULT_FSCACHE_HOME;
1014        SortedSet<String>        newJEProperties;
1015    
1016        EnvironmentMutableConfig newMutableEnvConfig =
1017          new EnvironmentMutableConfig();
1018        EnvironmentConfig        newEnvConfig =
1019          new EnvironmentConfig();
1020    
1021        // Read configuration.
1022        newConfigEntryDN = configuration.dn();
1023        newLockTimeout   = configuration.getLockTimeout();
1024    
1025        // If the value of zero arrives make sure it is traslated
1026        // to the maximum possible value we can cap maxEntries to.
1027        newMaxEntries = configuration.getMaxEntries();
1028        if (newMaxEntries <= 0) {
1029          newMaxEntries = DEFAULT_FSCACHE_MAX_ENTRIES;
1030        }
1031    
1032        // Maximum memory/space this cache can utilize.
1033        newMaxAllowedMemory = configuration.getMaxMemorySize();
1034    
1035        // Determine JE cache percent.
1036        newJECachePercent = configuration.getDBCachePercent();
1037    
1038        // Determine JE cache size.
1039        newJECacheSize = configuration.getDBCacheSize();
1040    
1041        // Check if this cache is persistent.
1042        newPersistentCache = configuration.isPersistentCache();
1043    
1044        // Check if this cache should use compact encoding.
1045        newCompactEncoding = configuration.isCompactEncoding();
1046    
1047        // Get native JE properties.
1048        newJEProperties = configuration.getJEProperty();
1049    
1050        switch (errorHandler.getConfigPhase())
1051        {
1052        case PHASE_INIT:
1053          // Determine the cache type.
1054          newCacheType = configuration.getCacheType().toString();
1055    
1056          // Determine the cache home.
1057          newCacheHome = configuration.getCacheDirectory();
1058    
1059          newIncludeFilters = EntryCacheCommon.getFilters(
1060              configuration.getIncludeFilter(),
1061              ERR_CACHE_INVALID_INCLUDE_FILTER,
1062              errorHandler,
1063              newConfigEntryDN
1064              );
1065          newExcludeFilters = EntryCacheCommon.getFilters (
1066              configuration.getExcludeFilter(),
1067              ERR_CACHE_INVALID_EXCLUDE_FILTER,
1068              errorHandler,
1069              newConfigEntryDN
1070              );
1071          // JE configuration properties.
1072          try {
1073            newMutableEnvConfig.setCachePercent((newJECachePercent != 0 ?
1074              newJECachePercent :
1075              EnvironmentConfig.DEFAULT.getCachePercent()));
1076          } catch (Exception e) {
1077            if (debugEnabled()) {
1078              TRACER.debugCaught(DebugLogLevel.ERROR, e);
1079            }
1080            errorHandler.reportError(
1081              ERR_FSCACHE_CANNOT_SET_JE_MEMORY_PCT.get(),
1082              false,
1083              DirectoryServer.getServerErrorResultCode()
1084              );
1085          }
1086          try {
1087            newMutableEnvConfig.setCacheSize(newJECacheSize);
1088          } catch (Exception e) {
1089            if (debugEnabled()) {
1090              TRACER.debugCaught(DebugLogLevel.ERROR, e);
1091            }
1092            errorHandler.reportError(
1093              ERR_FSCACHE_CANNOT_SET_JE_MEMORY_SIZE.get(),
1094              false,
1095              DirectoryServer.getServerErrorResultCode()
1096              );
1097          }
1098          // JE native properties.
1099          try {
1100            newEnvConfig = ConfigurableEnvironment.setJEProperties(
1101              newEnvConfig, newJEProperties, configAttrMap);
1102          } catch (Exception e) {
1103            if (debugEnabled()) {
1104              TRACER.debugCaught(DebugLogLevel.ERROR, e);
1105            }
1106            errorHandler.reportError(
1107              ERR_FSCACHE_CANNOT_SET_JE_PROPERTIES.get(e.getMessage()),
1108              false, DirectoryServer.getServerErrorResultCode());
1109          }
1110          break;
1111        case PHASE_ACCEPTABLE:  // acceptable and apply are using the same
1112        case PHASE_APPLY:       // error ID codes
1113          newIncludeFilters = EntryCacheCommon.getFilters (
1114              configuration.getIncludeFilter(),
1115              ERR_CACHE_INVALID_INCLUDE_FILTER,
1116              errorHandler,
1117              newConfigEntryDN
1118              );
1119          newExcludeFilters = EntryCacheCommon.getFilters (
1120              configuration.getExcludeFilter(),
1121              ERR_CACHE_INVALID_EXCLUDE_FILTER,
1122              errorHandler,
1123              newConfigEntryDN
1124              );
1125          // Iterate through native JE properties.
1126          try {
1127            Map paramsMap = EnvironmentParams.SUPPORTED_PARAMS;
1128            // If this entry cache is disabled then there is no open JE
1129            // environment to check against, skip mutable check if so.
1130            if (configuration.isEnabled()) {
1131              newMutableEnvConfig =
1132                ConfigurableEnvironment.setJEProperties(
1133                entryCacheEnv.getConfig(), newJEProperties, configAttrMap);
1134              EnvironmentConfig oldEnvConfig = entryCacheEnv.getConfig();
1135              for (String jeEntry : newJEProperties) {
1136                // There is no need to validate properties yet again.
1137                StringTokenizer st = new StringTokenizer(jeEntry, "=");
1138                if (st.countTokens() == 2) {
1139                  String jePropertyName = st.nextToken();
1140                  String jePropertyValue = st.nextToken();
1141                  ConfigParam param = (ConfigParam) paramsMap.get(jePropertyName);
1142                  if (!param.isMutable()) {
1143                    String oldValue = oldEnvConfig.getConfigParam(param.getName());
1144                    String newValue = jePropertyValue;
1145                    if (!oldValue.equalsIgnoreCase(newValue)) {
1146                      Message message =
1147                        INFO_CONFIG_JE_PROPERTY_REQUIRES_RESTART.get(
1148                        jePropertyName);
1149                      errorHandler.reportError(message, true, ResultCode.SUCCESS,
1150                        true);
1151                      if (debugEnabled()) {
1152                        TRACER.debugInfo("The change to the following property " +
1153                          "will take effect when the component is restarted: " +
1154                          jePropertyName);
1155                      }
1156                    }
1157                  }
1158                }
1159              }
1160            } else {
1161              newMutableEnvConfig =
1162                ConfigurableEnvironment.setJEProperties(
1163                new EnvironmentConfig(), newJEProperties, configAttrMap);
1164            }
1165          } catch (ConfigException ce) {
1166            errorHandler.reportError(ce.getMessageObject(),
1167              false, DirectoryServer.getServerErrorResultCode());
1168          } catch (Exception e) {
1169            errorHandler.reportError(
1170              Message.raw(stackTraceToSingleLineString(e)),
1171              false, DirectoryServer.getServerErrorResultCode());
1172          }
1173          break;
1174        }
1175    
1176        if (applyChanges && errorHandler.getIsAcceptable())
1177        {
1178          switch (errorHandler.getConfigPhase()) {
1179          case PHASE_INIT:
1180            cacheType = newCacheType;
1181            cacheHome = newCacheHome;
1182            entryCacheEnvConfig = newEnvConfig;
1183            entryCacheEnvMutableConfig = newMutableEnvConfig;
1184            break;
1185          case PHASE_APPLY:
1186            try {
1187                newMutableEnvConfig =
1188                  entryCacheEnv.getMutableConfig();
1189                newMutableEnvConfig.setCachePercent((newJECachePercent != 0 ?
1190                  newJECachePercent :
1191                  EnvironmentConfig.DEFAULT.getCachePercent()));
1192                entryCacheEnv.setMutableConfig(newMutableEnvConfig);
1193                entryCacheEnv.evictMemory();
1194            } catch (Exception e) {
1195                if (debugEnabled()) {
1196                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
1197                }
1198                errorHandler.reportError(
1199                  ERR_FSCACHE_CANNOT_SET_JE_MEMORY_PCT.get(),
1200                  false,
1201                  DirectoryServer.getServerErrorResultCode()
1202                  );
1203            }
1204            try {
1205                newMutableEnvConfig =
1206                  entryCacheEnv.getMutableConfig();
1207                newMutableEnvConfig.setCacheSize(newJECacheSize);
1208                entryCacheEnv.setMutableConfig(newMutableEnvConfig);
1209                entryCacheEnv.evictMemory();
1210            } catch (Exception e) {
1211                if (debugEnabled()) {
1212                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
1213                }
1214                errorHandler.reportError(
1215                  ERR_FSCACHE_CANNOT_SET_JE_MEMORY_SIZE.get(),
1216                  false,
1217                  DirectoryServer.getServerErrorResultCode()
1218                  );
1219            }
1220            try {
1221              EnvironmentConfig oldEnvConfig = entryCacheEnv.getConfig();
1222              newEnvConfig = ConfigurableEnvironment.setJEProperties(
1223                oldEnvConfig, newJEProperties, configAttrMap);
1224              // This takes care of changes to the JE environment for those
1225              // properties that are mutable at runtime.
1226              entryCacheEnv.setMutableConfig(newEnvConfig);
1227            } catch (Exception e) {
1228              if (debugEnabled()) {
1229                  TRACER.debugCaught(DebugLogLevel.ERROR, e);
1230                }
1231                errorHandler.reportError(
1232                  ERR_FSCACHE_CANNOT_SET_JE_PROPERTIES.get(e.getMessage()),
1233                  false,
1234                  DirectoryServer.getServerErrorResultCode()
1235                  );
1236            }
1237            break;
1238          }
1239    
1240          maxEntries       = new AtomicLong(newMaxEntries);
1241          maxAllowedMemory = newMaxAllowedMemory;
1242          persistentCache  = newPersistentCache;
1243    
1244          encodeConfig     = new EntryEncodeConfig(true,
1245            newCompactEncoding, newCompactEncoding);
1246    
1247          setLockTimeout(newLockTimeout);
1248          setIncludeFilters(newIncludeFilters);
1249          setExcludeFilters(newExcludeFilters);
1250    
1251          registeredConfiguration = configuration;
1252        }
1253    
1254        return errorHandler.getIsAcceptable();
1255      }
1256    
1257      /**
1258       * {@inheritDoc}
1259       */
1260      public ArrayList<Attribute> getMonitorData()
1261      {
1262        ArrayList<Attribute> attrs = new ArrayList<Attribute>();
1263    
1264        try {
1265          attrs = EntryCacheCommon.getGenericMonitorData(
1266            new Long(cacheHits.longValue()),
1267            // If cache misses is maintained by default cache
1268            // get it from there and if not point to itself.
1269            DirectoryServer.getEntryCache().getCacheMisses(),
1270            new Long(entryCacheEnv.getStats(
1271              entryCacheEnvStatsConfig).getTotalLogSize()),
1272            new Long(maxAllowedMemory),
1273            new Long(entryCacheIndex.dnMap.size()),
1274            (((maxEntries.longValue() != Integer.MAX_VALUE) &&
1275              (maxEntries.longValue() != Long.MAX_VALUE)) ?
1276               new Long(maxEntries.longValue()) : new Long(0))
1277            );
1278        } catch (Exception e) {
1279          if (debugEnabled()) {
1280            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1281          }
1282        }
1283    
1284        return attrs;
1285      }
1286    
1287      /**
1288       * {@inheritDoc}
1289       */
1290      public Long getCacheCount()
1291      {
1292        return new Long(entryCacheIndex.dnMap.size());
1293      }
1294    
1295      /**
1296       * Retrieves and decodes the entry with the specified DN from JE backend db.
1297       *
1298       * @param  entryDN   The DN of the entry to retrieve.
1299       *
1300       * @return  The requested entry if it is present in the cache, or
1301       *          <CODE>null</CODE> if it is not present.
1302       */
1303      private Entry getEntryFromDB(DN entryDN)
1304      {
1305        DatabaseEntry cacheEntryKey = new DatabaseEntry();
1306        DatabaseEntry primaryData = new DatabaseEntry();
1307    
1308        try {
1309          // Get the primary key and data.
1310          cacheEntryKey.setData(entryDN.toNormalizedString().getBytes("UTF-8"));
1311          if (entryCacheDB.get(null, cacheEntryKey,
1312                  primaryData,
1313                  LockMode.DEFAULT) == OperationStatus.SUCCESS) {
1314    
1315            Entry entry = Entry.decode(primaryData.getData());
1316            entry.setDN(entryDN);
1317            return entry;
1318          } else {
1319            throw new Exception();
1320          }
1321        } catch (Exception e) {
1322          if (debugEnabled()) {
1323            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1324          }
1325    
1326          // Log an error message.
1327          logError(ERR_FSCACHE_CANNOT_RETRIEVE_ENTRY.get());
1328        }
1329        return null;
1330      }
1331    
1332      /**
1333       * Encodes and stores the entry in the JE backend db.
1334       *
1335       * @param  entry    The entry to store in the cache.
1336       * @param  backend  The backend with which the entry is associated.
1337       * @param  entryID  The entry ID within the provided backend that uniquely
1338       *                  identifies the specified entry.
1339       *
1340       * @return  <CODE>false</CODE> if some problem prevented the method from
1341       *          completing successfully, or <CODE>true</CODE> if the entry
1342       *          was either stored or the cache determined that this entry
1343       *          should never be cached for some reason.
1344       */
1345      private boolean putEntryToDB(String dnString,
1346                                   Backend backend,
1347                                   long entryID,
1348                                   byte[] entryBytes) {
1349        try {
1350          // Obtain a lock on the cache.  If this fails, then don't do anything.
1351          if (!cacheWriteLock.tryLock(getLockTimeout(), TimeUnit.MILLISECONDS)) {
1352            return false;
1353          }
1354          // See if the current fs space usage is within acceptable constraints. If
1355          // so, then add the entry to the cache (or replace it if it is already
1356          // present).  If not, then remove an existing entry and don't add the new
1357          // entry.
1358          long usedMemory = 0;
1359    
1360          // Zero means unlimited here.
1361          if (maxAllowedMemory != 0) {
1362            // Get approximate current total log size of JE environment in bytes.
1363            usedMemory =
1364                entryCacheEnv.getStats(entryCacheEnvStatsConfig).getTotalLogSize();
1365    
1366            // TODO: Check and log a warning if usedMemory hits default or
1367            // configurable watermark, see Issue 1735.
1368    
1369            if (usedMemory > maxAllowedMemory) {
1370              long savedMaxEntries = maxEntries.longValue();
1371              // Cap maxEntries artificially but dont let it go negative under
1372              // any circumstances.
1373              maxEntries.set((entryCacheIndex.dnMap.isEmpty() ? 0 :
1374                entryCacheIndex.dnMap.size() - 1));
1375              // Add the entry to the map to trigger remove of the eldest entry.
1376              // @see LinkedHashMapRotator.removeEldestEntry() for more details.
1377              entryCacheIndex.dnMap.put(dnString, entryID);
1378              // Restore the map and maxEntries.
1379              entryCacheIndex.dnMap.remove(dnString);
1380              maxEntries.set(savedMaxEntries);
1381              // We'll always return true in this case, even tho we didn't actually
1382              // add the entry due to memory constraints.
1383              return true;
1384            }
1385          }
1386    
1387          // Create key.
1388          DatabaseEntry cacheEntryKey = new DatabaseEntry();
1389          cacheEntryKey.setData(dnString.getBytes("UTF-8"));
1390    
1391          // Create data and put this cache entry into the database.
1392          if (entryCacheDB.put(null, cacheEntryKey,
1393              new DatabaseEntry(entryBytes)) == OperationStatus.SUCCESS) {
1394            // Add the entry to the cache index maps.
1395            Map<Long,String> map =
1396              entryCacheIndex.backendMap.get(backend.getBackendID());
1397            if (map == null) {
1398              map = new HashMap<Long,String>();
1399              map.put(entryID, dnString);
1400              entryCacheIndex.backendMap.put(backend.getBackendID(), map);
1401            } else {
1402              map.put(entryID, dnString);
1403            }
1404            entryCacheIndex.dnMap.put(dnString, entryID);
1405          }
1406    
1407          // We'll always return true in this case, even if we didn't actually add
1408          // the entry due to memory constraints.
1409          return true;
1410        } catch (Exception e) {
1411          if (debugEnabled()) {
1412            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1413          }
1414    
1415          // Log an error message.
1416          logError(
1417              ERR_FSCACHE_CANNOT_STORE_ENTRY.get());
1418    
1419          return false;
1420        } finally {
1421          if (cacheLock.isWriteLockedByCurrentThread()) {
1422            cacheWriteLock.unlock();
1423          }
1424        }
1425      }
1426    
1427      /**
1428       * Checks if the cache home exist and if not tries to recursively create it.
1429       * If either is successful adjusts cache home access permissions accordingly
1430       * to allow only process owner or the superuser to access JE environment.
1431       *
1432       * @param  cacheHome  String representation of complete file system path.
1433       *
1434       * @throws Exception  If failed to establish cache home.
1435       */
1436      private void checkAndSetupCacheHome(String cacheHome) throws Exception {
1437    
1438        boolean cacheHasHome = false;
1439        File cacheHomeDir = new File(cacheHome);
1440        if (cacheHomeDir.exists() &&
1441            cacheHomeDir.canRead() &&
1442            cacheHomeDir.canWrite()) {
1443          cacheHasHome = true;
1444        } else {
1445          try {
1446            cacheHasHome = cacheHomeDir.mkdirs();
1447          } catch (SecurityException e) {
1448            cacheHasHome = false;
1449          }
1450        }
1451        if ( cacheHasHome ) {
1452          // TODO: Investigate if its feasible to employ SetFileAttributes()
1453          // FILE_ATTRIBUTE_TEMPORARY attribute on Windows via native code.
1454          if(FilePermission.canSetPermissions()) {
1455            try {
1456              if(!FilePermission.setPermissions(cacheHomeDir,
1457                  CACHE_HOME_PERMISSIONS)) {
1458                throw new Exception();
1459              }
1460            } catch(Exception e) {
1461              // Log a warning that the permissions were not set.
1462              Message message = WARN_FSCACHE_SET_PERMISSIONS_FAILED.get(cacheHome);
1463              logError(message);
1464            }
1465          }
1466        } else {
1467          throw new Exception();
1468        }
1469      }
1470    
1471      /**
1472       * Return a verbose string representation of the current cache maps.
1473       * This is useful primary for debugging and diagnostic purposes such
1474       * as in the entry cache unit tests.
1475       * @return String verbose string representation of the current cache
1476       *                maps in the following format: dn:id:backend
1477       *                one cache entry map representation per line
1478       *                or <CODE>null</CODE> if all maps are empty.
1479       */
1480      private String toVerboseString()
1481      {
1482        String verboseString = new String();
1483        StringBuilder sb = new StringBuilder();
1484    
1485        Map<String,Long> dnMapCopy;
1486        Map<String,Map<Long,String>> backendMapCopy;
1487    
1488        // Grab write lock to prevent any modifications
1489        // to the cache maps until a snapshot is taken.
1490        cacheWriteLock.lock();
1491        try {
1492          // Examining the real maps will hold the lock
1493          // and can cause map modifications in case of
1494          // any access order maps, make copies instead.
1495          dnMapCopy = new LinkedHashMap<String,Long>(entryCacheIndex.dnMap);
1496          backendMapCopy =
1497            new HashMap<String,Map<Long,String>>
1498              (entryCacheIndex.backendMap);
1499        } finally {
1500          cacheWriteLock.unlock();
1501        }
1502    
1503        // Check dnMap first.
1504        for (String dn : dnMapCopy.keySet()) {
1505          sb.append(dn.toString());
1506          sb.append(":");
1507          sb.append((dnMapCopy.get(dn) != null ?
1508              dnMapCopy.get(dn).toString() : null));
1509          sb.append(":");
1510          String backendID = null;
1511          Iterator<String> backendIterator = backendMapCopy.keySet().iterator();
1512          while (backendIterator.hasNext()) {
1513            backendID = backendIterator.next();
1514            Map<Long, String> map = backendMapCopy.get(backendID);
1515            if ((map != null) &&
1516                (map.get(dnMapCopy.get(dn)) != null) &&
1517                (map.get(dnMapCopy.get(dn)).equals(dn))) {
1518              break;
1519            }
1520          }
1521          sb.append(backendID);
1522          sb.append(ServerConstants.EOL);
1523        }
1524    
1525        // See if there is anything on backendMap that isnt reflected on dnMap
1526        // in case maps went out of sync.
1527        String backendID = null;
1528        Iterator<String> backendIterator = backendMapCopy.keySet().iterator();
1529        while (backendIterator.hasNext()) {
1530          backendID = backendIterator.next();
1531          Map<Long, String> map = backendMapCopy.get(backendID);
1532          for (Long id : map.keySet()) {
1533            if (!dnMapCopy.containsKey(map.get(id)) || map.get(id) == null) {
1534              sb.append((map.get(id) != null ? map.get(id) : null));
1535              sb.append(":");
1536              sb.append(id.toString());
1537              sb.append(":");
1538              sb.append(backendID);
1539              sb.append(ServerConstants.EOL);
1540            }
1541          }
1542        }
1543    
1544        verboseString = sb.toString();
1545    
1546        return (verboseString.length() > 0 ? verboseString : null);
1547      }
1548    
1549      /**
1550       * This method is called each time we add a new key/value pair to the map.
1551       * The eldest entry is selected by the LinkedHashMap implementation based
1552       * on the access order configured.
1553       *
1554       * @param  eldest  The least recently inserted entry in the map, or if
1555       *                 this is an access-ordered map, the least recently
1556       *                 accessed entry. This is the entry that will be
1557       *                 removed it this method returns true. If the map was
1558       *                 empty prior to the put or putAll invocation resulting
1559       *                 in this invocation, this will be the entry that was
1560       *                 just inserted; in other words, if the map contains a
1561       *                 single entry, the eldest entry is also the newest.
1562       *
1563       * @return boolean {@code true} if the eldest entry should be removed
1564       *                 from the map; {@code false} if it should be retained.
1565       */
1566      protected boolean removeEldestEntry(Map.Entry eldest) {
1567        // Check if we hit the limit on max entries and if so remove
1568        // the eldest entry otherwise do nothing.
1569        if (entryCacheIndex.dnMap.size() > maxEntries.longValue()) {
1570          DatabaseEntry cacheEntryKey = new DatabaseEntry();
1571          cacheWriteLock.lock();
1572          try {
1573            // Remove the the eldest entry from supporting maps.
1574            String entryStringDN = (String) eldest.getKey();
1575            long entryID = ((Long) eldest.getValue()).longValue();
1576            cacheEntryKey.setData(entryStringDN.getBytes("UTF-8"));
1577            Set<String> backendSet = entryCacheIndex.backendMap.keySet();
1578            Iterator<String> backendIterator = backendSet.iterator();
1579            while (backendIterator.hasNext()) {
1580              Map<Long, String> map = entryCacheIndex.backendMap.get(
1581                backendIterator.next());
1582              if ((map.get(entryID) != null) &&
1583                (map.get(entryID).equals(entryStringDN))) {
1584                map.remove(entryID);
1585                // If this backend becomes empty now
1586                // remove it from the backend map.
1587                if (map.isEmpty()) {
1588                  backendIterator.remove();
1589                }
1590                break;
1591              }
1592            }
1593            // Remove the the eldest entry from the database.
1594            entryCacheDB.delete(null, cacheEntryKey);
1595          } catch (Exception e) {
1596            if (debugEnabled()) {
1597              TRACER.debugCaught(DebugLogLevel.ERROR, e);
1598            }
1599          } finally {
1600            cacheWriteLock.unlock();
1601          }
1602          return true;
1603        } else {
1604          return false;
1605        }
1606      }
1607    
1608      /**
1609       * This exception should be thrown if an error occurs while
1610       * trying to locate and load persistent cache index from
1611       * the existing entry cache database.
1612       */
1613      private class CacheIndexNotFoundException extends OpenDsException {
1614        static final long serialVersionUID = 6444756053577853869L;
1615        public CacheIndexNotFoundException() {}
1616        public CacheIndexNotFoundException(Message message) {
1617          super(message);
1618        }
1619      }
1620    
1621      /**
1622       * This exception should be thrown if persistent cache index
1623       * found in the existing entry cache database is determined
1624       * to be empty, inconsistent or damaged.
1625       */
1626      private class CacheIndexImpairedException extends OpenDsException {
1627        static final long serialVersionUID = -369455697709478407L;
1628        public CacheIndexImpairedException() {}
1629        public CacheIndexImpairedException(Message message) {
1630          super(message);
1631        }
1632      }
1633    
1634    }