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}