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    
028    package org.opends.server.admin.server;
029    
030    
031    
032    import static org.opends.messages.AdminMessages.*;
033    import static org.opends.server.loggers.debug.DebugLogger.*;
034    import static org.opends.server.util.StaticUtils.*;
035    
036    import java.util.Collections;
037    import java.util.LinkedList;
038    import java.util.List;
039    import java.util.Map;
040    import java.util.Set;
041    import java.util.SortedSet;
042    
043    import org.opends.messages.AdminMessages;
044    import org.opends.messages.Message;
045    import org.opends.server.admin.Configuration;
046    import org.opends.server.admin.Constraint;
047    import org.opends.server.admin.InstantiableRelationDefinition;
048    import org.opends.server.admin.ManagedObjectDefinition;
049    import org.opends.server.admin.ManagedObjectPath;
050    import org.opends.server.admin.OptionalRelationDefinition;
051    import org.opends.server.admin.PropertyDefinition;
052    import org.opends.server.admin.PropertyProvider;
053    import org.opends.server.admin.RelationDefinition;
054    import org.opends.server.admin.SingletonRelationDefinition;
055    import org.opends.server.api.ConfigAddListener;
056    import org.opends.server.api.ConfigChangeListener;
057    import org.opends.server.api.ConfigDeleteListener;
058    import org.opends.server.config.ConfigEntry;
059    import org.opends.server.config.ConfigException;
060    import org.opends.server.core.DirectoryServer;
061    import org.opends.server.loggers.debug.DebugTracer;
062    import org.opends.server.types.DN;
063    import org.opends.server.types.DebugLogLevel;
064    
065    
066    
067    /**
068     * A server-side managed object.
069     *
070     * @param <S>
071     *          The type of server configuration represented by the server
072     *          managed object.
073     */
074    public final class ServerManagedObject<S extends Configuration> implements
075        PropertyProvider {
076    
077      /**
078       * The tracer object for the debug logger.
079       */
080      private static final DebugTracer TRACER = getTracer();
081    
082      // The configuration entry associated with this server managed
083      // object (null if root).
084      private ConfigEntry configEntry;
085    
086      // The management context.
087      private final ServerManagementContext context = ServerManagementContext
088          .getInstance();
089    
090      // The managed object's definition.
091      private final ManagedObjectDefinition<?, S> definition;
092    
093      // The managed object path identifying this managed object's
094      // location.
095      private final ManagedObjectPath<?, S> path;
096    
097      // The managed object's properties.
098      private final Map<PropertyDefinition<?>, SortedSet<?>> properties;
099    
100    
101    
102      /**
103       * Creates an new server side managed object.
104       *
105       * @param path
106       *          The managed object path.
107       * @param d
108       *          The managed object definition.
109       * @param properties
110       *          The managed object's properties.
111       * @param configEntry
112       *          The configuration entry associated with the managed
113       *          object.
114       */
115      ServerManagedObject(ManagedObjectPath<?, S> path,
116          ManagedObjectDefinition<?, S> d,
117          Map<PropertyDefinition<?>, SortedSet<?>> properties,
118          ConfigEntry configEntry) {
119        this.definition = d;
120        this.path = path;
121        this.properties = properties;
122        this.configEntry = configEntry;
123      }
124    
125    
126    
127      /**
128       * Deregisters an existing configuration add listener.
129       *
130       * @param <M>
131       *          The type of the child server configuration object.
132       * @param d
133       *          The instantiable relation definition.
134       * @param listener
135       *          The configuration add listener.
136       * @throws IllegalArgumentException
137       *           If the instantiable relation definition is not
138       *           associated with this managed object's definition.
139       */
140      public <M extends Configuration> void deregisterAddListener(
141          InstantiableRelationDefinition<?, M> d,
142          ConfigurationAddListener<M> listener) throws IllegalArgumentException {
143        validateRelationDefinition(d);
144    
145        DN baseDN = DNBuilder.create(path, d);
146        deregisterAddListener(baseDN, listener);
147      }
148    
149    
150    
151      /**
152       * Deregisters an existing server managed object add listener.
153       *
154       * @param <M>
155       *          The type of the child server configuration object.
156       * @param d
157       *          The instantiable relation definition.
158       * @param listener
159       *          The server managed object add listener.
160       * @throws IllegalArgumentException
161       *           If the instantiable relation definition is not
162       *           associated with this managed object's definition.
163       */
164      public <M extends Configuration> void deregisterAddListener(
165          InstantiableRelationDefinition<?, M> d,
166          ServerManagedObjectAddListener<M> listener)
167          throws IllegalArgumentException {
168        validateRelationDefinition(d);
169    
170        DN baseDN = DNBuilder.create(path, d);
171        deregisterAddListener(baseDN, listener);
172      }
173    
174    
175    
176      /**
177       * Deregisters an existing configuration add listener.
178       *
179       * @param <M>
180       *          The type of the child server configuration object.
181       * @param d
182       *          The optional relation definition.
183       * @param listener
184       *          The configuration add listener.
185       * @throws IllegalArgumentException
186       *           If the optional relation definition is not associated
187       *           with this managed object's definition.
188       */
189      public <M extends Configuration> void deregisterAddListener(
190          OptionalRelationDefinition<?, M> d, ConfigurationAddListener<M> listener)
191          throws IllegalArgumentException {
192        validateRelationDefinition(d);
193    
194        DN baseDN = DNBuilder.create(path, d).getParent();
195        deregisterAddListener(baseDN, listener);
196      }
197    
198    
199    
200      /**
201       * Deregisters an existing server managed object add listener.
202       *
203       * @param <M>
204       *          The type of the child server configuration object.
205       * @param d
206       *          The optional relation definition.
207       * @param listener
208       *          The server managed object add listener.
209       * @throws IllegalArgumentException
210       *           If the optional relation definition is not associated
211       *           with this managed object's definition.
212       */
213      public <M extends Configuration> void deregisterAddListener(
214          OptionalRelationDefinition<?, M> d,
215          ServerManagedObjectAddListener<M> listener)
216          throws IllegalArgumentException {
217        validateRelationDefinition(d);
218    
219        DN baseDN = DNBuilder.create(path, d).getParent();
220        deregisterAddListener(baseDN, listener);
221      }
222    
223    
224    
225      /**
226       * Deregisters an existing configuration change listener.
227       *
228       * @param listener
229       *          The configuration change listener.
230       */
231      public void deregisterChangeListener(
232          ConfigurationChangeListener<? super S> listener) {
233        for (ConfigChangeListener l : configEntry.getChangeListeners()) {
234          if (l instanceof ConfigChangeListenerAdaptor) {
235            ConfigChangeListenerAdaptor<?> adaptor =
236              (ConfigChangeListenerAdaptor<?>) l;
237            ServerManagedObjectChangeListener<?> l2 = adaptor
238                .getServerManagedObjectChangeListener();
239            if (l2 instanceof ServerManagedObjectChangeListenerAdaptor<?>) {
240              ServerManagedObjectChangeListenerAdaptor<?> adaptor2 =
241                (ServerManagedObjectChangeListenerAdaptor<?>) l2;
242              if (adaptor2.getConfigurationChangeListener() == listener) {
243                adaptor.finalizeChangeListener();
244                configEntry.deregisterChangeListener(adaptor);
245              }
246            }
247          }
248        }
249      }
250    
251    
252    
253      /**
254       * Deregisters an existing server managed object change listener.
255       *
256       * @param listener
257       *          The server managed object change listener.
258       */
259      public void deregisterChangeListener(
260          ServerManagedObjectChangeListener<? super S> listener) {
261        for (ConfigChangeListener l : configEntry.getChangeListeners()) {
262          if (l instanceof ConfigChangeListenerAdaptor) {
263            ConfigChangeListenerAdaptor<?> adaptor =
264              (ConfigChangeListenerAdaptor<?>) l;
265            if (adaptor.getServerManagedObjectChangeListener() == listener) {
266              adaptor.finalizeChangeListener();
267              configEntry.deregisterChangeListener(adaptor);
268            }
269          }
270        }
271      }
272    
273    
274    
275      /**
276       * Deregisters an existing configuration delete listener.
277       *
278       * @param <M>
279       *          The type of the child server configuration object.
280       * @param d
281       *          The instantiable relation definition.
282       * @param listener
283       *          The configuration delete listener.
284       * @throws IllegalArgumentException
285       *           If the instantiable relation definition is not
286       *           associated with this managed object's definition.
287       */
288      public <M extends Configuration> void deregisterDeleteListener(
289          InstantiableRelationDefinition<?, M> d,
290          ConfigurationDeleteListener<M> listener) throws IllegalArgumentException {
291        validateRelationDefinition(d);
292    
293        DN baseDN = DNBuilder.create(path, d);
294        deregisterDeleteListener(baseDN, listener);
295      }
296    
297    
298    
299      /**
300       * Deregisters an existing server managed object delete listener.
301       *
302       * @param <M>
303       *          The type of the child server configuration object.
304       * @param d
305       *          The instantiable relation definition.
306       * @param listener
307       *          The server managed object delete listener.
308       * @throws IllegalArgumentException
309       *           If the instantiable relation definition is not
310       *           associated with this managed object's definition.
311       */
312      public <M extends Configuration> void deregisterDeleteListener(
313          InstantiableRelationDefinition<?, M> d,
314          ServerManagedObjectDeleteListener<M> listener)
315          throws IllegalArgumentException {
316        validateRelationDefinition(d);
317    
318        DN baseDN = DNBuilder.create(path, d);
319        deregisterDeleteListener(baseDN, listener);
320      }
321    
322    
323    
324      /**
325       * Deregisters an existing configuration delete listener.
326       *
327       * @param <M>
328       *          The type of the child server configuration object.
329       * @param d
330       *          The optional relation definition.
331       * @param listener
332       *          The configuration delete listener.
333       * @throws IllegalArgumentException
334       *           If the optional relation definition is not associated
335       *           with this managed object's definition.
336       */
337      public <M extends Configuration> void deregisterDeleteListener(
338          OptionalRelationDefinition<?, M> d,
339          ConfigurationDeleteListener<M> listener) throws IllegalArgumentException {
340        validateRelationDefinition(d);
341    
342        DN baseDN = DNBuilder.create(path, d).getParent();
343        deregisterDeleteListener(baseDN, listener);
344      }
345    
346    
347    
348      /**
349       * Deregisters an existing server managed object delete listener.
350       *
351       * @param <M>
352       *          The type of the child server configuration object.
353       * @param d
354       *          The optional relation definition.
355       * @param listener
356       *          The server managed object delete listener.
357       * @throws IllegalArgumentException
358       *           If the optional relation definition is not associated
359       *           with this managed object's definition.
360       */
361      public <M extends Configuration> void deregisterDeleteListener(
362          OptionalRelationDefinition<?, M> d,
363          ServerManagedObjectDeleteListener<M> listener)
364          throws IllegalArgumentException {
365        validateRelationDefinition(d);
366    
367        DN baseDN = DNBuilder.create(path, d).getParent();
368        deregisterDeleteListener(baseDN, listener);
369      }
370    
371    
372    
373      /**
374       * Retrieve an instantiable child managed object.
375       *
376       * @param <M>
377       *          The requested type of the child server managed object
378       *          configuration.
379       * @param d
380       *          The instantiable relation definition.
381       * @param name
382       *          The name of the child managed object.
383       * @return Returns the instantiable child managed object.
384       * @throws IllegalArgumentException
385       *           If the relation definition is not associated with this
386       *           managed object's definition.
387       * @throws ConfigException
388       *           If the child managed object could not be found or if it
389       *           could not be decoded.
390       */
391      public <M extends Configuration> ServerManagedObject<? extends M> getChild(
392          InstantiableRelationDefinition<?, M> d, String name)
393          throws IllegalArgumentException, ConfigException {
394        validateRelationDefinition(d);
395        return context.getManagedObject(path.child(d, name));
396      }
397    
398    
399    
400      /**
401       * Retrieve an optional child managed object.
402       *
403       * @param <M>
404       *          The requested type of the child server managed object
405       *          configuration.
406       * @param d
407       *          The optional relation definition.
408       * @return Returns the optional child managed object.
409       * @throws IllegalArgumentException
410       *           If the optional relation definition is not associated
411       *           with this managed object's definition.
412       * @throws ConfigException
413       *           If the child managed object could not be found or if it
414       *           could not be decoded.
415       */
416      public <M extends Configuration> ServerManagedObject<? extends M> getChild(
417          OptionalRelationDefinition<?, M> d) throws IllegalArgumentException,
418          ConfigException {
419        validateRelationDefinition(d);
420        return context.getManagedObject(path.child(d));
421      }
422    
423    
424    
425      /**
426       * Retrieve a singleton child managed object.
427       *
428       * @param <M>
429       *          The requested type of the child server managed object
430       *          configuration.
431       * @param d
432       *          The singleton relation definition.
433       * @return Returns the singleton child managed object.
434       * @throws IllegalArgumentException
435       *           If the relation definition is not associated with this
436       *           managed object's definition.
437       * @throws ConfigException
438       *           If the child managed object could not be found or if it
439       *           could not be decoded.
440       */
441      public <M extends Configuration> ServerManagedObject<? extends M> getChild(
442          SingletonRelationDefinition<?, M> d) throws IllegalArgumentException,
443          ConfigException {
444        validateRelationDefinition(d);
445        return context.getManagedObject(path.child(d));
446      }
447    
448    
449    
450      /**
451       * Creates a server configuration view of this managed object.
452       *
453       * @return Returns the server configuration view of this managed
454       *         object.
455       */
456      public S getConfiguration() {
457        return definition.createServerConfiguration(this);
458      }
459    
460    
461    
462      /**
463       * Get the DN of the LDAP entry associated with this server managed
464       * object.
465       *
466       * @return Returns the DN of the LDAP entry associated with this
467       *         server managed object, or an null DN if this is the root
468       *         managed object.
469       */
470      public DN getDN() {
471        if (configEntry != null) {
472          return configEntry.getDN();
473        } else {
474          return DN.nullDN();
475        }
476      }
477    
478    
479    
480      /**
481       * Get the definition associated with this server managed object.
482       *
483       * @return Returns the definition associated with this server
484       *         managed object.
485       */
486      public ManagedObjectDefinition<?, S> getManagedObjectDefinition() {
487        return definition;
488      }
489    
490    
491    
492      /**
493       * Get the path of this server managed object.
494       *
495       * @return Returns the path of this server managed object.
496       */
497      public ManagedObjectPath<?, S> getManagedObjectPath() {
498        return path;
499      }
500    
501    
502    
503      /**
504       * Get the effective value of the specified property. If the
505       * property is multi-valued then just the first value is returned.
506       * If the property does not have a value then its default value is
507       * returned if it has one, or <code>null</code> indicating that
508       * any default behavior is applicable.
509       *
510       * @param <T>
511       *          The type of the property to be retrieved.
512       * @param d
513       *          The property to be retrieved.
514       * @return Returns the property's effective value, or
515       *         <code>null</code> indicating that any default behavior
516       *         is applicable.
517       * @throws IllegalArgumentException
518       *           If the property definition is not associated with this
519       *           managed object's definition.
520       */
521      public <T> T getPropertyValue(PropertyDefinition<T> d)
522          throws IllegalArgumentException {
523        Set<T> values = getPropertyValues(d);
524        if (values.isEmpty()) {
525          return null;
526        } else {
527          return values.iterator().next();
528        }
529      }
530    
531    
532    
533      /**
534       * Get the effective values of the specified property. If the
535       * property does not have any values then its default values are
536       * returned if it has any, or an empty set indicating that any
537       * default behavior is applicable.
538       *
539       * @param <T>
540       *          The type of the property to be retrieved.
541       * @param d
542       *          The property to be retrieved.
543       * @return Returns an unmodifiable set containing the property's
544       *         effective values. An empty set indicates that the
545       *         property has no default values defined and any default
546       *         behavior is applicable.
547       * @throws IllegalArgumentException
548       *           If the property definition is not associated with this
549       *           managed object's definition.
550       */
551      @SuppressWarnings("unchecked")
552      public <T> SortedSet<T> getPropertyValues(PropertyDefinition<T> d)
553          throws IllegalArgumentException {
554        if (!properties.containsKey(d)) {
555          throw new IllegalArgumentException("Unknown property " + d.getName());
556        }
557        return Collections.unmodifiableSortedSet((SortedSet<T>) properties.get(d));
558      }
559    
560    
561    
562      /**
563       * Determines whether or not the optional managed object associated
564       * with the specified optional relations exists.
565       *
566       * @param d
567       *          The optional relation definition.
568       * @return Returns <code>true</code> if the optional managed
569       *         object exists, <code>false</code> otherwise.
570       * @throws IllegalArgumentException
571       *           If the optional relation definition is not associated
572       *           with this managed object's definition.
573       */
574      public boolean hasChild(OptionalRelationDefinition<?, ?> d)
575          throws IllegalArgumentException {
576        validateRelationDefinition(d);
577        return context.managedObjectExists(path.child(d));
578      }
579    
580    
581    
582      /**
583       * Lists the child managed objects associated with the specified
584       * instantiable relation.
585       *
586       * @param d
587       *          The instantiable relation definition.
588       * @return Returns the names of the child managed objects.
589       * @throws IllegalArgumentException
590       *           If the relation definition is not associated with this
591       *           managed object's definition.
592       */
593      public String[] listChildren(InstantiableRelationDefinition<?, ?> d)
594          throws IllegalArgumentException {
595        validateRelationDefinition(d);
596        return context.listManagedObjects(path, d);
597      }
598    
599    
600    
601      /**
602       * Register to be notified when new child configurations are added
603       * beneath an instantiable relation.
604       *
605       * @param <M>
606       *          The type of the child server configuration object.
607       * @param d
608       *          The instantiable relation definition.
609       * @param listener
610       *          The configuration add listener.
611       * @throws IllegalArgumentException
612       *           If the instantiable relation definition is not
613       *           associated with this managed object's definition.
614       * @throws ConfigException
615       *           If the configuration entry associated with the
616       *           instantiable relation could not be retrieved.
617       */
618      public <M extends Configuration> void registerAddListener(
619          InstantiableRelationDefinition<?, M> d,
620          ConfigurationAddListener<M> listener) throws IllegalArgumentException,
621          ConfigException {
622        registerAddListener(d, new ServerManagedObjectAddListenerAdaptor<M>(
623            listener));
624      }
625    
626    
627    
628      /**
629       * Register to be notified when new child server managed object are
630       * added beneath an instantiable relation.
631       *
632       * @param <M>
633       *          The type of the child server configuration object.
634       * @param d
635       *          The instantiable relation definition.
636       * @param listener
637       *          The server managed object add listener.
638       * @throws IllegalArgumentException
639       *           If the instantiable relation definition is not
640       *           associated with this managed object's definition.
641       * @throws ConfigException
642       *           If the configuration entry associated with the
643       *           instantiable relation could not be retrieved.
644       */
645      public <M extends Configuration> void registerAddListener(
646          InstantiableRelationDefinition<?, M> d,
647          ServerManagedObjectAddListener<M> listener)
648          throws IllegalArgumentException, ConfigException {
649        validateRelationDefinition(d);
650        DN baseDN = DNBuilder.create(path, d);
651        ConfigAddListener adaptor = new ConfigAddListenerAdaptor<M>(path, d,
652            listener);
653        registerAddListener(baseDN, adaptor);
654      }
655    
656    
657    
658      /**
659       * Register to be notified when a new child configurations is added
660       * beneath an optional relation.
661       *
662       * @param <M>
663       *          The type of the child server configuration object.
664       * @param d
665       *          The optional relation definition.
666       * @param listener
667       *          The configuration add listener.
668       * @throws IllegalArgumentException
669       *           If the optional relation definition is not associated
670       *           with this managed object's definition.
671       * @throws ConfigException
672       *           If the configuration entry associated with the optional
673       *           relation could not be retrieved.
674       */
675      public <M extends Configuration> void registerAddListener(
676          OptionalRelationDefinition<?, M> d, ConfigurationAddListener<M> listener)
677          throws IllegalArgumentException, ConfigException {
678        registerAddListener(d, new ServerManagedObjectAddListenerAdaptor<M>(
679            listener));
680      }
681    
682    
683    
684      /**
685       * Register to be notified when a new child server managed object is
686       * added beneath an optional relation.
687       *
688       * @param <M>
689       *          The type of the child server configuration object.
690       * @param d
691       *          The optional relation definition.
692       * @param listener
693       *          The server managed object add listener.
694       * @throws IllegalArgumentException
695       *           If the optional relation definition is not associated
696       *           with this managed object's definition.
697       * @throws ConfigException
698       *           If the configuration entry associated with the optional
699       *           relation could not be retrieved.
700       */
701      public <M extends Configuration> void registerAddListener(
702          OptionalRelationDefinition<?, M> d,
703          ServerManagedObjectAddListener<M> listener)
704          throws IllegalArgumentException, ConfigException {
705        validateRelationDefinition(d);
706        DN baseDN = DNBuilder.create(path, d).getParent();
707        ConfigAddListener adaptor = new ConfigAddListenerAdaptor<M>(path, d,
708            listener);
709        registerAddListener(baseDN, adaptor);
710      }
711    
712    
713    
714      /**
715       * Register to be notified when this server managed object is
716       * changed.
717       *
718       * @param listener
719       *          The configuration change listener.
720       */
721      public void registerChangeListener(
722          ConfigurationChangeListener<? super S> listener) {
723        registerChangeListener(new ServerManagedObjectChangeListenerAdaptor<S>(
724            listener));
725      }
726    
727    
728    
729      /**
730       * Register to be notified when this server managed object is
731       * changed.
732       *
733       * @param listener
734       *          The server managed object change listener.
735       */
736      public void registerChangeListener(
737          ServerManagedObjectChangeListener<? super S> listener) {
738        ConfigChangeListener adaptor = new ConfigChangeListenerAdaptor<S>(path,
739            listener);
740        configEntry.registerChangeListener(adaptor);
741    
742        // Change listener registration usually signifies that a managed
743        // object has been accepted and added to the server configuration
744        // during initialization post-add.
745    
746        // FIXME: we should prevent multiple invocations in the case where
747        // multiple change listeners are registered for the same object.
748        for (Constraint constraint : definition.getAllConstraints()) {
749          for (ServerConstraintHandler handler : constraint
750              .getServerConstraintHandlers()) {
751            try {
752              handler.performPostAdd(this);
753            } catch (ConfigException e) {
754              if (debugEnabled()) {
755                TRACER.debugCaught(DebugLogLevel.ERROR, e);
756              }
757            }
758          }
759        }
760      }
761    
762    
763    
764      /**
765       * Register to be notified when existing child configurations are
766       * deleted beneath an instantiable relation.
767       *
768       * @param <M>
769       *          The type of the child server configuration object.
770       * @param d
771       *          The instantiable relation definition.
772       * @param listener
773       *          The configuration delete listener.
774       * @throws IllegalArgumentException
775       *           If the instantiable relation definition is not
776       *           associated with this managed object's definition.
777       * @throws ConfigException
778       *           If the configuration entry associated with the
779       *           instantiable relation could not be retrieved.
780       */
781      public <M extends Configuration> void registerDeleteListener(
782          InstantiableRelationDefinition<?, M> d,
783          ConfigurationDeleteListener<M> listener) throws IllegalArgumentException,
784          ConfigException {
785        registerDeleteListener(d, new ServerManagedObjectDeleteListenerAdaptor<M>(
786            listener));
787      }
788    
789    
790    
791      /**
792       * Register to be notified when existing child server managed
793       * objects are deleted beneath an instantiable relation.
794       *
795       * @param <M>
796       *          The type of the child server configuration object.
797       * @param d
798       *          The instantiable relation definition.
799       * @param listener
800       *          The server managed objects delete listener.
801       * @throws IllegalArgumentException
802       *           If the instantiable relation definition is not
803       *           associated with this managed object's definition.
804       * @throws ConfigException
805       *           If the configuration entry associated with the
806       *           instantiable relation could not be retrieved.
807       */
808      public <M extends Configuration> void registerDeleteListener(
809          InstantiableRelationDefinition<?, M> d,
810          ServerManagedObjectDeleteListener<M> listener)
811          throws IllegalArgumentException, ConfigException {
812        validateRelationDefinition(d);
813        DN baseDN = DNBuilder.create(path, d);
814        ConfigDeleteListener adaptor = new ConfigDeleteListenerAdaptor<M>(path, d,
815            listener);
816        registerDeleteListener(baseDN, adaptor);
817      }
818    
819    
820    
821      /**
822       * Register to be notified when an existing child configuration is
823       * deleted beneath an optional relation.
824       *
825       * @param <M>
826       *          The type of the child server configuration object.
827       * @param d
828       *          The optional relation definition.
829       * @param listener
830       *          The configuration delete listener.
831       * @throws IllegalArgumentException
832       *           If the optional relation definition is not associated
833       *           with this managed object's definition.
834       * @throws ConfigException
835       *           If the configuration entry associated with the optional
836       *           relation could not be retrieved.
837       */
838      public <M extends Configuration> void registerDeleteListener(
839          OptionalRelationDefinition<?, M> d,
840          ConfigurationDeleteListener<M> listener) throws IllegalArgumentException,
841          ConfigException {
842        registerDeleteListener(d, new ServerManagedObjectDeleteListenerAdaptor<M>(
843            listener));
844      }
845    
846    
847    
848      /**
849       * Register to be notified when an existing child server managed
850       * object is deleted beneath an optional relation.
851       *
852       * @param <M>
853       *          The type of the child server configuration object.
854       * @param d
855       *          The optional relation definition.
856       * @param listener
857       *          The server managed object delete listener.
858       * @throws IllegalArgumentException
859       *           If the optional relation definition is not associated
860       *           with this managed object's definition.
861       * @throws ConfigException
862       *           If the configuration entry associated with the optional
863       *           relation could not be retrieved.
864       */
865      public <M extends Configuration> void registerDeleteListener(
866          OptionalRelationDefinition<?, M> d,
867          ServerManagedObjectDeleteListener<M> listener)
868          throws IllegalArgumentException, ConfigException {
869        validateRelationDefinition(d);
870        DN baseDN = DNBuilder.create(path, d).getParent();
871        ConfigDeleteListener adaptor = new ConfigDeleteListenerAdaptor<M>(path, d,
872            listener);
873        registerDeleteListener(baseDN, adaptor);
874      }
875    
876    
877    
878      /**
879       * {@inheritDoc}
880       */
881      @Override
882      public String toString() {
883        StringBuilder builder = new StringBuilder();
884    
885        builder.append("{ TYPE=");
886        builder.append(definition.getName());
887        builder.append(", DN=\"");
888        builder.append(getDN());
889        builder.append('\"');
890        for (Map.Entry<PropertyDefinition<?>, SortedSet<?>> value : properties
891            .entrySet()) {
892          builder.append(", ");
893          builder.append(value.getKey().getName());
894          builder.append('=');
895          builder.append(value.getValue());
896        }
897        builder.append(" }");
898    
899        return builder.toString();
900      }
901    
902    
903    
904      /**
905       * Determines whether or not this managed object can be used by the
906       * server.
907       *
908       * @throws ConstraintViolationException
909       *           If one or more constraints determined that this managed
910       *           object cannot be used by the server.
911       */
912      void ensureIsUsable() throws ConstraintViolationException {
913        // Enforce any constraints.
914        boolean isUsable = true;
915        List<Message> reasons = new LinkedList<Message>();
916        for (Constraint constraint : definition.getAllConstraints()) {
917          for (ServerConstraintHandler handler : constraint
918              .getServerConstraintHandlers()) {
919            try {
920              if (!handler.isUsable(this, reasons)) {
921                isUsable = false;
922              }
923            } catch (ConfigException e) {
924              Message message = ERR_SERVER_CONSTRAINT_EXCEPTION.get(e
925                  .getMessageObject());
926              reasons.add(message);
927              isUsable = false;
928            }
929          }
930        }
931    
932        if (!isUsable) {
933          throw new ConstraintViolationException(this, reasons);
934        }
935      }
936    
937    
938    
939      /**
940       * Update the config entry associated with this server managed
941       * object. This is only intended to be used by change listener call
942       * backs in order to update the managed object with the correct
943       * config entry.
944       *
945       * @param configEntry
946       *          The configuration entry.
947       */
948      void setConfigEntry(ConfigEntry configEntry) {
949        this.configEntry = configEntry;
950      }
951    
952    
953    
954      // Deregister an add listener.
955      private <M extends Configuration> void deregisterAddListener(DN baseDN,
956          ConfigurationAddListener<M> listener) {
957        try {
958          ConfigEntry configEntry = getListenerConfigEntry(baseDN);
959          if (configEntry != null) {
960            for (ConfigAddListener l : configEntry.getAddListeners()) {
961              if (l instanceof ConfigAddListenerAdaptor) {
962                ConfigAddListenerAdaptor<?> adaptor =
963                  (ConfigAddListenerAdaptor<?>) l;
964                ServerManagedObjectAddListener<?> l2 = adaptor
965                    .getServerManagedObjectAddListener();
966                if (l2 instanceof ServerManagedObjectAddListenerAdaptor<?>) {
967                  ServerManagedObjectAddListenerAdaptor<?> adaptor2 =
968                    (ServerManagedObjectAddListenerAdaptor<?>) l2;
969                  if (adaptor2.getConfigurationAddListener() == listener) {
970                    configEntry.deregisterAddListener(adaptor);
971                  }
972                }
973              }
974            }
975          }
976        } catch (ConfigException e) {
977          // Ignore the exception since this implies deregistration.
978          if (debugEnabled()) {
979            TRACER.debugCaught(DebugLogLevel.ERROR, e);
980          }
981        }
982      }
983    
984    
985    
986      // Deregister an add listener.
987      private <M extends Configuration> void deregisterAddListener(DN baseDN,
988          ServerManagedObjectAddListener<M> listener) {
989        try {
990          ConfigEntry configEntry = getListenerConfigEntry(baseDN);
991          if (configEntry != null) {
992            for (ConfigAddListener l : configEntry.getAddListeners()) {
993              if (l instanceof ConfigAddListenerAdaptor) {
994                ConfigAddListenerAdaptor<?> adaptor =
995                  (ConfigAddListenerAdaptor<?>) l;
996                if (adaptor.getServerManagedObjectAddListener() == listener) {
997                  configEntry.deregisterAddListener(adaptor);
998                }
999              }
1000            }
1001          }
1002        } catch (ConfigException e) {
1003          // Ignore the exception since this implies deregistration.
1004          if (debugEnabled()) {
1005            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1006          }
1007        }
1008      }
1009    
1010    
1011    
1012      // Deregister a delete listener.
1013      private <M extends Configuration> void deregisterDeleteListener(DN baseDN,
1014          ConfigurationDeleteListener<M> listener) {
1015        try {
1016          ConfigEntry configEntry = getListenerConfigEntry(baseDN);
1017          if (configEntry != null) {
1018            for (ConfigDeleteListener l : configEntry.getDeleteListeners()) {
1019              if (l instanceof ConfigDeleteListenerAdaptor) {
1020                ConfigDeleteListenerAdaptor<?> adaptor =
1021                  (ConfigDeleteListenerAdaptor<?>) l;
1022                ServerManagedObjectDeleteListener<?> l2 = adaptor
1023                    .getServerManagedObjectDeleteListener();
1024                if (l2 instanceof ServerManagedObjectDeleteListenerAdaptor<?>) {
1025                  ServerManagedObjectDeleteListenerAdaptor<?> adaptor2 =
1026                    (ServerManagedObjectDeleteListenerAdaptor<?>) l2;
1027                  if (adaptor2.getConfigurationDeleteListener() == listener) {
1028                    configEntry.deregisterDeleteListener(adaptor);
1029                  }
1030                }
1031              }
1032            }
1033          }
1034        } catch (ConfigException e) {
1035          // Ignore the exception since this implies deregistration.
1036          if (debugEnabled()) {
1037            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1038          }
1039        }
1040      }
1041    
1042    
1043    
1044      // Deregister a delete listener.
1045      private <M extends Configuration> void deregisterDeleteListener(DN baseDN,
1046          ServerManagedObjectDeleteListener<M> listener) {
1047        try {
1048          ConfigEntry configEntry = getListenerConfigEntry(baseDN);
1049          if (configEntry != null) {
1050            for (ConfigDeleteListener l : configEntry.getDeleteListeners()) {
1051              if (l instanceof ConfigDeleteListenerAdaptor) {
1052                ConfigDeleteListenerAdaptor<?> adaptor =
1053                  (ConfigDeleteListenerAdaptor<?>) l;
1054                if (adaptor.getServerManagedObjectDeleteListener() == listener) {
1055                  configEntry.deregisterDeleteListener(adaptor);
1056                }
1057              }
1058            }
1059          }
1060        } catch (ConfigException e) {
1061          // Ignore the exception since this implies deregistration.
1062          if (debugEnabled()) {
1063            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1064          }
1065        }
1066      }
1067    
1068    
1069    
1070      // Gets a config entry required for a listener and throws a config
1071      // exception on failure or returns null if the entry does not exist.
1072      private ConfigEntry getListenerConfigEntry(DN dn) throws ConfigException {
1073        // Attempt to retrieve the listener base entry.
1074        ConfigEntry configEntry;
1075        try {
1076          configEntry = DirectoryServer.getConfigEntry(dn);
1077        } catch (ConfigException e) {
1078          if (debugEnabled()) {
1079            TRACER.debugCaught(DebugLogLevel.ERROR, e);
1080          }
1081    
1082          Message message = AdminMessages.ERR_ADMIN_CANNOT_GET_LISTENER_BASE.get(
1083              String.valueOf(dn), stackTraceToSingleLineString(e));
1084          throw new ConfigException(message, e);
1085        }
1086    
1087        return configEntry;
1088      }
1089    
1090    
1091    
1092      // Register an instantiable or optional relation add listener.
1093      private void registerAddListener(DN baseDN, ConfigAddListener adaptor)
1094          throws IllegalArgumentException, ConfigException {
1095        ConfigEntry relationEntry = getListenerConfigEntry(baseDN);
1096    
1097        if (relationEntry != null) {
1098          relationEntry.registerAddListener(adaptor);
1099        } else {
1100          // The relation entry does not exist yet so register a delayed
1101          // add listener.
1102          ConfigAddListener delayedListener = new DelayedConfigAddListener(baseDN,
1103              adaptor);
1104          registerDelayedListener(baseDN, delayedListener);
1105        }
1106      }
1107    
1108    
1109    
1110      // Register a delayed listener with the nearest existing parent
1111      // entry to the provided base DN.
1112      private void registerDelayedListener(DN baseDN,
1113          ConfigAddListener delayedListener) throws ConfigException {
1114        DN parentDN = baseDN.getParent();
1115        while (parentDN != null) {
1116          ConfigEntry relationEntry = getListenerConfigEntry(parentDN);
1117          if (relationEntry == null) {
1118            delayedListener = new DelayedConfigAddListener(parentDN,
1119                delayedListener);
1120            parentDN = parentDN.getParent();
1121          } else {
1122            relationEntry.registerAddListener(delayedListener);
1123            return;
1124          }
1125        }
1126    
1127        // No parent entry could be found.
1128        Message message = AdminMessages.ERR_ADMIN_UNABLE_TO_REGISTER_LISTENER
1129            .get(String.valueOf(baseDN));
1130        throw new ConfigException(message);
1131      }
1132    
1133    
1134    
1135      // Register an instantiable or optional relation delete listener.
1136      private void registerDeleteListener(DN baseDN, ConfigDeleteListener adaptor)
1137          throws ConfigException {
1138        ConfigEntry relationEntry = getListenerConfigEntry(baseDN);
1139    
1140        if (relationEntry != null) {
1141          relationEntry.registerDeleteListener(adaptor);
1142        } else {
1143          // The relation entry does not exist yet so register a delayed
1144          // add listener.
1145          ConfigAddListener delayedListener = new DelayedConfigAddListener(baseDN,
1146              adaptor);
1147          registerDelayedListener(baseDN, delayedListener);
1148        }
1149      }
1150    
1151    
1152    
1153      // Validate that a relation definition belongs to this managed
1154      // object.
1155      private void validateRelationDefinition(RelationDefinition<?, ?> rd)
1156          throws IllegalArgumentException {
1157        RelationDefinition<?, ?> tmp = definition.getRelationDefinition(rd
1158            .getName());
1159        if (tmp != rd) {
1160          throw new IllegalArgumentException("The relation " + rd.getName()
1161              + " is not associated with a " + definition.getName());
1162        }
1163      }
1164    }