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