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}