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}