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