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 }