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.Attribute; 042import org.opends.server.types.AttributeBuilder; 043import org.opends.server.types.AttributeType; 044import org.opends.server.types.DN; 045import org.opends.server.types.Entry; 046import org.opends.server.types.ObjectClass; 047 048import static org.opends.messages.ConfigMessages.*; 049import static org.opends.server.config.ConfigConstants.*; 050import static org.opends.server.util.StaticUtils.*; 051 052/** 053 * This class defines a configuration entry, which can hold zero or more 054 * attributes that may control the configuration of various components of the 055 * Directory Server. 056 */ 057@org.opends.server.types.PublicAPI( 058 stability=org.opends.server.types.StabilityLevel.VOLATILE, 059 mayInstantiate=true, 060 mayExtend=false, 061 mayInvoke=true) 062public final class ConfigEntry 063{ 064 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 065 066 067 068 069 /** The set of immediate children for this configuration entry. */ 070 private final ConcurrentMap<DN,ConfigEntry> children; 071 072 /** The immediate parent for this configuration entry. */ 073 private ConfigEntry parent; 074 075 /** The set of add listeners that have been registered with this entry. */ 076 private final CopyOnWriteArrayList<ConfigAddListener> addListeners; 077 078 /** The set of change listeners that have been registered with this entry. */ 079 private final CopyOnWriteArrayList<ConfigChangeListener> changeListeners; 080 081 /** The set of delete listeners that have been registered with this entry. */ 082 private final CopyOnWriteArrayList<ConfigDeleteListener> deleteListeners; 083 084 /** The actual entry wrapped by this configuration entry. */ 085 private Entry entry; 086 087 /** The lock used to provide threadsafe access to this configuration entry. */ 088 private Object entryLock; 089 090 091 092 /** 093 * Creates a new config entry with the provided information. 094 * 095 * @param entry The entry that will be encapsulated by this config entry. 096 * @param parent The configuration entry that is the immediate parent for 097 * this configuration entry. It may be <CODE>null</CODE> if 098 * this entry is the configuration root. 099 */ 100 public ConfigEntry(Entry entry, ConfigEntry parent) 101 { 102 this.entry = entry; 103 this.parent = parent; 104 105 children = new ConcurrentHashMap<>(); 106 addListeners = new CopyOnWriteArrayList<>(); 107 changeListeners = new CopyOnWriteArrayList<>(); 108 deleteListeners = new CopyOnWriteArrayList<>(); 109 entryLock = new Object(); 110 } 111 112 113 114 /** 115 * Retrieves the actual entry wrapped by this configuration entry. 116 * 117 * @return The actual entry wrapped by this configuration entry. 118 */ 119 public Entry getEntry() 120 { 121 return entry; 122 } 123 124 125 126 /** 127 * Replaces the actual entry wrapped by this configuration entry with the 128 * provided entry. The given entry must be non-null and must have the same DN 129 * as the current entry. No validation will be performed on the target entry. 130 * All add/delete/change listeners that have been registered will be 131 * maintained, it will keep the same parent and set of children, and all other 132 * settings will remain the same. 133 * 134 * @param entry The new entry to store in this config entry. 135 */ 136 public void setEntry(Entry entry) 137 { 138 synchronized (entryLock) 139 { 140 this.entry = entry; 141 } 142 } 143 144 145 146 /** 147 * Retrieves the DN for this configuration entry. 148 * 149 * @return The DN for this configuration entry. 150 */ 151 public DN getDN() 152 { 153 return entry.getName(); 154 } 155 156 157 158 /** 159 * Indicates whether this configuration entry contains the specified 160 * objectclass. 161 * 162 * @param name The name of the objectclass for which to make the 163 * determination. 164 * 165 * @return <CODE>true</CODE> if this configuration entry contains the 166 * specified objectclass, or <CODE>false</CODE> if not. 167 */ 168 public boolean hasObjectClass(String name) 169 { 170 ObjectClass oc = DirectoryServer.getObjectClass(name.toLowerCase()); 171 if (oc == null) 172 { 173 oc = DirectoryServer.getDefaultObjectClass(name); 174 } 175 176 return entry.hasObjectClass(oc); 177 } 178 179 180 181 /** 182 * Retrieves the specified configuration attribute from this configuration 183 * entry. 184 * 185 * @param stub The stub to use to format the returned configuration 186 * attribute. 187 * 188 * @return The requested configuration attribute from this configuration 189 * entry, or <CODE>null</CODE> if no such attribute is present in 190 * this entry. 191 * 192 * @throws ConfigException If the specified attribute exists but cannot be 193 * interpreted as the specified type of 194 * configuration attribute. 195 */ 196 public ConfigAttribute getConfigAttribute(ConfigAttribute stub) 197 throws ConfigException 198 { 199 String attrName = stub.getName(); 200 AttributeType attrType = DirectoryServer.getAttributeTypeOrDefault(attrName.toLowerCase(), attrName); 201 List<Attribute> attrList = entry.getAttribute(attrType); 202 if (attrList != null && !attrList.isEmpty()) 203 { 204 return stub.getConfigAttribute(attrList); 205 } 206 return null; 207 } 208 209 210 211 /** 212 * Puts the provided configuration attribute in this entry (adding a new 213 * attribute if one doesn't exist, or replacing it if one does). This must 214 * only be performed on a duplicate of a configuration entry and never on a 215 * configuration entry itself. 216 * 217 * @param attribute The configuration attribute to use. 218 */ 219 public void putConfigAttribute(ConfigAttribute attribute) 220 { 221 String name = attribute.getName(); 222 AttributeType attrType = DirectoryServer.getAttributeTypeOrDefault( 223 name.toLowerCase(), name, attribute.getSyntax()); 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}