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