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 }