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