001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2006-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.extensions; 028 import org.opends.messages.Message; 029 030 031 032 import java.lang.ref.ReferenceQueue; 033 import java.lang.ref.SoftReference; 034 import java.util.ArrayList; 035 import java.util.HashSet; 036 import java.util.Iterator; 037 import java.util.List; 038 import java.util.concurrent.ConcurrentHashMap; 039 import org.opends.messages.MessageBuilder; 040 041 import org.opends.server.admin.server.ConfigurationChangeListener; 042 import org.opends.server.admin.std.server.EntryCacheCfg; 043 import org.opends.server.admin.std.server.SoftReferenceEntryCacheCfg; 044 import org.opends.server.api.Backend; 045 import org.opends.server.api.EntryCache; 046 import org.opends.server.config.ConfigException; 047 import org.opends.server.core.DirectoryServer; 048 import org.opends.server.loggers.debug.DebugTracer; 049 import org.opends.server.types.Attribute; 050 import org.opends.server.types.CacheEntry; 051 import org.opends.server.types.ConfigChangeResult; 052 import org.opends.server.types.DebugLogLevel; 053 import org.opends.server.types.DN; 054 import org.opends.server.types.Entry; 055 import org.opends.server.types.InitializationException; 056 import org.opends.server.types.LockManager; 057 import org.opends.server.types.SearchFilter; 058 import org.opends.server.util.ServerConstants; 059 060 061 import static org.opends.server.loggers.debug.DebugLogger.*; 062 import static org.opends.messages.ExtensionMessages.*; 063 064 065 066 /** 067 * This class defines a Directory Server entry cache that uses soft references 068 * to manage objects in a way that will allow them to be freed if the JVM is 069 * running low on memory. 070 */ 071 public class SoftReferenceEntryCache 072 extends EntryCache <SoftReferenceEntryCacheCfg> 073 implements 074 ConfigurationChangeListener<SoftReferenceEntryCacheCfg>, 075 Runnable 076 { 077 /** 078 * The tracer object for the debug logger. 079 */ 080 private static final DebugTracer TRACER = getTracer(); 081 082 // The mapping between entry DNs and their corresponding entries. 083 private ConcurrentHashMap<DN,SoftReference<CacheEntry>> dnMap; 084 085 // The mapping between backend+ID and their corresponding entries. 086 private ConcurrentHashMap<Backend, 087 ConcurrentHashMap<Long,SoftReference<CacheEntry>>> idMap; 088 089 // The reference queue that will be used to notify us whenever a soft 090 // reference is freed. 091 private ReferenceQueue<CacheEntry> referenceQueue; 092 093 // Currently registered configuration object. 094 private SoftReferenceEntryCacheCfg registeredConfiguration; 095 096 private Thread cleanerThread; 097 098 private volatile boolean shutdown = false; 099 100 101 102 /** 103 * Creates a new instance of this soft reference entry cache. All 104 * initialization should be performed in the <CODE>initializeEntryCache</CODE> 105 * method. 106 */ 107 public SoftReferenceEntryCache() 108 { 109 super(); 110 111 dnMap = new ConcurrentHashMap<DN,SoftReference<CacheEntry>>(); 112 idMap = new ConcurrentHashMap<Backend, 113 ConcurrentHashMap<Long,SoftReference<CacheEntry>>>(); 114 115 setExcludeFilters(new HashSet<SearchFilter>()); 116 setIncludeFilters(new HashSet<SearchFilter>()); 117 setLockTimeout(LockManager.DEFAULT_TIMEOUT); 118 referenceQueue = new ReferenceQueue<CacheEntry>(); 119 } 120 121 122 123 /** 124 * {@inheritDoc} 125 */ 126 public void initializeEntryCache( 127 SoftReferenceEntryCacheCfg configuration 128 ) 129 throws ConfigException, InitializationException 130 { 131 cleanerThread = new Thread(this, "Soft Reference Entry Cache Cleaner"); 132 cleanerThread.setDaemon(true); 133 cleanerThread.start(); 134 135 registeredConfiguration = configuration; 136 configuration.addSoftReferenceChangeListener (this); 137 138 dnMap.clear(); 139 idMap.clear(); 140 141 // Read configuration and apply changes. 142 boolean applyChanges = true; 143 ArrayList<Message> errorMessages = new ArrayList<Message>(); 144 EntryCacheCommon.ConfigErrorHandler errorHandler = 145 EntryCacheCommon.getConfigErrorHandler ( 146 EntryCacheCommon.ConfigPhase.PHASE_INIT, null, errorMessages 147 ); 148 if (!processEntryCacheConfig(configuration, applyChanges, errorHandler)) { 149 MessageBuilder buffer = new MessageBuilder(); 150 if (!errorMessages.isEmpty()) { 151 Iterator<Message> iterator = errorMessages.iterator(); 152 buffer.append(iterator.next()); 153 while (iterator.hasNext()) { 154 buffer.append(". "); 155 buffer.append(iterator.next()); 156 } 157 } 158 Message message = ERR_SOFTREFCACHE_CANNOT_INITIALIZE.get( 159 buffer.toString()); 160 throw new ConfigException(message); 161 } 162 } 163 164 165 166 /** 167 * {@inheritDoc} 168 */ 169 public synchronized void finalizeEntryCache() 170 { 171 registeredConfiguration.removeSoftReferenceChangeListener (this); 172 173 shutdown = true; 174 175 dnMap.clear(); 176 idMap.clear(); 177 if (cleanerThread != null) { 178 for (int i = 0; cleanerThread.isAlive() && (i < 5); i++) { 179 cleanerThread.interrupt(); 180 try { 181 cleanerThread.join(10); 182 } catch (InterruptedException e) { 183 // We'll exit eventually. 184 } 185 } 186 cleanerThread = null; 187 } 188 } 189 190 191 192 /** 193 * {@inheritDoc} 194 */ 195 public boolean containsEntry(DN entryDN) 196 { 197 if (entryDN == null) { 198 return false; 199 } 200 201 // Indicate whether the DN map contains the specified DN. 202 return dnMap.containsKey(entryDN); 203 } 204 205 206 207 /** 208 * {@inheritDoc} 209 */ 210 public Entry getEntry(DN entryDN) 211 { 212 SoftReference<CacheEntry> ref = dnMap.get(entryDN); 213 if (ref == null) 214 { 215 // Indicate cache miss. 216 cacheMisses.getAndIncrement(); 217 return null; 218 } 219 else 220 { 221 CacheEntry cacheEntry = ref.get(); 222 if (cacheEntry == null) 223 { 224 // Indicate cache miss. 225 cacheMisses.getAndIncrement(); 226 return null; 227 } 228 else 229 { 230 // Indicate cache hit. 231 cacheHits.getAndIncrement(); 232 return cacheEntry.getEntry(); 233 } 234 } 235 } 236 237 238 239 /** 240 * {@inheritDoc} 241 */ 242 public long getEntryID(DN entryDN) 243 { 244 SoftReference<CacheEntry> ref = dnMap.get(entryDN); 245 if (ref == null) 246 { 247 return -1; 248 } 249 else 250 { 251 CacheEntry cacheEntry = ref.get(); 252 if (cacheEntry == null) 253 { 254 return -1; 255 } 256 else 257 { 258 return cacheEntry.getEntryID(); 259 } 260 } 261 } 262 263 264 265 /** 266 * {@inheritDoc} 267 */ 268 public DN getEntryDN(Backend backend, long entryID) 269 { 270 // Locate specific backend map and return the entry DN by ID. 271 ConcurrentHashMap<Long,SoftReference<CacheEntry>> 272 backendMap = idMap.get(backend); 273 if (backendMap != null) { 274 SoftReference<CacheEntry> ref = backendMap.get(entryID); 275 if (ref != null) { 276 CacheEntry cacheEntry = ref.get(); 277 if (cacheEntry != null) { 278 return cacheEntry.getDN(); 279 } 280 } 281 } 282 return null; 283 } 284 285 286 287 /** 288 * {@inheritDoc} 289 */ 290 public void putEntry(Entry entry, Backend backend, long entryID) 291 { 292 // Create the cache entry based on the provided information. 293 CacheEntry cacheEntry = new CacheEntry(entry, backend, entryID); 294 SoftReference<CacheEntry> ref = 295 new SoftReference<CacheEntry>(cacheEntry, referenceQueue); 296 297 SoftReference<CacheEntry> oldRef = dnMap.put(entry.getDN(), ref); 298 if (oldRef != null) 299 { 300 oldRef.clear(); 301 } 302 303 ConcurrentHashMap<Long,SoftReference<CacheEntry>> map = idMap.get(backend); 304 if (map == null) 305 { 306 map = new ConcurrentHashMap<Long,SoftReference<CacheEntry>>(); 307 map.put(entryID, ref); 308 idMap.put(backend, map); 309 } 310 else 311 { 312 oldRef = map.put(entryID, ref); 313 if (oldRef != null) 314 { 315 oldRef.clear(); 316 } 317 } 318 } 319 320 321 322 /** 323 * {@inheritDoc} 324 */ 325 public boolean putEntryIfAbsent(Entry entry, Backend backend, 326 long entryID) 327 { 328 // See if the entry already exists. If so, then return false. 329 if (dnMap.containsKey(entry.getDN())) 330 { 331 return false; 332 } 333 334 335 // Create the cache entry based on the provided information. 336 CacheEntry cacheEntry = new CacheEntry(entry, backend, entryID); 337 SoftReference<CacheEntry> ref = 338 new SoftReference<CacheEntry>(cacheEntry, referenceQueue); 339 340 dnMap.put(entry.getDN(), ref); 341 342 ConcurrentHashMap<Long,SoftReference<CacheEntry>> map = idMap.get(backend); 343 if (map == null) 344 { 345 map = new ConcurrentHashMap<Long,SoftReference<CacheEntry>>(); 346 map.put(entryID, ref); 347 idMap.put(backend, map); 348 } 349 else 350 { 351 map.put(entryID, ref); 352 } 353 354 return true; 355 } 356 357 358 359 /** 360 * {@inheritDoc} 361 */ 362 public void removeEntry(DN entryDN) 363 { 364 SoftReference<CacheEntry> ref = dnMap.remove(entryDN); 365 if (ref != null) 366 { 367 ref.clear(); 368 369 CacheEntry cacheEntry = ref.get(); 370 if (cacheEntry != null) 371 { 372 Backend backend = cacheEntry.getBackend(); 373 374 ConcurrentHashMap<Long,SoftReference<CacheEntry>> map = 375 idMap.get(backend); 376 if (map != null) 377 { 378 ref = map.remove(cacheEntry.getEntryID()); 379 if (ref != null) 380 { 381 ref.clear(); 382 } 383 // If this backend becomes empty now remove 384 // it from the idMap map. 385 if (map.isEmpty()) 386 { 387 idMap.remove(backend); 388 } 389 } 390 } 391 } 392 } 393 394 395 396 /** 397 * {@inheritDoc} 398 */ 399 public void clear() 400 { 401 dnMap.clear(); 402 idMap.clear(); 403 } 404 405 406 407 /** 408 * {@inheritDoc} 409 */ 410 public void clearBackend(Backend backend) 411 { 412 // FIXME -- Would it be better just to dump everything? 413 ConcurrentHashMap<Long,SoftReference<CacheEntry>> map = 414 idMap.remove(backend); 415 if (map != null) 416 { 417 for (SoftReference<CacheEntry> ref : map.values()) 418 { 419 CacheEntry cacheEntry = ref.get(); 420 if (cacheEntry != null) 421 { 422 dnMap.remove(cacheEntry.getDN()); 423 } 424 425 ref.clear(); 426 } 427 428 map.clear(); 429 } 430 } 431 432 433 434 /** 435 * {@inheritDoc} 436 */ 437 public void clearSubtree(DN baseDN) 438 { 439 // Determine the backend used to hold the specified base DN and clear it. 440 Backend backend = DirectoryServer.getBackend(baseDN); 441 if (backend == null) 442 { 443 // FIXME -- Should we clear everything just to be safe? 444 return; 445 } 446 else 447 { 448 clearBackend(backend); 449 } 450 } 451 452 453 454 /** 455 * {@inheritDoc} 456 */ 457 public void handleLowMemory() 458 { 459 // This function should automatically be taken care of by the nature of the 460 // soft references used in this cache. 461 // FIXME -- Do we need to do anything at all here? 462 } 463 464 465 466 /** 467 * {@inheritDoc} 468 */ 469 @Override() 470 public boolean isConfigurationAcceptable(EntryCacheCfg configuration, 471 List<Message> unacceptableReasons) 472 { 473 SoftReferenceEntryCacheCfg config = 474 (SoftReferenceEntryCacheCfg) configuration; 475 return isConfigurationChangeAcceptable(config, unacceptableReasons); 476 } 477 478 479 480 /** 481 * {@inheritDoc} 482 */ 483 public boolean isConfigurationChangeAcceptable( 484 SoftReferenceEntryCacheCfg configuration, 485 List<Message> unacceptableReasons) 486 { 487 boolean applyChanges = false; 488 EntryCacheCommon.ConfigErrorHandler errorHandler = 489 EntryCacheCommon.getConfigErrorHandler ( 490 EntryCacheCommon.ConfigPhase.PHASE_ACCEPTABLE, 491 unacceptableReasons, 492 null 493 ); 494 processEntryCacheConfig (configuration, applyChanges, errorHandler); 495 496 return errorHandler.getIsAcceptable(); 497 } 498 499 500 501 /** 502 * {@inheritDoc} 503 */ 504 public ConfigChangeResult applyConfigurationChange( 505 SoftReferenceEntryCacheCfg configuration 506 ) 507 { 508 boolean applyChanges = true; 509 ArrayList<Message> errorMessages = new ArrayList<Message>(); 510 EntryCacheCommon.ConfigErrorHandler errorHandler = 511 EntryCacheCommon.getConfigErrorHandler ( 512 EntryCacheCommon.ConfigPhase.PHASE_APPLY, null, errorMessages 513 ); 514 // Do not apply changes unless this cache is enabled. 515 if (configuration.isEnabled()) { 516 processEntryCacheConfig (configuration, applyChanges, errorHandler); 517 } 518 519 boolean adminActionRequired = errorHandler.getIsAdminActionRequired(); 520 ConfigChangeResult changeResult = new ConfigChangeResult( 521 errorHandler.getResultCode(), 522 adminActionRequired, 523 errorHandler.getErrorMessages() 524 ); 525 return changeResult; 526 } 527 528 529 530 /** 531 * Parses the provided configuration and configure the entry cache. 532 * 533 * @param configuration The new configuration containing the changes. 534 * @param applyChanges If true then take into account the new configuration. 535 * @param errorHandler An handler used to report errors. 536 * 537 * @return <CODE>true</CODE> if configuration is acceptable, 538 * or <CODE>false</CODE> otherwise. 539 */ 540 public boolean processEntryCacheConfig( 541 SoftReferenceEntryCacheCfg configuration, 542 boolean applyChanges, 543 EntryCacheCommon.ConfigErrorHandler errorHandler 544 ) 545 { 546 // Local variables to read configuration. 547 DN newConfigEntryDN; 548 long newLockTimeout; 549 HashSet<SearchFilter> newIncludeFilters = null; 550 HashSet<SearchFilter> newExcludeFilters = null; 551 552 // Read configuration. 553 newConfigEntryDN = configuration.dn(); 554 newLockTimeout = configuration.getLockTimeout(); 555 556 // Get include and exclude filters. 557 switch (errorHandler.getConfigPhase()) 558 { 559 case PHASE_INIT: 560 case PHASE_ACCEPTABLE: 561 case PHASE_APPLY: 562 newIncludeFilters = EntryCacheCommon.getFilters ( 563 configuration.getIncludeFilter(), 564 ERR_CACHE_INVALID_INCLUDE_FILTER, 565 errorHandler, 566 newConfigEntryDN 567 ); 568 newExcludeFilters = EntryCacheCommon.getFilters ( 569 configuration.getExcludeFilter(), 570 ERR_CACHE_INVALID_EXCLUDE_FILTER, 571 errorHandler, 572 newConfigEntryDN 573 ); 574 break; 575 } 576 577 if (applyChanges && errorHandler.getIsAcceptable()) 578 { 579 setLockTimeout(newLockTimeout); 580 setIncludeFilters(newIncludeFilters); 581 setExcludeFilters(newExcludeFilters); 582 583 registeredConfiguration = configuration; 584 } 585 586 return errorHandler.getIsAcceptable(); 587 } 588 589 590 591 592 /** 593 * Operate in a loop, receiving notification of soft references that have been 594 * freed and removing the corresponding entries from the cache. 595 */ 596 public void run() 597 { 598 while (!shutdown) 599 { 600 try 601 { 602 CacheEntry freedEntry = referenceQueue.remove().get(); 603 604 if (freedEntry != null) 605 { 606 SoftReference<CacheEntry> ref = dnMap.remove(freedEntry.getDN()); 607 608 if (ref != null) 609 { 610 // Note that the entry is there, but it could be a newer version of 611 // the entry so we want to make sure it's the same one. 612 CacheEntry removedEntry = ref.get(); 613 if (removedEntry != freedEntry) 614 { 615 dnMap.putIfAbsent(freedEntry.getDN(), ref); 616 } 617 else 618 { 619 ref.clear(); 620 621 Backend backend = freedEntry.getBackend(); 622 ConcurrentHashMap<Long,SoftReference<CacheEntry>> map = 623 idMap.get(backend); 624 if (map != null) 625 { 626 ref = map.remove(freedEntry.getEntryID()); 627 if (ref != null) 628 { 629 ref.clear(); 630 } 631 // If this backend becomes empty now remove 632 // it from the idMap map. 633 if (map.isEmpty()) { 634 idMap.remove(backend); 635 } 636 } 637 } 638 } 639 } 640 } 641 catch (Exception e) 642 { 643 if (debugEnabled()) 644 { 645 TRACER.debugCaught(DebugLogLevel.ERROR, e); 646 } 647 } 648 } 649 } 650 651 652 653 /** 654 * {@inheritDoc} 655 */ 656 public ArrayList<Attribute> getMonitorData() 657 { 658 ArrayList<Attribute> attrs = new ArrayList<Attribute>(); 659 660 try { 661 attrs = EntryCacheCommon.getGenericMonitorData( 662 new Long(cacheHits.longValue()), 663 // If cache misses is maintained by default cache 664 // get it from there and if not point to itself. 665 DirectoryServer.getEntryCache().getCacheMisses(), 666 null, 667 null, 668 new Long(dnMap.size()), 669 null 670 ); 671 } catch (Exception e) { 672 if (debugEnabled()) { 673 TRACER.debugCaught(DebugLogLevel.ERROR, e); 674 } 675 } 676 677 return attrs; 678 } 679 680 681 682 /** 683 * {@inheritDoc} 684 */ 685 public Long getCacheCount() 686 { 687 return new Long(dnMap.size()); 688 } 689 690 691 692 /** 693 * Return a verbose string representation of the current cache maps. 694 * This is useful primary for debugging and diagnostic purposes such 695 * as in the entry cache unit tests. 696 * @return String verbose string representation of the current cache 697 * maps in the following format: dn:id:backend 698 * one cache entry map representation per line 699 * or <CODE>null</CODE> if all maps are empty. 700 */ 701 private String toVerboseString() 702 { 703 String verboseString = new String(); 704 StringBuilder sb = new StringBuilder(); 705 706 // There're no locks in this cache to keep dnMap and idMap in 707 // sync. Examine dnMap only since its more likely to be up to 708 // date than idMap. Dont bother with copies either since this 709 // is SoftReference based implementation. 710 for(SoftReference<CacheEntry> ce : dnMap.values()) { 711 sb.append(ce.get().getDN().toString()); 712 sb.append(":"); 713 sb.append(Long.toString(ce.get().getEntryID())); 714 sb.append(":"); 715 sb.append(ce.get().getBackend().getBackendID()); 716 sb.append(ServerConstants.EOL); 717 } 718 719 verboseString = sb.toString(); 720 721 return (verboseString.length() > 0 ? verboseString : null); 722 } 723 } 724