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-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2015 ForgeRock AS.
026 */
027package org.forgerock.opendj.config.server;
028
029import static com.forgerock.opendj.ldap.AdminMessages.*;
030
031import java.util.Collections;
032import java.util.LinkedList;
033import java.util.List;
034import java.util.Map;
035import java.util.Set;
036import java.util.SortedSet;
037
038import org.forgerock.i18n.LocalizableMessage;
039import org.forgerock.opendj.config.Configuration;
040import org.forgerock.opendj.config.Constraint;
041import org.forgerock.opendj.config.InstantiableRelationDefinition;
042import org.forgerock.opendj.config.ManagedObjectDefinition;
043import org.forgerock.opendj.config.ManagedObjectPath;
044import org.forgerock.opendj.config.OptionalRelationDefinition;
045import org.forgerock.opendj.config.PropertyDefinition;
046import org.forgerock.opendj.config.PropertyProvider;
047import org.forgerock.opendj.config.RelationDefinition;
048import org.forgerock.opendj.config.SetRelationDefinition;
049import org.forgerock.opendj.config.SingletonRelationDefinition;
050import org.forgerock.opendj.config.server.spi.ConfigAddListener;
051import org.forgerock.opendj.config.server.spi.ConfigChangeListener;
052import org.forgerock.opendj.config.server.spi.ConfigDeleteListener;
053import org.forgerock.opendj.config.server.spi.ConfigurationRepository;
054import org.forgerock.opendj.ldap.DN;
055import org.forgerock.util.Pair;
056import org.slf4j.Logger;
057import org.slf4j.LoggerFactory;
058
059/**
060 * A server-side managed object.
061 *
062 * @param <S>
063 *            The type of server configuration represented by the server managed
064 *            object.
065 */
066public final class ServerManagedObject<S extends Configuration> implements PropertyProvider {
067
068    private static final Logger logger = LoggerFactory.getLogger(ServerManagedObject.class);
069
070    /**
071     * The DN of configuration entry associated with this server managed object,
072     * which is {@code null} for root.
073     */
074    private DN configDN;
075
076    private final ServerManagementContext serverContext;
077
078    private final ConfigurationRepository configRepository;
079
080    private final ManagedObjectDefinition<?, S> definition;
081
082    /** The managed object path identifying this managed object's location. */
083    private final ManagedObjectPath<?, S> path;
084
085    private final Map<PropertyDefinition<?>, SortedSet<?>> properties;
086
087    /**
088     * Creates an new server side managed object.
089     *
090     * @param path
091     *            The managed object path.
092     * @param definition
093     *            The managed object definition.
094     * @param properties
095     *            The managed object's properties.
096     * @param configDN
097     *            The configuration entry associated with the managed object.
098     * @param context
099     *            The server management context.
100     */
101    ServerManagedObject(final ManagedObjectPath<?, S> path, final ManagedObjectDefinition<?, S> definition,
102            final Map<PropertyDefinition<?>, SortedSet<?>> properties, final DN configDN,
103            final ServerManagementContext context) {
104        this.definition = definition;
105        this.path = path;
106        this.properties = properties;
107        this.configDN = configDN;
108        this.serverContext = context;
109        this.configRepository = context.getConfigRepository();
110    }
111
112    /**
113     * Deregisters an existing configuration add listener.
114     *
115     * @param <M>
116     *            The type of the child server configuration object.
117     * @param d
118     *            The instantiable relation definition.
119     * @param listener
120     *            The configuration add listener.
121     * @throws IllegalArgumentException
122     *             If the instantiable relation definition is not associated
123     *             with this managed object's definition.
124     */
125    public <M extends Configuration> void deregisterAddListener(InstantiableRelationDefinition<?, M> d,
126            ConfigurationAddListener<M> listener) {
127        validateRelationDefinition(d);
128        DN baseDN = DNBuilder.create(path, d);
129        deregisterAddListener(baseDN, listener);
130    }
131
132    /**
133     * Deregisters an existing server managed object add listener.
134     *
135     * @param <M>
136     *            The type of the child server configuration object.
137     * @param d
138     *            The instantiable relation definition.
139     * @param listener
140     *            The server managed object add listener.
141     * @throws IllegalArgumentException
142     *             If the instantiable relation definition is not associated
143     *             with this managed object's definition.
144     */
145    public <M extends Configuration> void deregisterAddListener(InstantiableRelationDefinition<?, M> d,
146            ServerManagedObjectAddListener<M> listener) {
147        validateRelationDefinition(d);
148        DN baseDN = DNBuilder.create(path, d);
149        deregisterAddListener(baseDN, listener);
150    }
151
152    /**
153     * Deregisters an existing configuration add listener.
154     *
155     * @param <M>
156     *            The type of the child server configuration object.
157     * @param d
158     *            The optional relation definition.
159     * @param listener
160     *            The configuration add listener.
161     * @throws IllegalArgumentException
162     *             If the optional relation definition is not associated with
163     *             this managed object's definition.
164     */
165    public <M extends Configuration> void deregisterAddListener(OptionalRelationDefinition<?, M> d,
166            ConfigurationAddListener<M> listener) {
167        validateRelationDefinition(d);
168        DN baseDN = DNBuilder.create(path, d).parent();
169        deregisterAddListener(baseDN, listener);
170    }
171
172    /**
173     * Deregisters an existing server managed object add listener.
174     *
175     * @param <M>
176     *            The type of the child server configuration object.
177     * @param d
178     *            The optional relation definition.
179     * @param listener
180     *            The server managed object add listener.
181     * @throws IllegalArgumentException
182     *             If the optional relation definition is not associated with
183     *             this managed object's definition.
184     */
185    public <M extends Configuration> void deregisterAddListener(OptionalRelationDefinition<?, M> d,
186            ServerManagedObjectAddListener<M> listener) {
187        validateRelationDefinition(d);
188        DN baseDN = DNBuilder.create(path, d).parent();
189        deregisterAddListener(baseDN, listener);
190    }
191
192    /**
193     * Deregisters an existing configuration add listener.
194     *
195     * @param <M>
196     *            The type of the child server configuration object.
197     * @param d
198     *            The set relation definition.
199     * @param listener
200     *            The configuration add listener.
201     * @throws IllegalArgumentException
202     *             If the set relation definition is not associated with this
203     *             managed object's definition.
204     */
205    public <M extends Configuration> void deregisterAddListener(SetRelationDefinition<?, M> d,
206            ConfigurationAddListener<M> listener) {
207        validateRelationDefinition(d);
208        DN baseDN = DNBuilder.create(path, d);
209        deregisterAddListener(baseDN, listener);
210    }
211
212    /**
213     * Deregisters an existing server managed object add listener.
214     *
215     * @param <M>
216     *            The type of the child server configuration object.
217     * @param d
218     *            The set relation definition.
219     * @param listener
220     *            The server managed object add listener.
221     * @throws IllegalArgumentException
222     *             If the set relation definition is not associated with this
223     *             managed object's definition.
224     */
225    public <M extends Configuration> void deregisterAddListener(SetRelationDefinition<?, M> d,
226            ServerManagedObjectAddListener<M> listener) {
227        validateRelationDefinition(d);
228        DN baseDN = DNBuilder.create(path, d);
229        deregisterAddListener(baseDN, listener);
230    }
231
232    /**
233     * Deregisters an existing configuration change listener.
234     *
235     * @param listener
236     *            The configuration change listener.
237     */
238    public void deregisterChangeListener(ConfigurationChangeListener<? super S> listener) {
239        for (ConfigChangeListener l : configRepository.getChangeListeners(configDN)) {
240            if (l instanceof ConfigChangeListenerAdaptor) {
241                ConfigChangeListenerAdaptor<?> adaptor = (ConfigChangeListenerAdaptor<?>) l;
242                ServerManagedObjectChangeListener<?> l2 = adaptor.getServerManagedObjectChangeListener();
243                if (l2 instanceof ServerManagedObjectChangeListenerAdaptor<?>) {
244                    ServerManagedObjectChangeListenerAdaptor<?> adaptor2 =
245                        (ServerManagedObjectChangeListenerAdaptor<?>) l2;
246                    if (adaptor2.getConfigurationChangeListener() == listener) {
247                        adaptor.finalizeChangeListener();
248                        configRepository.deregisterChangeListener(configDN, adaptor);
249                    }
250                }
251            }
252        }
253    }
254
255    /**
256     * Deregisters an existing server managed object change listener.
257     *
258     * @param listener
259     *            The server managed object change listener.
260     */
261    public void deregisterChangeListener(ServerManagedObjectChangeListener<? super S> listener) {
262        for (ConfigChangeListener l : configRepository.getChangeListeners(configDN)) {
263            if (l instanceof ConfigChangeListenerAdaptor) {
264                ConfigChangeListenerAdaptor<?> adaptor = (ConfigChangeListenerAdaptor<?>) l;
265                if (adaptor.getServerManagedObjectChangeListener() == listener) {
266                    adaptor.finalizeChangeListener();
267                    configRepository.deregisterChangeListener(configDN, adaptor);
268                }
269            }
270        }
271    }
272
273    /**
274     * Deregisters an existing configuration delete listener.
275     *
276     * @param <M>
277     *            The type of the child server configuration object.
278     * @param d
279     *            The instantiable relation definition.
280     * @param listener
281     *            The configuration delete listener.
282     * @throws IllegalArgumentException
283     *             If the instantiable relation definition is not associated
284     *             with this managed object's definition.
285     */
286    public <M extends Configuration> void deregisterDeleteListener(InstantiableRelationDefinition<?, M> d,
287            ConfigurationDeleteListener<M> listener) {
288        validateRelationDefinition(d);
289
290        DN baseDN = DNBuilder.create(path, d);
291        deregisterDeleteListener(baseDN, listener);
292    }
293
294    /**
295     * Deregisters an existing server managed object delete listener.
296     *
297     * @param <M>
298     *            The type of the child server configuration object.
299     * @param d
300     *            The instantiable relation definition.
301     * @param listener
302     *            The server managed object delete listener.
303     * @throws IllegalArgumentException
304     *             If the instantiable relation definition is not associated
305     *             with this managed object's definition.
306     */
307    public <M extends Configuration> void deregisterDeleteListener(InstantiableRelationDefinition<?, M> d,
308            ServerManagedObjectDeleteListener<M> listener) {
309        validateRelationDefinition(d);
310
311        DN baseDN = DNBuilder.create(path, d);
312        deregisterDeleteListener(baseDN, listener);
313    }
314
315    /**
316     * Deregisters an existing configuration delete listener.
317     *
318     * @param <M>
319     *            The type of the child server configuration object.
320     * @param d
321     *            The optional relation definition.
322     * @param listener
323     *            The configuration delete listener.
324     * @throws IllegalArgumentException
325     *             If the optional relation definition is not associated with
326     *             this managed object's definition.
327     */
328    public <M extends Configuration> void deregisterDeleteListener(OptionalRelationDefinition<?, M> d,
329            ConfigurationDeleteListener<M> listener) {
330        validateRelationDefinition(d);
331
332        DN baseDN = DNBuilder.create(path, d).parent();
333        deregisterDeleteListener(baseDN, listener);
334    }
335
336    /**
337     * Deregisters an existing server managed object delete listener.
338     *
339     * @param <M>
340     *            The type of the child server configuration object.
341     * @param d
342     *            The optional relation definition.
343     * @param listener
344     *            The server managed object delete listener.
345     * @throws IllegalArgumentException
346     *             If the optional relation definition is not associated with
347     *             this managed object's definition.
348     */
349    public <M extends Configuration> void deregisterDeleteListener(OptionalRelationDefinition<?, M> d,
350            ServerManagedObjectDeleteListener<M> listener) {
351        validateRelationDefinition(d);
352
353        DN baseDN = DNBuilder.create(path, d).parent();
354        deregisterDeleteListener(baseDN, listener);
355    }
356
357    /**
358     * Deregisters an existing configuration delete listener.
359     *
360     * @param <M>
361     *            The type of the child server configuration object.
362     * @param d
363     *            The set relation definition.
364     * @param listener
365     *            The configuration delete listener.
366     * @throws IllegalArgumentException
367     *             If the set relation definition is not associated with this
368     *             managed object's definition.
369     */
370    public <M extends Configuration> void deregisterDeleteListener(SetRelationDefinition<?, M> d,
371            ConfigurationDeleteListener<M> listener) {
372        validateRelationDefinition(d);
373
374        DN baseDN = DNBuilder.create(path, d);
375        deregisterDeleteListener(baseDN, listener);
376    }
377
378    /**
379     * Deregisters an existing server managed object delete listener.
380     *
381     * @param <M>
382     *            The type of the child server configuration object.
383     * @param d
384     *            The set relation definition.
385     * @param listener
386     *            The server managed object delete listener.
387     * @throws IllegalArgumentException
388     *             If the set relation definition is not associated with this
389     *             managed object's definition.
390     */
391    public <M extends Configuration> void deregisterDeleteListener(SetRelationDefinition<?, M> d,
392            ServerManagedObjectDeleteListener<M> listener) {
393        validateRelationDefinition(d);
394
395        DN baseDN = DNBuilder.create(path, d);
396        deregisterDeleteListener(baseDN, listener);
397    }
398
399    /**
400     * Retrieve an instantiable child managed object.
401     *
402     * @param <M>
403     *            The requested type of the child server managed object
404     *            configuration.
405     * @param d
406     *            The instantiable relation definition.
407     * @param name
408     *            The name of the child managed object.
409     * @return Returns the instantiable child managed object.
410     * @throws IllegalArgumentException
411     *             If the relation definition is not associated with this
412     *             managed object's definition.
413     * @throws ConfigException
414     *             If the child managed object could not be found or if it could
415     *             not be decoded.
416     */
417    public <M extends Configuration> ServerManagedObject<? extends M> getChild(InstantiableRelationDefinition<?, M> d,
418            String name) throws ConfigException {
419        validateRelationDefinition(d);
420        return serverContext.getManagedObject(path.child(d, name));
421    }
422
423    /**
424     * Retrieve an optional child managed object.
425     *
426     * @param <M>
427     *            The requested type of the child server managed object
428     *            configuration.
429     * @param d
430     *            The optional relation definition.
431     * @return Returns the optional child managed object.
432     * @throws IllegalArgumentException
433     *             If the optional relation definition is not associated with
434     *             this managed object's definition.
435     * @throws ConfigException
436     *             If the child managed object could not be found or if it could
437     *             not be decoded.
438     */
439    public <M extends Configuration> ServerManagedObject<? extends M> getChild(OptionalRelationDefinition<?, M> d)
440            throws ConfigException {
441        validateRelationDefinition(d);
442        return serverContext.getManagedObject(path.child(d));
443    }
444
445    /**
446     * Retrieve a set child managed object.
447     *
448     * @param <M>
449     *            The requested type of the child server managed object
450     *            configuration.
451     * @param d
452     *            The set relation definition.
453     * @param name
454     *            The name of the child managed object.
455     * @return Returns the set child managed object.
456     * @throws IllegalArgumentException
457     *             If the relation definition is not associated with this
458     *             managed object's definition or if {@code name} specifies a
459     *             managed object definition which is not a sub-type of the
460     *             relation's child definition.
461     * @throws ConfigException
462     *             If the child managed object could not be found or if it could
463     *             not be decoded.
464     */
465    public <M extends Configuration> ServerManagedObject<? extends M> getChild(SetRelationDefinition<?, M> d,
466            String name) throws ConfigException {
467        validateRelationDefinition(d);
468
469        return serverContext.getManagedObject(path.child(d, name));
470    }
471
472    /**
473     * Retrieve a singleton child managed object.
474     *
475     * @param <M>
476     *            The requested type of the child server managed object
477     *            configuration.
478     * @param d
479     *            The singleton relation definition.
480     * @return Returns the singleton child managed object.
481     * @throws IllegalArgumentException
482     *             If the relation definition is not associated with this
483     *             managed object's definition.
484     * @throws ConfigException
485     *             If the child managed object could not be found or if it could
486     *             not be decoded.
487     */
488    public <M extends Configuration> ServerManagedObject<? extends M> getChild(SingletonRelationDefinition<?, M> d)
489            throws ConfigException {
490        validateRelationDefinition(d);
491        return serverContext.getManagedObject(path.child(d));
492    }
493
494    /**
495     * Returns the server management context used by this object.
496     *
497     * @return the context
498     */
499    public ServerManagementContext getServerContext() {
500        return serverContext;
501    }
502
503    /**
504     * Creates a server configuration view of this managed object.
505     *
506     * @return Returns the server configuration view of this managed object.
507     */
508    public S getConfiguration() {
509        return definition.createServerConfiguration(this);
510    }
511
512    /**
513     * Get the DN of the LDAP entry associated with this server managed object.
514     *
515     * @return Returns the DN of the LDAP entry associated with this server
516     *         managed object, or an null DN if this is the root managed object.
517     */
518    public DN getDN() {
519        if (configDN != null) {
520            return configDN;
521        }
522        return DN.rootDN();
523    }
524
525    /**
526     * Get the definition associated with this server managed object.
527     *
528     * @return Returns the definition associated with this server managed
529     *         object.
530     */
531    public ManagedObjectDefinition<?, S> getManagedObjectDefinition() {
532        return definition;
533    }
534
535    /**
536     * Get the path of this server managed object.
537     *
538     * @return Returns the path of this server managed object.
539     */
540    public ManagedObjectPath<?, S> getManagedObjectPath() {
541        return path;
542    }
543
544    /**
545     * Get the effective value of the specified property. If the property is
546     * multi-valued then just the first value is returned. If the property does
547     * not have a value then its default value is returned if it has one, or
548     * <code>null</code> indicating that any default behavior is applicable.
549     *
550     * @param <T>
551     *            The type of the property to be retrieved.
552     * @param d
553     *            The property to be retrieved.
554     * @return Returns the property's effective value, or <code>null</code>
555     *         indicating that any default behavior is applicable.
556     * @throws IllegalArgumentException
557     *             If the property definition is not associated with this
558     *             managed object's definition.
559     */
560    public <T> T getPropertyValue(PropertyDefinition<T> d) {
561        Set<T> values = getPropertyValues(d);
562        if (!values.isEmpty()) {
563            return values.iterator().next();
564        }
565        return null;
566    }
567
568    /**
569     * Get the effective values of the specified property. If the property does
570     * not have any values then its default values are returned if it has any,
571     * or an empty set indicating that any default behavior is applicable.
572     *
573     * @param <T>
574     *            The type of the property to be retrieved.
575     * @param d
576     *            The property to be retrieved.
577     * @return Returns an unmodifiable set containing the property's effective
578     *         values. An empty set indicates that the property has no default
579     *         values defined and any default behavior is applicable.
580     * @throws IllegalArgumentException
581     *             If the property definition is not associated with this
582     *             managed object's definition.
583     */
584    @Override
585    @SuppressWarnings("unchecked")
586    public <T> SortedSet<T> getPropertyValues(PropertyDefinition<T> d) {
587        if (!properties.containsKey(d)) {
588            throw new IllegalArgumentException("Unknown property " + d.getName());
589        }
590        return Collections.unmodifiableSortedSet((SortedSet<T>) properties.get(d));
591    }
592
593    /**
594     * Determines whether or not the optional managed object associated with the
595     * specified optional relations exists.
596     *
597     * @param d
598     *            The optional relation definition.
599     * @return Returns <code>true</code> if the optional managed object exists,
600     *         <code>false</code> otherwise.
601     * @throws IllegalArgumentException
602     *             If the optional relation definition is not associated with
603     *             this managed object's definition.
604     */
605    public boolean hasChild(OptionalRelationDefinition<?, ?> d) {
606        validateRelationDefinition(d);
607        return serverContext.managedObjectExists(path.child(d));
608    }
609
610    /**
611     * Lists the child managed objects associated with the specified
612     * instantiable relation.
613     *
614     * @param d
615     *            The instantiable relation definition.
616     * @return Returns the names of the child managed objects.
617     * @throws IllegalArgumentException
618     *             If the relation definition is not associated with this
619     *             managed object's definition.
620     */
621    public String[] listChildren(InstantiableRelationDefinition<?, ?> d) {
622        validateRelationDefinition(d);
623        return serverContext.listManagedObjects(path, d);
624    }
625
626    /**
627     * Lists the child managed objects associated with the specified set
628     * relation.
629     *
630     * @param d
631     *            The set relation definition.
632     * @return Returns the names of the child managed objects.
633     * @throws IllegalArgumentException
634     *             If the relation definition is not associated with this
635     *             managed object's definition.
636     */
637    public String[] listChildren(SetRelationDefinition<?, ?> d) {
638        validateRelationDefinition(d);
639        return serverContext.listManagedObjects(path, d);
640    }
641
642    /**
643     * Register to be notified when new child configurations are added beneath
644     * an instantiable relation.
645     *
646     * @param <M>
647     *            The type of the child server configuration object.
648     * @param d
649     *            The instantiable relation definition.
650     * @param listener
651     *            The configuration add listener.
652     * @throws IllegalArgumentException
653     *             If the instantiable relation definition is not associated
654     *             with this managed object's definition.
655     * @throws ConfigException
656     *             If the configuration entry associated with the instantiable
657     *             relation could not be retrieved.
658     */
659    public <M extends Configuration> void registerAddListener(InstantiableRelationDefinition<?, M> d,
660            ConfigurationAddListener<M> listener) throws ConfigException {
661        registerAddListener(d, new ServerManagedObjectAddListenerAdaptor<M>(listener));
662    }
663
664    /**
665     * Register to be notified when new child server managed object are added
666     * beneath an instantiable relation.
667     *
668     * @param <M>
669     *            The type of the child server configuration object.
670     * @param d
671     *            The instantiable relation definition.
672     * @param listener
673     *            The server managed object add listener.
674     * @throws IllegalArgumentException
675     *             If the instantiable relation definition is not associated
676     *             with this managed object's definition.
677     * @throws ConfigException
678     *             If the configuration entry associated with the instantiable
679     *             relation could not be retrieved.
680     */
681    public <M extends Configuration> void registerAddListener(InstantiableRelationDefinition<?, M> d,
682            ServerManagedObjectAddListener<M> listener) throws ConfigException {
683        validateRelationDefinition(d);
684        DN baseDN = DNBuilder.create(path, d);
685        ConfigAddListener adaptor = new ConfigAddListenerAdaptor<>(serverContext, path, d, listener);
686        registerAddListener(baseDN, adaptor);
687    }
688
689    /**
690     * Register to be notified when a new child configurations is added beneath
691     * an optional relation.
692     *
693     * @param <M>
694     *            The type of the child server configuration object.
695     * @param d
696     *            The optional relation definition.
697     * @param listener
698     *            The configuration add listener.
699     * @throws IllegalArgumentException
700     *             If the optional relation definition is not associated with
701     *             this managed object's definition.
702     * @throws ConfigException
703     *             If the configuration entry associated with the optional
704     *             relation could not be retrieved.
705     */
706    public <M extends Configuration> void registerAddListener(OptionalRelationDefinition<?, M> d,
707            ConfigurationAddListener<M> listener) throws ConfigException {
708        registerAddListener(d, new ServerManagedObjectAddListenerAdaptor<M>(listener));
709    }
710
711    /**
712     * Register to be notified when a new child server managed object is added
713     * beneath an optional relation.
714     *
715     * @param <M>
716     *            The type of the child server configuration object.
717     * @param d
718     *            The optional relation definition.
719     * @param listener
720     *            The server managed object add listener.
721     * @throws IllegalArgumentException
722     *             If the optional relation definition is not associated with
723     *             this managed object's definition.
724     * @throws ConfigException
725     *             If the configuration entry associated with the optional
726     *             relation could not be retrieved.
727     */
728    public <M extends Configuration> void registerAddListener(OptionalRelationDefinition<?, M> d,
729            ServerManagedObjectAddListener<M> listener) throws ConfigException {
730        validateRelationDefinition(d);
731        DN baseDN = DNBuilder.create(path, d).parent();
732        ConfigAddListener adaptor = new ConfigAddListenerAdaptor<>(serverContext, path, d, listener);
733        registerAddListener(baseDN, adaptor);
734    }
735
736    /**
737     * Register to be notified when new child configurations are added beneath a
738     * set relation.
739     *
740     * @param <M>
741     *            The type of the child server configuration object.
742     * @param d
743     *            The set relation definition.
744     * @param listener
745     *            The configuration add listener.
746     * @throws IllegalArgumentException
747     *             If the set relation definition is not associated with this
748     *             managed object's definition.
749     * @throws ConfigException
750     *             If the configuration entry associated with the set relation
751     *             could not be retrieved.
752     */
753    public <M extends Configuration> void registerAddListener(SetRelationDefinition<?, M> d,
754            ConfigurationAddListener<M> listener) throws ConfigException {
755        registerAddListener(d, new ServerManagedObjectAddListenerAdaptor<M>(listener));
756    }
757
758    /**
759     * Register to be notified when new child server managed object are added
760     * beneath a set relation.
761     *
762     * @param <M>
763     *            The type of the child server configuration object.
764     * @param d
765     *            The set relation definition.
766     * @param listener
767     *            The server managed object add listener.
768     * @throws IllegalArgumentException
769     *             If the set relation definition is not associated with this
770     *             managed object's definition.
771     * @throws ConfigException
772     *             If the configuration entry associated with the set relation
773     *             could not be retrieved.
774     */
775    public <M extends Configuration> void registerAddListener(SetRelationDefinition<?, M> d,
776            ServerManagedObjectAddListener<M> listener) throws ConfigException {
777        validateRelationDefinition(d);
778        DN baseDN = DNBuilder.create(path, d);
779        ConfigAddListener adaptor = new ConfigAddListenerAdaptor<>(serverContext, path, d, listener);
780        registerAddListener(baseDN, adaptor);
781    }
782
783    /**
784     * Register to be notified when this server managed object is changed.
785     *
786     * @param listener
787     *            The configuration change listener.
788     */
789    public void registerChangeListener(ConfigurationChangeListener<? super S> listener) {
790        registerChangeListener(new ServerManagedObjectChangeListenerAdaptor<S>(listener));
791    }
792
793    /**
794     * Register to be notified when this server managed object is changed.
795     *
796     * @param listener
797     *            The server managed object change listener.
798     */
799    public void registerChangeListener(ServerManagedObjectChangeListener<? super S> listener) {
800        ConfigChangeListener adaptor = new ConfigChangeListenerAdaptor<>(serverContext, path, listener);
801        configRepository.registerChangeListener(configDN, adaptor);
802
803        // TODO : go toward this
804        // Entry entry;
805        // configBackend.registerChangeListener(entry.getName(), adapter));
806
807        // Change listener registration usually signifies that a managed
808        // object has been accepted and added to the server configuration
809        // during initialization post-add.
810
811        // FIXME: we should prevent multiple invocations in the case where
812        // multiple change listeners are registered for the same object.
813        for (Constraint constraint : definition.getAllConstraints()) {
814            for (ServerConstraintHandler handler : constraint.getServerConstraintHandlers()) {
815                try {
816                    handler.performPostAdd(this);
817                } catch (ConfigException e) {
818                    logger.trace("Unable to perform post add", e);
819                }
820            }
821        }
822    }
823
824    /**
825     * Register to be notified when existing child configurations are deleted
826     * beneath an instantiable relation.
827     *
828     * @param <M>
829     *            The type of the child server configuration object.
830     * @param d
831     *            The instantiable relation definition.
832     * @param listener
833     *            The configuration delete listener.
834     * @throws IllegalArgumentException
835     *             If the instantiable relation definition is not associated
836     *             with this managed object's definition.
837     * @throws ConfigException
838     *             If the configuration entry associated with the instantiable
839     *             relation could not be retrieved.
840     */
841    public <M extends Configuration> void registerDeleteListener(InstantiableRelationDefinition<?, M> d,
842            ConfigurationDeleteListener<M> listener) throws ConfigException {
843        registerDeleteListener(d, new ServerManagedObjectDeleteListenerAdaptor<M>(listener));
844    }
845
846    /**
847     * Register to be notified when existing child server managed objects are
848     * deleted beneath an instantiable relation.
849     *
850     * @param <M>
851     *            The type of the child server configuration object.
852     * @param d
853     *            The instantiable relation definition.
854     * @param listener
855     *            The server managed objects delete listener.
856     * @throws IllegalArgumentException
857     *             If the instantiable relation definition is not associated
858     *             with this managed object's definition.
859     * @throws ConfigException
860     *             If the configuration entry associated with the instantiable
861     *             relation could not be retrieved.
862     */
863    public <M extends Configuration> void registerDeleteListener(InstantiableRelationDefinition<?, M> d,
864            ServerManagedObjectDeleteListener<M> listener) throws ConfigException {
865        validateRelationDefinition(d);
866        DN baseDN = DNBuilder.create(path, d);
867        ConfigDeleteListener adaptor = new ConfigDeleteListenerAdaptor<>(serverContext, path, d, listener);
868        registerDeleteListener(baseDN, adaptor);
869    }
870
871    /**
872     * Register to be notified when an existing child configuration is deleted
873     * beneath an optional relation.
874     *
875     * @param <M>
876     *            The type of the child server configuration object.
877     * @param d
878     *            The optional relation definition.
879     * @param listener
880     *            The configuration delete listener.
881     * @throws IllegalArgumentException
882     *             If the optional relation definition is not associated with
883     *             this managed object's definition.
884     * @throws ConfigException
885     *             If the configuration entry associated with the optional
886     *             relation could not be retrieved.
887     */
888    public <M extends Configuration> void registerDeleteListener(OptionalRelationDefinition<?, M> d,
889            ConfigurationDeleteListener<M> listener) throws ConfigException {
890        registerDeleteListener(d, new ServerManagedObjectDeleteListenerAdaptor<M>(listener));
891    }
892
893    /**
894     * Register to be notified when an existing child server managed object is
895     * deleted beneath an optional relation.
896     *
897     * @param <M>
898     *            The type of the child server configuration object.
899     * @param d
900     *            The optional relation definition.
901     * @param listener
902     *            The server managed object delete listener.
903     * @throws IllegalArgumentException
904     *             If the optional relation definition is not associated with
905     *             this managed object's definition.
906     * @throws ConfigException
907     *             If the configuration entry associated with the optional
908     *             relation could not be retrieved.
909     */
910    public <M extends Configuration> void registerDeleteListener(OptionalRelationDefinition<?, M> d,
911            ServerManagedObjectDeleteListener<M> listener) throws ConfigException {
912        validateRelationDefinition(d);
913        DN baseDN = DNBuilder.create(path, d).parent();
914        ConfigDeleteListener adaptor = new ConfigDeleteListenerAdaptor<>(serverContext, path, d, listener);
915        registerDeleteListener(baseDN, adaptor);
916    }
917
918    /**
919     * Register to be notified when existing child configurations are deleted
920     * beneath a set relation.
921     *
922     * @param <M>
923     *            The type of the child server configuration object.
924     * @param d
925     *            The set relation definition.
926     * @param listener
927     *            The configuration delete listener.
928     * @throws IllegalArgumentException
929     *             If the set relation definition is not associated with this
930     *             managed object's definition.
931     * @throws ConfigException
932     *             If the configuration entry associated with the set relation
933     *             could not be retrieved.
934     */
935    public <M extends Configuration> void registerDeleteListener(SetRelationDefinition<?, M> d,
936            ConfigurationDeleteListener<M> listener) throws ConfigException {
937        registerDeleteListener(d, new ServerManagedObjectDeleteListenerAdaptor<M>(listener));
938    }
939
940    /**
941     * Register to be notified when existing child server managed objects are
942     * deleted beneath a set relation.
943     *
944     * @param <M>
945     *            The type of the child server configuration object.
946     * @param d
947     *            The set relation definition.
948     * @param listener
949     *            The server managed objects delete listener.
950     * @throws IllegalArgumentException
951     *             If the set relation definition is not associated with this
952     *             managed object's definition.
953     * @throws ConfigException
954     *             If the configuration entry associated with the set relation
955     *             could not be retrieved.
956     */
957    public <M extends Configuration> void registerDeleteListener(SetRelationDefinition<?, M> d,
958            ServerManagedObjectDeleteListener<M> listener) throws ConfigException {
959        validateRelationDefinition(d);
960        DN baseDN = DNBuilder.create(path, d);
961        ConfigDeleteListener adaptor = new ConfigDeleteListenerAdaptor<>(serverContext, path, d, listener);
962        registerDeleteListener(baseDN, adaptor);
963    }
964
965    /** {@inheritDoc} */
966    @Override
967    public String toString() {
968        StringBuilder builder = new StringBuilder();
969
970        builder.append("{ TYPE=");
971        builder.append(definition.getName());
972        builder.append(", DN=\"");
973        builder.append(getDN());
974        builder.append('\"');
975        for (Map.Entry<PropertyDefinition<?>, SortedSet<?>> value : properties.entrySet()) {
976            builder.append(", ");
977            builder.append(value.getKey().getName());
978            builder.append('=');
979            builder.append(value.getValue());
980        }
981        builder.append(" }");
982
983        return builder.toString();
984    }
985
986    /**
987     * Determines whether or not this managed object can be used by the server.
988     *
989     * @throws ConstraintViolationException
990     *             If one or more constraints determined that this managed
991     *             object cannot be used by the server.
992     */
993    void ensureIsUsable() throws ConstraintViolationException {
994        // Enforce any constraints.
995        boolean isUsable = true;
996        List<LocalizableMessage> reasons = new LinkedList<>();
997        for (Constraint constraint : definition.getAllConstraints()) {
998            for (ServerConstraintHandler handler : constraint.getServerConstraintHandlers()) {
999                try {
1000                    if (!handler.isUsable(this, reasons)) {
1001                        isUsable = false;
1002                    }
1003                } catch (ConfigException e) {
1004                    LocalizableMessage message = ERR_SERVER_CONSTRAINT_EXCEPTION.get(e.getMessageObject());
1005                    reasons.add(message);
1006                    isUsable = false;
1007                }
1008            }
1009        }
1010
1011        if (!isUsable) {
1012            throw new ConstraintViolationException(this, reasons);
1013        }
1014    }
1015
1016    /**
1017     * Update the config DN associated with this server managed object. This
1018     * is only intended to be used by change listener call backs in order to
1019     * update the managed object with the correct config DN.
1020     *
1021     * @param configDN
1022     *            The DN of the underlying configuration entry.
1023     */
1024    void setConfigDN(DN configDN) {
1025        this.configDN = configDN;
1026    }
1027
1028    /** Deregister an add listener. */
1029    private <M extends Configuration> void deregisterAddListener(DN baseDN, ConfigurationAddListener<M> listener) {
1030        try {
1031            if (configRepository.hasEntry(baseDN)) {
1032                for (ConfigAddListener configListener : configRepository.getAddListeners(baseDN)) {
1033                    if (configListener instanceof ConfigAddListenerAdaptor) {
1034                        ConfigAddListenerAdaptor<?> adaptor = (ConfigAddListenerAdaptor<?>) configListener;
1035                        ServerManagedObjectAddListener<?> smoListener = adaptor.getServerManagedObjectAddListener();
1036                        if (smoListener instanceof ServerManagedObjectAddListenerAdaptor<?>) {
1037                            ServerManagedObjectAddListenerAdaptor<?> adaptor2 =
1038                                (ServerManagedObjectAddListenerAdaptor<?>) smoListener;
1039                            if (adaptor2.getConfigurationAddListener() == listener) {
1040                                configRepository.deregisterAddListener(baseDN, adaptor);
1041                            }
1042                        }
1043                    }
1044                }
1045            } else {
1046                // The relation entry does not exist so check for and deregister
1047                // delayed add listener.
1048                deregisterDelayedAddListener(baseDN, listener);
1049            }
1050        } catch (ConfigException e) {
1051            // Ignore the exception since this implies deregistration.
1052            logger.trace("Unable to deregister add listener", e);
1053        }
1054    }
1055
1056    /** Deregister an add listener. */
1057    private <M extends Configuration> void deregisterAddListener(DN baseDN,
1058        ServerManagedObjectAddListener<M> listener) {
1059        try {
1060            if (configRepository.hasEntry(baseDN)) {
1061                for (ConfigAddListener configListener : configRepository.getAddListeners(baseDN)) {
1062                    if (configListener instanceof ConfigAddListenerAdaptor) {
1063                        ConfigAddListenerAdaptor<?> adaptor = (ConfigAddListenerAdaptor<?>) configListener;
1064                        if (adaptor.getServerManagedObjectAddListener() == listener) {
1065                            configRepository.deregisterAddListener(baseDN, adaptor);
1066                        }
1067                    }
1068                }
1069            } else {
1070                // The relation entry does not exist so check for and deregister
1071                // delayed add listener.
1072                deregisterDelayedAddListener(baseDN, listener);
1073            }
1074        } catch (ConfigException e) {
1075            // Ignore the exception since this implies deregistration.
1076            logger.trace("Unable to deregister add listener", e);
1077        }
1078    }
1079
1080    /**
1081     * Convenience method to retrieve the initial listener and its intermediate
1082     * adaptor from the provided configListener.
1083     *
1084     * @param <T>
1085     *            Type of the configuration.
1086     * @param configListener
1087     *            Listener from wich to extract the initial listener.
1088     * @return a pair of (intermediate adaptor, intermediate listener) or
1089     *         {@code Pair.EMPTY} if listener can't be extracted
1090     */
1091    // @Checkstyle:off
1092    static <T extends Configuration> Pair<ConfigAddListenerAdaptor<T>, ConfigurationAddListener<T>>
1093        extractInitialListener(ConfigAddListener configListener) {
1094        // @Checkstyle:on
1095        Pair<ConfigAddListenerAdaptor<T>, ServerManagedObjectAddListener<T>> pair =
1096                extractIntermediateListener(configListener);
1097        if (!pair.equals(Pair.EMPTY) && pair.getSecond() instanceof ServerManagedObjectAddListenerAdaptor) {
1098            ServerManagedObjectAddListenerAdaptor<T> adaptor2 = (ServerManagedObjectAddListenerAdaptor<T>)
1099                    pair.getSecond();
1100            return Pair.of(pair.getFirst(), adaptor2.getConfigurationAddListener());
1101        }
1102        return Pair.empty();
1103    }
1104
1105    /**
1106     * Convenience method to retrieve the intermediate listener and its
1107     * intermediate adaptor from the provided configListener.
1108     *
1109     * @param <T>
1110     *            Type of the configuration.
1111     * @param configListener
1112     *            Listener from wich to extract the initial listener.
1113     * @return a pair of (intermediate adaptor, initial listener) or
1114     *         {@code Pair.EMPTY} if listener can't be extracted
1115     */
1116    @SuppressWarnings("unchecked")
1117    // @Checkstyle:off
1118    static <T extends Configuration> Pair<ConfigAddListenerAdaptor<T>, ServerManagedObjectAddListener<T>>
1119        extractIntermediateListener(ConfigAddListener configListener) {
1120        // @Checkstyle:on
1121        if (configListener instanceof ConfigAddListenerAdaptor) {
1122            ConfigAddListenerAdaptor<T> adaptor = (ConfigAddListenerAdaptor<T>) configListener;
1123            return Pair.of(adaptor, adaptor.getServerManagedObjectAddListener());
1124        }
1125        return Pair.empty();
1126    }
1127
1128    /** Deregister a delete listener. */
1129    private <M extends Configuration> void deregisterDeleteListener(DN baseDN,
1130        ConfigurationDeleteListener<M> listener) {
1131        try {
1132            if (configRepository.hasEntry(baseDN)) {
1133                for (ConfigDeleteListener l : configRepository.getDeleteListeners(baseDN)) {
1134                    if (l instanceof ConfigDeleteListenerAdaptor) {
1135                        ConfigDeleteListenerAdaptor<?> adaptor = (ConfigDeleteListenerAdaptor<?>) l;
1136                        ServerManagedObjectDeleteListener<?> l2 = adaptor.getServerManagedObjectDeleteListener();
1137                        if (l2 instanceof ServerManagedObjectDeleteListenerAdaptor<?>) {
1138                            ServerManagedObjectDeleteListenerAdaptor<?> adaptor2 =
1139                                (ServerManagedObjectDeleteListenerAdaptor<?>) l2;
1140                            if (adaptor2.getConfigurationDeleteListener() == listener) {
1141                                configRepository.deregisterDeleteListener(baseDN, adaptor);
1142                            }
1143                        }
1144                    }
1145                }
1146            } else {
1147                // The relation entry does not exist so check for and deregister
1148                // delayed add listener.
1149                deregisterDelayedDeleteListener(baseDN, listener);
1150            }
1151        } catch (ConfigException e) {
1152            // Ignore the exception since this implies deregistration.
1153            logger.trace("Unable to deregister delete listener", e);
1154        }
1155    }
1156
1157    /** Deregister a delete listener. */
1158    private <M extends Configuration> void deregisterDeleteListener(DN baseDN,
1159            ServerManagedObjectDeleteListener<M> listener) {
1160        try {
1161            if (configRepository.hasEntry(baseDN)) {
1162                for (ConfigDeleteListener l : configRepository.getDeleteListeners(baseDN)) {
1163                    if (l instanceof ConfigDeleteListenerAdaptor) {
1164                        ConfigDeleteListenerAdaptor<?> adaptor = (ConfigDeleteListenerAdaptor<?>) l;
1165                        if (adaptor.getServerManagedObjectDeleteListener() == listener) {
1166                            configRepository.deregisterDeleteListener(baseDN, adaptor);
1167                        }
1168                    }
1169                }
1170            } else {
1171                // The relation entry does not exist so check for and deregister
1172                // delayed add listener.
1173                deregisterDelayedDeleteListener(baseDN, listener);
1174            }
1175        } catch (ConfigException e) {
1176            // Ignore the exception since this implies deregistration.
1177            logger.trace("Unable to deregister delete listener", e);
1178        }
1179    }
1180
1181    /** Register an instantiable or optional relation add listener. */
1182    private void registerAddListener(DN baseDN, ConfigAddListener adaptor) throws
1183        ConfigException {
1184        if (configRepository.hasEntry(baseDN)) {
1185            configRepository.registerAddListener(baseDN, adaptor);
1186        } else {
1187            // The relation entry does not exist yet
1188            // so register a delayed add listener.
1189            ConfigAddListener delayedListener = new DelayedConfigAddListener(baseDN, adaptor, configRepository);
1190            registerDelayedListener(baseDN, delayedListener);
1191        }
1192    }
1193
1194    /**
1195     * Register a delayed listener with the nearest existing parent
1196     * entry to the provided base DN.
1197     */
1198    private void registerDelayedListener(DN baseDN, ConfigAddListener delayedListener) throws ConfigException {
1199        DN currentDN = baseDN.parent();
1200        DN previousDN = currentDN;
1201        while (currentDN != null) {
1202            if (!configRepository.hasEntry(currentDN)) {
1203                delayedListener = new DelayedConfigAddListener(currentDN, delayedListener, configRepository);
1204                previousDN = currentDN;
1205                currentDN = currentDN.parent();
1206            } else {
1207                configRepository.registerAddListener(previousDN, delayedListener);
1208                return;
1209            }
1210        }
1211
1212        // No parent entry could be found.
1213        LocalizableMessage message = ERR_ADMIN_UNABLE_TO_REGISTER_LISTENER.get(String.valueOf(baseDN));
1214        throw new ConfigException(message);
1215    }
1216
1217    /**
1218     * Deregister a delayed listener with the nearest existing parent
1219     * entry to the provided base DN.
1220     */
1221    private <M extends Configuration> void deregisterDelayedAddListener(DN baseDN,
1222        ConfigurationAddListener<M> listener) throws ConfigException {
1223        DN parentDN = baseDN.parent();
1224        int delayWrappers = 0;
1225        while (parentDN != null) {
1226            if (!configRepository.hasEntry(parentDN)) {
1227                parentDN = parentDN.parent();
1228                delayWrappers++;
1229            } else {
1230                for (ConfigAddListener configListener : configRepository.getAddListeners(parentDN)) {
1231                    if (configListener instanceof DelayedConfigAddListener) {
1232                        DelayedConfigAddListener delayListener = (DelayedConfigAddListener) configListener;
1233                        ConfigAddListener wrappedListener;
1234
1235                        int i = delayWrappers;
1236                        for (; i > 0; i--) {
1237                            wrappedListener = delayListener.getDelayedAddListener();
1238                            if (wrappedListener instanceof DelayedConfigAddListener) {
1239                                delayListener = (DelayedConfigAddListener) configListener;
1240                            } else {
1241                                break;
1242                            }
1243                        }
1244
1245                        if (i > 0) {
1246                            // There are not enough level of wrapping
1247                            // so this can't be the listener we are looking for.
1248                            continue;
1249                        }
1250
1251                        ConfigAddListener delayedListener = delayListener.getDelayedAddListener();
1252
1253                        if (delayedListener instanceof ConfigAddListenerAdaptor) {
1254                            ConfigAddListenerAdaptor<?> adaptor = (ConfigAddListenerAdaptor<?>) delayedListener;
1255                            ServerManagedObjectAddListener<?> l2 = adaptor.getServerManagedObjectAddListener();
1256                            if (l2 instanceof ServerManagedObjectAddListenerAdaptor<?>) {
1257                                ServerManagedObjectAddListenerAdaptor<?> adaptor2 =
1258                                    (ServerManagedObjectAddListenerAdaptor<?>) l2;
1259                                if (adaptor2.getConfigurationAddListener() == listener) {
1260                                    configRepository.deregisterAddListener(parentDN, configListener);
1261                                }
1262                            }
1263                        }
1264                    }
1265                }
1266                return;
1267            }
1268        }
1269    }
1270
1271    /**
1272     * Deregister a delayed listener with the nearest existing parent
1273     * entry to the provided base DN.
1274     */
1275    private <M extends Configuration> void deregisterDelayedDeleteListener(DN baseDN,
1276            ConfigurationDeleteListener<M> listener) throws ConfigException {
1277        DN parentDN = baseDN.parent();
1278        int delayWrappers = 0;
1279        while (parentDN != null) {
1280            if (!configRepository.hasEntry(parentDN)) {
1281                parentDN = parentDN.parent();
1282                delayWrappers++;
1283            } else {
1284                for (ConfigAddListener l : configRepository.getAddListeners(parentDN)) {
1285                    if (l instanceof DelayedConfigAddListener) {
1286                        DelayedConfigAddListener delayListener = (DelayedConfigAddListener) l;
1287                        ConfigAddListener wrappedListener;
1288
1289                        int i = delayWrappers;
1290                        for (; i > 0; i--) {
1291                            wrappedListener = delayListener.getDelayedAddListener();
1292                            if (wrappedListener instanceof DelayedConfigAddListener) {
1293                                delayListener = (DelayedConfigAddListener) l;
1294                            } else {
1295                                break;
1296                            }
1297                        }
1298
1299                        if (i > 0) {
1300                            // There are not enough level of wrapping
1301                            // so this can't be the listener we are looking for.
1302                            continue;
1303                        }
1304
1305                        ConfigDeleteListener delayedListener = delayListener.getDelayedDeleteListener();
1306
1307                        if (delayedListener instanceof ConfigDeleteListenerAdaptor) {
1308                            ConfigDeleteListenerAdaptor<?> adaptor = (ConfigDeleteListenerAdaptor<?>) delayedListener;
1309                            ServerManagedObjectDeleteListener<?> l2 = adaptor.getServerManagedObjectDeleteListener();
1310                            if (l2 instanceof ServerManagedObjectDeleteListenerAdaptor<?>) {
1311                                ServerManagedObjectDeleteListenerAdaptor<?> adaptor2 =
1312                                    (ServerManagedObjectDeleteListenerAdaptor<?>) l2;
1313                                if (adaptor2.getConfigurationDeleteListener() == listener) {
1314                                    configRepository.deregisterAddListener(parentDN, l);
1315                                }
1316                            }
1317                        }
1318                    }
1319                }
1320                return;
1321            }
1322        }
1323    }
1324
1325    /**
1326     * Deregister a delayed listener with the nearest existing parent
1327     * entry to the provided base DN.
1328     */
1329    private <M extends Configuration> void deregisterDelayedAddListener(DN baseDN,
1330            ServerManagedObjectAddListener<M> listener) throws ConfigException {
1331        DN parentDN = baseDN.parent();
1332        int delayWrappers = 0;
1333        while (parentDN != null) {
1334            if (!configRepository.hasEntry(parentDN)) {
1335                parentDN = parentDN.parent();
1336                delayWrappers++;
1337            } else {
1338                for (ConfigAddListener configListener : configRepository.getAddListeners(parentDN)) {
1339                    if (configListener instanceof DelayedConfigAddListener) {
1340                        DelayedConfigAddListener delayListener = (DelayedConfigAddListener) configListener;
1341                        ConfigAddListener wrappedListener;
1342
1343                        int i = delayWrappers;
1344                        for (; i > 0; i--) {
1345                            wrappedListener = delayListener.getDelayedAddListener();
1346                            if (wrappedListener instanceof DelayedConfigAddListener) {
1347                                delayListener = (DelayedConfigAddListener) configListener;
1348                            } else {
1349                                break;
1350                            }
1351                        }
1352
1353                        if (i > 0) {
1354                            // There are not enough level of wrapping
1355                            // so this can't be the listener we are looking for.
1356                            continue;
1357                        }
1358
1359                        ConfigAddListener delayedListener = delayListener.getDelayedAddListener();
1360
1361                        if (delayedListener instanceof ConfigAddListenerAdaptor) {
1362                            ConfigAddListenerAdaptor<?> adaptor = (ConfigAddListenerAdaptor<?>) delayedListener;
1363                            if (adaptor.getServerManagedObjectAddListener() == listener) {
1364                                configRepository.deregisterAddListener(parentDN, configListener);
1365                            }
1366                        }
1367                    }
1368                }
1369                return;
1370            }
1371        }
1372    }
1373
1374    /**
1375     * Deregister a delayed listener with the nearest existing parent
1376     * entry to the provided base DN.
1377     */
1378    private <M extends Configuration> void deregisterDelayedDeleteListener(DN baseDN,
1379            ServerManagedObjectDeleteListener<M> listener) throws ConfigException {
1380        DN parentDN = baseDN.parent();
1381        int delayWrappers = 0;
1382        while (parentDN != null) {
1383            if (!configRepository.hasEntry(parentDN)) {
1384                parentDN = parentDN.parent();
1385                delayWrappers++;
1386            } else {
1387                for (ConfigAddListener configListener : configRepository.getAddListeners(parentDN)) {
1388                    if (configListener instanceof DelayedConfigAddListener) {
1389                        DelayedConfigAddListener delayListener = (DelayedConfigAddListener) configListener;
1390                        ConfigAddListener wrappedListener;
1391
1392                        int i = delayWrappers;
1393                        for (; i > 0; i--) {
1394                            wrappedListener = delayListener.getDelayedAddListener();
1395                            if (wrappedListener instanceof DelayedConfigAddListener) {
1396                                delayListener = (DelayedConfigAddListener) configListener;
1397                            } else {
1398                                break;
1399                            }
1400                        }
1401
1402                        if (i > 0) {
1403                            // There are not enough level of wrapping
1404                            // so this can't be the listener we are looking for.
1405                            continue;
1406                        }
1407
1408                        ConfigDeleteListener delayedListener = delayListener.getDelayedDeleteListener();
1409
1410                        if (delayedListener instanceof ConfigDeleteListenerAdaptor) {
1411                            ConfigDeleteListenerAdaptor<?> adaptor = (ConfigDeleteListenerAdaptor<?>) delayedListener;
1412                            if (adaptor.getServerManagedObjectDeleteListener() == listener) {
1413                                configRepository.deregisterAddListener(parentDN, configListener);
1414                            }
1415                        }
1416                    }
1417                }
1418                return;
1419            }
1420        }
1421    }
1422
1423    /** Register an instantiable or optional relation delete listener. */
1424    private void registerDeleteListener(DN baseDN, ConfigDeleteListener adaptor) throws ConfigException {
1425        if (configRepository.hasEntry(baseDN)) {
1426            configRepository.registerDeleteListener(baseDN, adaptor);
1427        } else {
1428            // The relation entry does not exist yet
1429            // so register a delayed add listener.
1430            ConfigAddListener delayedListener = new DelayedConfigAddListener(baseDN, adaptor, configRepository);
1431            registerDelayedListener(baseDN, delayedListener);
1432        }
1433    }
1434
1435    /** Validate that a relation definition belongs to this managed object. */
1436    private void validateRelationDefinition(RelationDefinition<?, ?> rd) {
1437        RelationDefinition<?, ?> tmp = definition.getRelationDefinition(rd.getName());
1438        if (tmp != rd) {
1439            throw new IllegalArgumentException("The relation " + rd.getName() + " is not associated with a "
1440                    + definition.getName());
1441        }
1442    }
1443}