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