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.config; 028 029 030 031 import java.util.ArrayList; 032 import java.util.LinkedHashSet; 033 import java.util.List; 034 import java.util.concurrent.ConcurrentHashMap; 035 import java.util.concurrent.CopyOnWriteArrayList; 036 037 import org.opends.messages.Message; 038 import org.opends.server.api.ConfigAddListener; 039 import org.opends.server.api.ConfigChangeListener; 040 import org.opends.server.api.ConfigDeleteListener; 041 import org.opends.server.core.DirectoryServer; 042 import org.opends.server.loggers.debug.DebugTracer; 043 import org.opends.server.types.Attribute; 044 import org.opends.server.types.AttributeType; 045 import org.opends.server.types.DN; 046 import org.opends.server.types.Entry; 047 import org.opends.server.types.ObjectClass; 048 import org.opends.server.types.DebugLogLevel; 049 050 import static org.opends.messages.ConfigMessages.*; 051 import static org.opends.server.config.ConfigConstants.*; 052 import static org.opends.server.loggers.debug.DebugLogger.*; 053 import static org.opends.server.util.StaticUtils.*; 054 055 056 057 /** 058 * This class defines a configuration entry, which can hold zero or more 059 * attributes that may control the configuration of various components of the 060 * Directory Server. 061 */ 062 @org.opends.server.types.PublicAPI( 063 stability=org.opends.server.types.StabilityLevel.VOLATILE, 064 mayInstantiate=true, 065 mayExtend=false, 066 mayInvoke=true) 067 public final class ConfigEntry 068 { 069 /** 070 * The tracer object for the debug logger. 071 */ 072 private static final DebugTracer TRACER = getTracer(); 073 074 075 076 077 // The set of immediate children for this configuration entry. 078 private ConcurrentHashMap<DN,ConfigEntry> children; 079 080 // The immediate parent for this configuration entry. 081 private ConfigEntry parent; 082 083 // The set of add listeners that have been registered with this entry. 084 private CopyOnWriteArrayList<ConfigAddListener> addListeners; 085 086 // The set of change listeners that have been registered with this entry. 087 private CopyOnWriteArrayList<ConfigChangeListener> changeListeners; 088 089 // The set of delete listeners that have been registered with this entry. 090 private CopyOnWriteArrayList<ConfigDeleteListener> deleteListeners; 091 092 // The actual entry wrapped by this configuration entry. 093 private Entry entry; 094 095 // The lock used to provide threadsafe access to this configuration entry. 096 private Object entryLock; 097 098 099 100 /** 101 * Creates a new config entry with the provided information. 102 * 103 * @param entry The entry that will be encapsulated by this config entry. 104 * @param parent The configuration entry that is the immediate parent for 105 * this configuration entry. It may be <CODE>null</CODE> if 106 * this entry is the configuration root. 107 */ 108 public ConfigEntry(Entry entry, ConfigEntry parent) 109 { 110 this.entry = entry; 111 this.parent = parent; 112 113 children = new ConcurrentHashMap<DN,ConfigEntry>(); 114 addListeners = new CopyOnWriteArrayList<ConfigAddListener>(); 115 changeListeners = new CopyOnWriteArrayList<ConfigChangeListener>(); 116 deleteListeners = new CopyOnWriteArrayList<ConfigDeleteListener>(); 117 entryLock = new Object(); 118 } 119 120 121 122 /** 123 * Retrieves the actual entry wrapped by this configuration entry. 124 * 125 * @return The actual entry wrapped by this configuration entry. 126 */ 127 public Entry getEntry() 128 { 129 return entry; 130 } 131 132 133 134 /** 135 * Replaces the actual entry wrapped by this configuration entry with the 136 * provided entry. The given entry must be non-null and must have the same DN 137 * as the current entry. No validation will be performed on the target entry. 138 * All add/delete/change listeners that have been registered will be 139 * maintained, it will keep the same parent and set of children, and all other 140 * settings will remain the same. 141 * 142 * @param entry The new entry to store in this config entry. 143 */ 144 public void setEntry(Entry entry) 145 { 146 synchronized (entryLock) 147 { 148 this.entry = entry; 149 } 150 } 151 152 153 154 /** 155 * Retrieves the DN for this configuration entry. 156 * 157 * @return The DN for this configuration entry. 158 */ 159 public DN getDN() 160 { 161 return entry.getDN(); 162 } 163 164 165 166 /** 167 * Indicates whether this configuration entry contains the specified 168 * objectclass. 169 * 170 * @param name The name of the objectclass for which to make the 171 * determination. 172 * 173 * @return <CODE>true</CODE> if this configuration entry contains the 174 * specified objectclass, or <CODE>false</CODE> if not. 175 */ 176 public boolean hasObjectClass(String name) 177 { 178 ObjectClass oc = DirectoryServer.getObjectClass(name.toLowerCase()); 179 if (oc == null) 180 { 181 oc = DirectoryServer.getDefaultObjectClass(name); 182 } 183 184 return entry.hasObjectClass(oc); 185 } 186 187 188 189 /** 190 * Retrieves the specified configuration attribute from this configuration 191 * entry. 192 * 193 * @param stub The stub to use to format the returned configuration 194 * attribute. 195 * 196 * @return The requested configuration attribute from this configuration 197 * entry, or <CODE>null</CODE> if no such attribute is present in 198 * this entry. 199 * 200 * @throws ConfigException If the specified attribute exists but cannot be 201 * interpreted as the specified type of 202 * configuration attribute. 203 */ 204 public ConfigAttribute getConfigAttribute(ConfigAttribute stub) 205 throws ConfigException 206 { 207 String attrName = stub.getName(); 208 AttributeType attrType = 209 DirectoryServer.getAttributeType(attrName.toLowerCase()); 210 if (attrType == null) 211 { 212 attrType = DirectoryServer.getDefaultAttributeType(attrName); 213 } 214 215 List<Attribute> attrList = entry.getAttribute(attrType); 216 if ((attrList == null) || (attrList.isEmpty())) 217 { 218 return null; 219 } 220 221 return stub.getConfigAttribute(attrList); 222 } 223 224 225 226 /** 227 * Puts the provided configuration attribute in this entry (adding a new 228 * attribute if one doesn't exist, or replacing it if one does). This must 229 * only be performed on a duplicate of a configuration entry and never on a 230 * configuration entry itself. 231 * 232 * @param attribute The configuration attribute to use. 233 */ 234 public void putConfigAttribute(ConfigAttribute attribute) 235 { 236 String name = attribute.getName(); 237 AttributeType attrType = 238 DirectoryServer.getAttributeType(name.toLowerCase()); 239 if (attrType == null) 240 { 241 attrType = 242 DirectoryServer.getDefaultAttributeType(name, attribute.getSyntax()); 243 } 244 245 ArrayList<Attribute> attrs = new ArrayList<Attribute>(2); 246 attrs.add(new Attribute(attrType, name, attribute.getActiveValues())); 247 if (attribute.hasPendingValues()) 248 { 249 LinkedHashSet<String> options = new LinkedHashSet<String>(1); 250 options.add(OPTION_PENDING_VALUES); 251 attrs.add(new Attribute(attrType, name, options, 252 attribute.getPendingValues())); 253 } 254 255 entry.putAttribute(attrType, attrs); 256 } 257 258 259 260 /** 261 * Removes the specified configuration attribute from the entry. This will 262 * have no impact if the specified attribute is not contained in the entry. 263 * 264 * @param lowerName The name of the configuration attribute to remove from 265 * the entry, formatted in all lowercase characters. 266 * 267 * @return <CODE>true</CODE> if the requested attribute was found and 268 * removed, or <CODE>false</CODE> if not. 269 */ 270 public boolean removeConfigAttribute(String lowerName) 271 { 272 for (AttributeType t : entry.getUserAttributes().keySet()) 273 { 274 if (t.hasNameOrOID(lowerName)) 275 { 276 entry.getUserAttributes().remove(t); 277 return true; 278 } 279 } 280 281 for (AttributeType t : entry.getOperationalAttributes().keySet()) 282 { 283 if (t.hasNameOrOID(lowerName)) 284 { 285 entry.getOperationalAttributes().remove(t); 286 return true; 287 } 288 } 289 290 return false; 291 } 292 293 294 295 /** 296 * Retrieves the configuration entry that is the immediate parent for this 297 * configuration entry. 298 * 299 * @return The configuration entry that is the immediate parent for this 300 * configuration entry. It may be <CODE>null</CODE> if this entry is 301 * the configuration root. 302 */ 303 public ConfigEntry getParent() 304 { 305 return parent; 306 } 307 308 309 310 /** 311 * Retrieves the set of children associated with this configuration entry. 312 * This list should not be altered by the caller. 313 * 314 * @return The set of children associated with this configuration entry. 315 */ 316 public ConcurrentHashMap<DN,ConfigEntry> getChildren() 317 { 318 return children; 319 } 320 321 322 323 /** 324 * Indicates whether this entry has any children. 325 * 326 * @return <CODE>true</CODE> if this entry has one or more children, or 327 * <CODE>false</CODE> if not. 328 */ 329 public boolean hasChildren() 330 { 331 return (! children.isEmpty()); 332 } 333 334 335 336 /** 337 * Adds the specified entry as a child of this configuration entry. No check 338 * will be made to determine whether the specified entry actually should be a 339 * child of this entry, and this method will not notify any add listeners that 340 * might be registered with this configuration entry. 341 * 342 * @param childEntry The entry to add as a child of this configuration 343 * entry. 344 * 345 * @throws ConfigException If the provided entry could not be added as a 346 * child of this configuration entry (e.g., because 347 * another entry already exists with the same DN). 348 */ 349 public void addChild(ConfigEntry childEntry) 350 throws ConfigException 351 { 352 ConfigEntry conflictingChild; 353 354 synchronized (entryLock) 355 { 356 conflictingChild = children.putIfAbsent(childEntry.getDN(), childEntry); 357 } 358 359 if (conflictingChild != null) 360 { 361 Message message = ERR_CONFIG_ENTRY_CONFLICTING_CHILD.get( 362 conflictingChild.getDN().toString(), entry.getDN().toString()); 363 throw new ConfigException(message); 364 } 365 } 366 367 368 369 /** 370 * Attempts to remove the child entry with the specified DN. This method will 371 * not notify any delete listeners that might be registered with this 372 * configuration entry. 373 * 374 * @param childDN The DN of the child entry to remove from this config 375 * entry. 376 * 377 * @return The configuration entry that was removed as a child of this 378 * entry. 379 * 380 * @throws ConfigException If the specified child entry did not exist or if 381 * it had children of its own. 382 */ 383 public ConfigEntry removeChild(DN childDN) 384 throws ConfigException 385 { 386 synchronized (entryLock) 387 { 388 try 389 { 390 ConfigEntry childEntry = children.get(childDN); 391 if (childEntry == null) 392 { 393 Message message = ERR_CONFIG_ENTRY_NO_SUCH_CHILD.get( 394 childDN.toString(), entry.getDN().toString()); 395 throw new ConfigException(message); 396 } 397 398 if (childEntry.hasChildren()) 399 { 400 Message message = ERR_CONFIG_ENTRY_CANNOT_REMOVE_NONLEAF.get( 401 childDN.toString(), entry.getDN().toString()); 402 throw new ConfigException(message); 403 } 404 405 children.remove(childDN); 406 return childEntry; 407 } 408 catch (ConfigException ce) 409 { 410 throw ce; 411 } 412 catch (Exception e) 413 { 414 if (debugEnabled()) 415 { 416 TRACER.debugCaught(DebugLogLevel.ERROR, e); 417 } 418 419 Message message = ERR_CONFIG_ENTRY_CANNOT_REMOVE_CHILD. 420 get(String.valueOf(childDN), String.valueOf(entry.getDN()), 421 stackTraceToSingleLineString(e)); 422 throw new ConfigException(message, e); 423 } 424 } 425 } 426 427 428 429 /** 430 * Creates a duplicate of this configuration entry that should be used when 431 * making changes to this entry. Changes should only be made to the duplicate 432 * (never the original) and then applied to the original. Note that this 433 * method and the other methods used to make changes to the entry contents are 434 * not threadsafe and therefore must be externally synchronized to ensure that 435 * only one change may be in progress at any given time. 436 * 437 * @return A duplicate of this configuration entry that should be used when 438 * making changes to this entry. 439 */ 440 public ConfigEntry duplicate() 441 { 442 return new ConfigEntry(entry.duplicate(false), parent); 443 } 444 445 446 447 /** 448 * Retrieves the set of change listeners that have been registered with this 449 * configuration entry. 450 * 451 * @return The set of change listeners that have been registered with this 452 * configuration entry. 453 */ 454 public CopyOnWriteArrayList<ConfigChangeListener> getChangeListeners() 455 { 456 return changeListeners; 457 } 458 459 460 461 /** 462 * Registers the provided change listener so that it will be notified of any 463 * changes to this configuration entry. No check will be made to determine 464 * whether the provided listener is already registered. 465 * 466 * @param listener The change listener to register with this config entry. 467 */ 468 public void registerChangeListener(ConfigChangeListener listener) 469 { 470 changeListeners.add(listener); 471 } 472 473 474 475 /** 476 * Attempts to deregister the provided change listener with this configuration 477 * entry. 478 * 479 * @param listener The change listener to deregister with this config entry. 480 * 481 * @return <CODE>true</CODE> if the specified listener was deregistered, or 482 * <CODE>false</CODE> if it was not. 483 */ 484 public boolean deregisterChangeListener(ConfigChangeListener listener) 485 { 486 return changeListeners.remove(listener); 487 } 488 489 490 491 /** 492 * Retrieves the set of config add listeners that have been registered for 493 * this entry. 494 * 495 * @return The set of config add listeners that have been registered for this 496 * entry. 497 */ 498 public CopyOnWriteArrayList<ConfigAddListener> getAddListeners() 499 { 500 return addListeners; 501 } 502 503 504 505 /** 506 * Registers the provided add listener so that it will be notified if any new 507 * entries are added immediately below this configuration entry. 508 * 509 * @param listener The add listener that should be registered. 510 */ 511 public void registerAddListener(ConfigAddListener listener) 512 { 513 addListeners.addIfAbsent(listener); 514 } 515 516 517 518 /** 519 * Deregisters the provided add listener so that it will no longer be 520 * notified if any new entries are added immediately below this configuration 521 * entry. 522 * 523 * @param listener The add listener that should be deregistered. 524 */ 525 public void deregisterAddListener(ConfigAddListener listener) 526 { 527 addListeners.remove(listener); 528 } 529 530 531 532 /** 533 * Retrieves the set of config delete listeners that have been registered for 534 * this entry. 535 * 536 * @return The set of config delete listeners that have been registered for 537 * this entry. 538 */ 539 public CopyOnWriteArrayList<ConfigDeleteListener> getDeleteListeners() 540 { 541 return deleteListeners; 542 } 543 544 545 546 /** 547 * Registers the provided delete listener so that it will be notified if any 548 * entries are deleted immediately below this configuration entry. 549 * 550 * @param listener The delete listener that should be registered. 551 */ 552 public void registerDeleteListener(ConfigDeleteListener listener) 553 { 554 deleteListeners.addIfAbsent(listener); 555 } 556 557 558 559 /** 560 * Deregisters the provided delete listener so that it will no longer be 561 * notified if any new are removed immediately below this configuration entry. 562 * 563 * @param listener The delete listener that should be deregistered. 564 */ 565 public void deregisterDeleteListener(ConfigDeleteListener listener) 566 { 567 deleteListeners.remove(listener); 568 } 569 } 570