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