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 2007-2008 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.forgerock.opendj.config;
018
019import static com.forgerock.opendj.ldap.config.ConfigMessages.*;
020import static com.forgerock.opendj.util.StaticUtils.*;
021
022import org.forgerock.util.Reject;
023
024import java.util.Collection;
025import java.util.Collections;
026import java.util.EnumSet;
027import java.util.HashMap;
028import java.util.Iterator;
029import java.util.LinkedList;
030import java.util.List;
031import java.util.Locale;
032import java.util.Map;
033import java.util.MissingResourceException;
034import java.util.SortedSet;
035
036import org.slf4j.Logger;
037import org.slf4j.LoggerFactory;
038import org.forgerock.i18n.LocalizableMessage;
039import org.forgerock.i18n.slf4j.LocalizedLogger;
040import org.forgerock.opendj.server.config.meta.RootCfgDefn;
041import org.forgerock.opendj.config.client.ClientConstraintHandler;
042import org.forgerock.opendj.config.client.ManagedObject;
043import org.forgerock.opendj.config.client.ManagedObjectDecodingException;
044import org.forgerock.opendj.config.client.ManagementContext;
045import org.forgerock.opendj.config.conditions.Condition;
046import org.forgerock.opendj.config.conditions.Conditions;
047import org.forgerock.opendj.config.server.ConfigChangeResult;
048import org.forgerock.opendj.config.server.ConfigException;
049import org.forgerock.opendj.config.server.ConfigurationDeleteListener;
050import org.forgerock.opendj.config.server.ServerConstraintHandler;
051import org.forgerock.opendj.config.server.ServerManagedObject;
052import org.forgerock.opendj.config.server.ServerManagedObjectChangeListener;
053import org.forgerock.opendj.config.server.ServerManagementContext;
054import org.forgerock.opendj.ldap.DN;
055import org.forgerock.opendj.ldap.LdapException;
056
057/**
058 * Aggregation property definition.
059 * <p>
060 * An aggregation property names one or more managed objects which are required
061 * by the managed object associated with this property. An aggregation property
062 * definition takes care to perform referential integrity checks: referenced
063 * managed objects cannot be deleted. Nor can an aggregation reference
064 * non-existent managed objects. Referential integrity checks are <b>not</b>
065 * performed during value validation. Instead they are performed when changes to
066 * the managed object are committed.
067 * <p>
068 * An aggregation property definition can optionally identify two properties:
069 * <ul>
070 * <li>an <code>enabled</code> property in the aggregated managed object - the
071 * property must be a {@link BooleanPropertyDefinition} and indicate whether the
072 * aggregated managed object is enabled or not. If specified, the administration
073 * framework will prevent the aggregated managed object from being disabled
074 * while it is referenced
075 * <li>an <code>enabled</code> property in this property's managed object - the
076 * property must be a {@link BooleanPropertyDefinition} and indicate whether
077 * this property's managed object is enabled or not. If specified, and as long
078 * as there is an equivalent <code>enabled</code> property defined for the
079 * aggregated managed object, the <code>enabled</code> property in the
080 * aggregated managed object will only be checked when this property is true.
081 * </ul>
082 * In other words, these properties can be used to make sure that referenced
083 * managed objects are not disabled while they are referenced.
084 *
085 * @param <C>
086 *            The type of client managed object configuration that this
087 *            aggregation property definition refers to.
088 * @param <S>
089 *            The type of server managed object configuration that this
090 *            aggregation property definition refers to.
091 */
092public final class AggregationPropertyDefinition<C extends ConfigurationClient, S extends Configuration> extends
093    PropertyDefinition<String> {
094
095    /**
096     * An interface for incrementally constructing aggregation property
097     * definitions.
098     *
099     * @param <C>
100     *            The type of client managed object configuration that this
101     *            aggregation property definition refers to.
102     * @param <S>
103     *            The type of server managed object configuration that this
104     *            aggregation property definition refers to.
105     */
106    public static final class Builder<C extends ConfigurationClient, S extends Configuration> extends
107        AbstractBuilder<String, AggregationPropertyDefinition<C, S>> {
108
109        /**
110         * The string representation of the managed object path specifying
111         * the parent of the aggregated managed objects.
112         */
113        private String parentPathString;
114
115        /**
116         * The name of a relation in the parent managed object which
117         * contains the aggregated managed objects.
118         */
119        private String rdName;
120
121        /** The condition which is used to determine if a referenced managed object is enabled. */
122        private Condition targetIsEnabledCondition = Conditions.TRUE;
123
124        /**
125         * The condition which is used to determine whether
126         * referenced managed objects need to be enabled.
127         */
128        private Condition targetNeedsEnablingCondition = Conditions.TRUE;
129
130        /** Private constructor. */
131        private Builder(AbstractManagedObjectDefinition<?, ?> d, String propertyName) {
132            super(d, propertyName);
133        }
134
135        /**
136         * Sets the name of the managed object which is the parent of the
137         * aggregated managed objects.
138         * <p>
139         * This must be defined before the property definition can be built.
140         *
141         * @param pathString
142         *            The string representation of the managed object path
143         *            specifying the parent of the aggregated managed objects.
144         */
145        public final void setParentPath(String pathString) {
146            this.parentPathString = pathString;
147        }
148
149        /**
150         * Sets the relation in the parent managed object which contains the
151         * aggregated managed objects.
152         * <p>
153         * This must be defined before the property definition can be built.
154         *
155         * @param rdName
156         *            The name of a relation in the parent managed object which
157         *            contains the aggregated managed objects.
158         */
159        public final void setRelationDefinition(String rdName) {
160            this.rdName = rdName;
161        }
162
163        /**
164         * Sets the condition which is used to determine if a referenced managed
165         * object is enabled. By default referenced managed objects are assumed
166         * to always be enabled.
167         *
168         * @param condition
169         *            The condition which is used to determine if a referenced
170         *            managed object is enabled.
171         */
172        public final void setTargetIsEnabledCondition(Condition condition) {
173            this.targetIsEnabledCondition = condition;
174        }
175
176        /**
177         * Sets the condition which is used to determine whether
178         * referenced managed objects need to be enabled. By default referenced
179         * managed objects must always be enabled.
180         *
181         * @param condition
182         *            The condition which is used to determine whether
183         *            referenced managed objects need to be enabled.
184         */
185        public final void setTargetNeedsEnablingCondition(Condition condition) {
186            this.targetNeedsEnablingCondition = condition;
187        }
188
189        @Override
190        protected AggregationPropertyDefinition<C, S> buildInstance(AbstractManagedObjectDefinition<?, ?> d,
191            String propertyName, EnumSet<PropertyOption> options, AdministratorAction adminAction,
192            DefaultBehaviorProvider<String> defaultBehavior) {
193            // Make sure that the parent path has been defined.
194            if (parentPathString == null) {
195                throw new IllegalStateException("Parent path undefined");
196            }
197
198            // Make sure that the relation definition has been defined.
199            if (rdName == null) {
200                throw new IllegalStateException("Relation definition undefined");
201            }
202
203            return new AggregationPropertyDefinition<>(d, propertyName, options, adminAction, defaultBehavior,
204                parentPathString, rdName, targetNeedsEnablingCondition, targetIsEnabledCondition);
205        }
206
207    }
208
209    /** A change listener which prevents the named component from being disabled. */
210    private final class ReferentialIntegrityChangeListener implements ServerManagedObjectChangeListener<S> {
211
212        /**
213         * The error message which should be returned if an attempt is
214         * made to disable the referenced component.
215         */
216        private final LocalizableMessage message;
217
218        /** The path of the referenced component. */
219        private final ManagedObjectPath<C, S> path;
220
221        /** Creates a new referential integrity delete listener. */
222        private ReferentialIntegrityChangeListener(ManagedObjectPath<C, S> path, LocalizableMessage message) {
223            this.path = path;
224            this.message = message;
225        }
226
227        @Override
228        public ConfigChangeResult applyConfigurationChange(ServerManagedObject<? extends S> mo) {
229            try {
230                if (targetIsEnabledCondition.evaluate(mo)) {
231                    return new ConfigChangeResult();
232                }
233            } catch (ConfigException e) {
234                // This should not happen - ignore it and throw an exception
235                // anyway below.
236            }
237
238            // This should not happen - the previous call-back should have
239            // trapped this.
240            throw new IllegalStateException("Attempting to disable a referenced "
241                + relationDefinition.getChildDefinition().getUserFriendlyName());
242        }
243
244        @Override
245        public boolean isConfigurationChangeAcceptable(ServerManagedObject<? extends S> mo,
246            List<LocalizableMessage> unacceptableReasons) {
247            // Always prevent the referenced component from being
248            // disabled.
249            try {
250                if (!targetIsEnabledCondition.evaluate(mo)) {
251                    unacceptableReasons.add(message);
252                    return false;
253                } else {
254                    return true;
255                }
256            } catch (ConfigException e) {
257                // The condition could not be evaluated.
258                debugLogger.trace("Unable to perform post add", e);
259                LocalizableMessage message =
260                    ERR_REFINT_UNABLE_TO_EVALUATE_TARGET_CONDITION.get(mo.getManagedObjectDefinition()
261                        .getUserFriendlyName(), mo.getDN(), getExceptionMessage(e));
262                LocalizedLogger logger =
263                    LocalizedLogger.getLocalizedLogger(ERR_REFINT_UNABLE_TO_EVALUATE_TARGET_CONDITION.resourceName());
264                logger.error(message);
265                unacceptableReasons.add(message);
266                return false;
267            }
268        }
269
270        /** Gets the path associated with this listener. */
271        private ManagedObjectPath<C, S> getManagedObjectPath() {
272            return path;
273        }
274
275    }
276
277    /** A delete listener which prevents the named component from being deleted. */
278    private final class ReferentialIntegrityDeleteListener implements ConfigurationDeleteListener<S> {
279
280        /** The DN of the referenced configuration entry. */
281        private final DN dn;
282
283        /**
284         * The error message which should be returned if an attempt is
285         * made to delete the referenced component.
286         */
287        private final LocalizableMessage message;
288
289        /** Creates a new referential integrity delete listener. */
290        private ReferentialIntegrityDeleteListener(DN dn, LocalizableMessage message) {
291            this.dn = dn;
292            this.message = message;
293        }
294
295        @Override
296        public ConfigChangeResult applyConfigurationDelete(S configuration) {
297            // This should not happen - the
298            // isConfigurationDeleteAcceptable() call-back should have
299            // trapped this.
300            if (configuration.dn().equals(dn)) {
301                // This should not happen - the
302                // isConfigurationDeleteAcceptable() call-back should have
303                // trapped this.
304                throw new IllegalStateException("Attempting to delete a referenced "
305                    + relationDefinition.getChildDefinition().getUserFriendlyName());
306            } else {
307                return new ConfigChangeResult();
308            }
309        }
310
311        @Override
312        public boolean isConfigurationDeleteAcceptable(S configuration, List<LocalizableMessage> unacceptableReasons) {
313            if (configuration.dn().equals(dn)) {
314                // Always prevent deletion of the referenced component.
315                unacceptableReasons.add(message);
316                return false;
317            }
318            return true;
319        }
320
321    }
322
323    /** The server-side constraint handler implementation. */
324    private class ServerHandler extends ServerConstraintHandler {
325
326        @Override
327        public boolean isUsable(ServerManagedObject<?> managedObject,
328            Collection<LocalizableMessage> unacceptableReasons) throws ConfigException {
329            SortedSet<String> names = managedObject.getPropertyValues(AggregationPropertyDefinition.this);
330            ServerManagementContext context = managedObject.getServerContext();
331            LocalizableMessage thisUFN = managedObject.getManagedObjectDefinition().getUserFriendlyName();
332            String thisDN = managedObject.getDN().toString();
333            LocalizableMessage thatUFN = getRelationDefinition().getUserFriendlyName();
334
335            boolean isUsable = true;
336            boolean needsEnabling = targetNeedsEnablingCondition.evaluate(managedObject);
337            for (String name : names) {
338                ManagedObjectPath<C, S> path = getChildPath(name);
339                String thatDN = path.toDN().toString();
340
341                if (!context.managedObjectExists(path)) {
342                    LocalizableMessage msg =
343                        ERR_SERVER_REFINT_DANGLING_REFERENCE.get(name, getName(), thisUFN, thisDN, thatUFN, thatDN);
344                    unacceptableReasons.add(msg);
345                    isUsable = false;
346                } else if (needsEnabling) {
347                    // Check that the referenced component is enabled if
348                    // required.
349                    ServerManagedObject<? extends S> ref = context.getManagedObject(path);
350                    if (!targetIsEnabledCondition.evaluate(ref)) {
351                        LocalizableMessage msg =
352                            ERR_SERVER_REFINT_TARGET_DISABLED.get(name, getName(), thisUFN, thisDN, thatUFN, thatDN);
353                        unacceptableReasons.add(msg);
354                        isUsable = false;
355                    }
356                }
357            }
358
359            return isUsable;
360        }
361
362        @Override
363        public void performPostAdd(ServerManagedObject<?> managedObject) throws ConfigException {
364            // First make sure existing listeners associated with this
365            // managed object are removed. This is required in order to
366            // prevent multiple change listener registrations from
367            // occurring, for example if this call-back is invoked multiple
368            // times after the same add event.
369            performPostDelete(managedObject);
370
371            // Add change and delete listeners against all referenced
372            // components.
373            LocalizableMessage thisUFN = managedObject.getManagedObjectDefinition().getUserFriendlyName();
374            String thisDN = managedObject.getDN().toString();
375            LocalizableMessage thatUFN = getRelationDefinition().getUserFriendlyName();
376
377            // Referenced managed objects will only need a change listener
378            // if they have can be disabled.
379            boolean needsChangeListeners = targetNeedsEnablingCondition.evaluate(managedObject);
380
381            // Delete listeners need to be registered against the parent
382            // entry of the referenced components.
383            ServerManagementContext context = managedObject.getServerContext();
384            ManagedObjectPath<?, ?> parentPath = getParentPath();
385            ServerManagedObject<?> parent = context.getManagedObject(parentPath);
386
387            // Create entries in the listener tables.
388            List<ReferentialIntegrityDeleteListener> dlist = new LinkedList<>();
389            deleteListeners.put(managedObject.getDN(), dlist);
390
391            List<ReferentialIntegrityChangeListener> clist = new LinkedList<>();
392            changeListeners.put(managedObject.getDN(), clist);
393
394            for (String name : managedObject.getPropertyValues(AggregationPropertyDefinition.this)) {
395                ManagedObjectPath<C, S> path = getChildPath(name);
396                DN dn = path.toDN();
397                String thatDN = dn.toString();
398
399                // Register the delete listener.
400                LocalizableMessage msg =
401                    ERR_SERVER_REFINT_CANNOT_DELETE.get(thatUFN, thatDN, getName(), thisUFN, thisDN);
402                ReferentialIntegrityDeleteListener dl = new ReferentialIntegrityDeleteListener(dn, msg);
403                parent.registerDeleteListener(getRelationDefinition(), dl);
404                dlist.add(dl);
405
406                // Register the change listener if required.
407                if (needsChangeListeners) {
408                    ServerManagedObject<? extends S> ref = context.getManagedObject(path);
409                    msg = ERR_SERVER_REFINT_CANNOT_DISABLE.get(thatUFN, thatDN, getName(), thisUFN, thisDN);
410                    ReferentialIntegrityChangeListener cl = new ReferentialIntegrityChangeListener(path, msg);
411                    ref.registerChangeListener(cl);
412                    clist.add(cl);
413                }
414            }
415        }
416
417        @Override
418        public void performPostDelete(ServerManagedObject<?> managedObject) throws ConfigException {
419            // Remove any registered delete and change listeners.
420            ServerManagementContext context = managedObject.getServerContext();
421            DN dn = managedObject.getDN();
422
423            // Delete listeners need to be deregistered against the parent
424            // entry of the referenced components.
425            ManagedObjectPath<?, ?> parentPath = getParentPath();
426            ServerManagedObject<?> parent = context.getManagedObject(parentPath);
427            if (deleteListeners.containsKey(dn)) {
428                for (ReferentialIntegrityDeleteListener dl : deleteListeners.get(dn)) {
429                    parent.deregisterDeleteListener(getRelationDefinition(), dl);
430                }
431                deleteListeners.remove(dn);
432            }
433
434            // Change listeners need to be deregistered from their
435            // associated referenced component.
436            if (changeListeners.containsKey(dn)) {
437                for (ReferentialIntegrityChangeListener cl : changeListeners.get(dn)) {
438                    ManagedObjectPath<C, S> path = cl.getManagedObjectPath();
439                    ServerManagedObject<? extends S> ref = context.getManagedObject(path);
440                    ref.deregisterChangeListener(cl);
441                }
442                changeListeners.remove(dn);
443            }
444        }
445
446        @Override
447        public void performPostModify(ServerManagedObject<?> managedObject) throws ConfigException {
448            // Remove all the constraints associated with this managed
449            // object and then re-register them.
450            performPostDelete(managedObject);
451            performPostAdd(managedObject);
452        }
453    }
454
455    /**
456     * The client-side constraint handler implementation which enforces
457     * referential integrity when aggregating managed objects are added or
458     * modified.
459     */
460    private class SourceClientHandler extends ClientConstraintHandler {
461
462        @Override
463        public boolean isAddAcceptable(ManagementContext context, ManagedObject<?> managedObject,
464            Collection<LocalizableMessage> unacceptableReasons) throws LdapException {
465            // If all of this managed object's "enabled" properties are true
466            // then any referenced managed objects must also be enabled.
467            boolean needsEnabling = targetNeedsEnablingCondition.evaluate(context, managedObject);
468
469            // Check the referenced managed objects exist and, if required,
470            // are enabled.
471            boolean isAcceptable = true;
472            LocalizableMessage ufn = getRelationDefinition().getUserFriendlyName();
473            for (String name : managedObject.getPropertyValues(AggregationPropertyDefinition.this)) {
474                // Retrieve the referenced managed object and make sure it
475                // exists.
476                ManagedObjectPath<?, ?> path = getChildPath(name);
477                ManagedObject<?> ref;
478                try {
479                    ref = context.getManagedObject(path);
480                } catch (DefinitionDecodingException | ManagedObjectDecodingException e) {
481                    LocalizableMessage msg =
482                        ERR_CLIENT_REFINT_TARGET_INVALID.get(ufn, name, getName(), e.getMessageObject());
483                    unacceptableReasons.add(msg);
484                    isAcceptable = false;
485                    continue;
486                } catch (ManagedObjectNotFoundException e) {
487                    LocalizableMessage msg = ERR_CLIENT_REFINT_TARGET_DANGLING_REFERENCE.get(ufn, name, getName());
488                    unacceptableReasons.add(msg);
489                    isAcceptable = false;
490                    continue;
491                }
492
493                // Make sure the reference managed object is enabled.
494                if (needsEnabling
495                        && !targetIsEnabledCondition.evaluate(context, ref)) {
496                    LocalizableMessage msg = ERR_CLIENT_REFINT_TARGET_DISABLED.get(ufn, name, getName());
497                    unacceptableReasons.add(msg);
498                    isAcceptable = false;
499                }
500            }
501            return isAcceptable;
502        }
503
504        @Override
505        public boolean isModifyAcceptable(ManagementContext context, ManagedObject<?> managedObject,
506            Collection<LocalizableMessage> unacceptableReasons) throws LdapException {
507            // The same constraint applies as for adds.
508            return isAddAcceptable(context, managedObject, unacceptableReasons);
509        }
510
511    }
512
513    /**
514     * The client-side constraint handler implementation which enforces
515     * referential integrity when aggregated managed objects are deleted or
516     * modified.
517     */
518    private class TargetClientHandler extends ClientConstraintHandler {
519
520        @Override
521        public boolean isDeleteAcceptable(ManagementContext context, ManagedObjectPath<?, ?> path,
522            Collection<LocalizableMessage> unacceptableReasons) throws LdapException {
523            // Any references to the deleted managed object should cause a
524            // constraint violation.
525            boolean isAcceptable = true;
526            for (ManagedObject<?> mo : findReferences(context, getManagedObjectDefinition(), path.getName())) {
527                final LocalizableMessage uName1 = mo.getManagedObjectDefinition().getUserFriendlyName();
528                final LocalizableMessage uName2 = getManagedObjectDefinition().getUserFriendlyName();
529                final String moName = mo.getManagedObjectPath().getName();
530
531                final LocalizableMessage msg = moName != null
532                    ? ERR_CLIENT_REFINT_CANNOT_DELETE_WITH_NAME.get(getName(), uName1, moName, uName2)
533                    : ERR_CLIENT_REFINT_CANNOT_DELETE_WITHOUT_NAME.get(getName(), uName1, uName2);
534                unacceptableReasons.add(msg);
535                isAcceptable = false;
536            }
537            return isAcceptable;
538        }
539
540        @Override
541        public boolean isModifyAcceptable(ManagementContext context, ManagedObject<?> managedObject,
542            Collection<LocalizableMessage> unacceptableReasons) throws LdapException {
543            // If the modified managed object is disabled and there are some
544            // active references then refuse the change.
545            if (targetIsEnabledCondition.evaluate(context, managedObject)) {
546                return true;
547            }
548
549            // The referenced managed object is disabled. Need to check for
550            // active references.
551            boolean isAcceptable = true;
552            for (ManagedObject<?> mo : findReferences(context, getManagedObjectDefinition(), managedObject
553                .getManagedObjectPath().getName())) {
554                if (targetNeedsEnablingCondition.evaluate(context, mo)) {
555                    final LocalizableMessage uName1 = managedObject.getManagedObjectDefinition().getUserFriendlyName();
556                    final LocalizableMessage uName2 = mo.getManagedObjectDefinition().getUserFriendlyName();
557                    final String moName = mo.getManagedObjectPath().getName();
558
559                    final LocalizableMessage msg = moName != null
560                        ? ERR_CLIENT_REFINT_CANNOT_DISABLE_WITH_NAME.get(uName1, getName(), uName2, moName)
561                        : ERR_CLIENT_REFINT_CANNOT_DISABLE_WITHOUT_NAME.get(uName1, getName(), uName2);
562                    unacceptableReasons.add(msg);
563                    isAcceptable = false;
564                }
565            }
566            return isAcceptable;
567        }
568
569        /** Find all managed objects which reference the named managed object using this property. */
570        private <C1 extends ConfigurationClient> List<ManagedObject<? extends C1>> findReferences(
571            ManagementContext context, AbstractManagedObjectDefinition<C1, ?> mod, String name)
572                throws LdapException {
573            List<ManagedObject<? extends C1>> instances = findInstances(context, mod);
574
575            Iterator<ManagedObject<? extends C1>> i = instances.iterator();
576            while (i.hasNext()) {
577                ManagedObject<? extends C1> mo = i.next();
578                boolean hasReference = false;
579
580                for (String value : mo.getPropertyValues(AggregationPropertyDefinition.this)) {
581                    if (compare(value, name) == 0) {
582                        hasReference = true;
583                        break;
584                    }
585                }
586
587                if (!hasReference) {
588                    i.remove();
589                }
590            }
591
592            return instances;
593        }
594
595        /** Find all instances of a specific type of managed object. */
596        @SuppressWarnings("unchecked")
597        private <C1 extends ConfigurationClient> List<ManagedObject<? extends C1>> findInstances(
598            ManagementContext context, AbstractManagedObjectDefinition<C1, ?> mod) throws LdapException {
599            List<ManagedObject<? extends C1>> instances = new LinkedList<>();
600
601            if (mod == RootCfgDefn.getInstance()) {
602                instances.add((ManagedObject<? extends C1>) context.getRootConfigurationManagedObject());
603            } else {
604                for (RelationDefinition<? super C1, ?> rd : mod.getAllReverseRelationDefinitions()) {
605                    for (ManagedObject<?> parent : findInstances(context, rd.getParentDefinition())) {
606                        try {
607                            if (rd instanceof SingletonRelationDefinition) {
608                                SingletonRelationDefinition<? super C1, ?> srd =
609                                    (SingletonRelationDefinition<? super C1, ?>) rd;
610                                ManagedObject<?> mo = parent.getChild(srd);
611                                if (mo.getManagedObjectDefinition().isChildOf(mod)) {
612                                    instances.add((ManagedObject<? extends C1>) mo);
613                                }
614                            } else if (rd instanceof OptionalRelationDefinition) {
615                                OptionalRelationDefinition<? super C1, ?> ord =
616                                    (OptionalRelationDefinition<? super C1, ?>) rd;
617                                ManagedObject<?> mo = parent.getChild(ord);
618                                if (mo.getManagedObjectDefinition().isChildOf(mod)) {
619                                    instances.add((ManagedObject<? extends C1>) mo);
620                                }
621                            } else if (rd instanceof InstantiableRelationDefinition) {
622                                InstantiableRelationDefinition<? super C1, ?> ird =
623                                    (InstantiableRelationDefinition<? super C1, ?>) rd;
624
625                                for (String name : parent.listChildren(ird)) {
626                                    ManagedObject<?> mo = parent.getChild(ird, name);
627                                    if (mo.getManagedObjectDefinition().isChildOf(mod)) {
628                                        instances.add((ManagedObject<? extends C1>) mo);
629                                    }
630                                }
631                            }
632                        } catch (OperationsException e) {
633                            // Ignore all operations exceptions.
634                        }
635                    }
636                }
637            }
638
639            return instances;
640        }
641    }
642
643    /**
644     * Creates an aggregation property definition builder.
645     *
646     * @param <C>
647     *            The type of client managed object configuration that this
648     *            aggregation property definition refers to.
649     * @param <S>
650     *            The type of server managed object configuration that this
651     *            aggregation property definition refers to.
652     * @param d
653     *            The managed object definition associated with this property
654     *            definition.
655     * @param propertyName
656     *            The property name.
657     * @return Returns the new aggregation property definition builder.
658     */
659    public static <C extends ConfigurationClient, S extends Configuration> Builder<C, S> createBuilder(
660        AbstractManagedObjectDefinition<?, ?> d, String propertyName) {
661        return new Builder<>(d, propertyName);
662    }
663
664    private static final Logger debugLogger = LoggerFactory.getLogger(AggregationPropertyDefinition.class);
665
666    /** The active server-side referential integrity change listeners associated with this property. */
667    private final Map<DN, List<ReferentialIntegrityChangeListener>> changeListeners = new HashMap<>();
668
669    /** The active server-side referential integrity delete listeners associated with this property. */
670    private final Map<DN, List<ReferentialIntegrityDeleteListener>> deleteListeners = new HashMap<>();
671
672    /** The name of the managed object which is the parent of the aggregated managed objects. */
673    private ManagedObjectPath<?, ?> parentPath;
674
675    /**
676     * The string representation of the managed object path specifying
677     * the parent of the aggregated managed objects.
678     */
679    private final String parentPathString;
680
681    /**
682     * The name of a relation in the parent managed object which
683     * contains the aggregated managed objects.
684     */
685    private final String rdName;
686
687    /** The relation in the parent managed object which contains the aggregated managed objects. */
688    private InstantiableRelationDefinition<C, S> relationDefinition;
689
690    /** The source constraint. */
691    private final Constraint sourceConstraint;
692
693    /** The condition which is used to determine if a referenced managed object is enabled. */
694    private final Condition targetIsEnabledCondition;
695
696    /**
697     * The condition which is used to determine whether
698     * referenced managed objects need to be enabled.
699     */
700    private final Condition targetNeedsEnablingCondition;
701
702    /** Private constructor. */
703    private AggregationPropertyDefinition(AbstractManagedObjectDefinition<?, ?> d, String propertyName,
704        EnumSet<PropertyOption> options, AdministratorAction adminAction,
705        DefaultBehaviorProvider<String> defaultBehavior, String parentPathString, String rdName,
706        Condition targetNeedsEnablingCondition, Condition targetIsEnabledCondition) {
707        super(d, String.class, propertyName, options, adminAction, defaultBehavior);
708
709        this.parentPathString = parentPathString;
710        this.rdName = rdName;
711        this.targetNeedsEnablingCondition = targetNeedsEnablingCondition;
712        this.targetIsEnabledCondition = targetIsEnabledCondition;
713        this.sourceConstraint = new Constraint() {
714
715            @Override
716            public Collection<ClientConstraintHandler> getClientConstraintHandlers() {
717                ClientConstraintHandler handler = new SourceClientHandler();
718                return Collections.singleton(handler);
719            }
720
721            @Override
722            public Collection<ServerConstraintHandler> getServerConstraintHandlers() {
723                ServerConstraintHandler handler = new ServerHandler();
724                return Collections.singleton(handler);
725            }
726        };
727    }
728
729    @Override
730    public <R, P> R accept(PropertyDefinitionVisitor<R, P> v, P p) {
731        return v.visitAggregation(this, p);
732    }
733
734    @Override
735    public <R, P> R accept(PropertyValueVisitor<R, P> v, String value, P p) {
736        return v.visitAggregation(this, value, p);
737    }
738
739    @Override
740    public String decodeValue(String value) {
741        Reject.ifNull(value);
742
743        try {
744            validateValue(value);
745            return value;
746        } catch (PropertyException e) {
747            throw PropertyException.illegalPropertyValueException(this, value);
748        }
749    }
750
751    /**
752     * Constructs a DN for a referenced managed object having the provided name.
753     * This method is implemented by first calling {@link #getChildPath(String)}
754     * and then invoking {@code ManagedObjectPath.toDN()} on the returned path.
755     *
756     * @param name
757     *            The name of the child managed object.
758     * @return Returns a DN for a referenced managed object having the provided
759     *         name.
760     */
761    public final DN getChildDN(String name) {
762        return getChildPath(name).toDN();
763    }
764
765    /**
766     * Constructs a managed object path for a referenced managed object having
767     * the provided name.
768     *
769     * @param name
770     *            The name of the child managed object.
771     * @return Returns a managed object path for a referenced managed object
772     *         having the provided name.
773     */
774    public final ManagedObjectPath<C, S> getChildPath(String name) {
775        return parentPath.child(relationDefinition, name);
776    }
777
778    /**
779     * Gets the name of the managed object which is the parent of the aggregated
780     * managed objects.
781     *
782     * @return Returns the name of the managed object which is the parent of the
783     *         aggregated managed objects.
784     */
785    public final ManagedObjectPath<?, ?> getParentPath() {
786        return parentPath;
787    }
788
789    /**
790     * Gets the relation in the parent managed object which contains the
791     * aggregated managed objects.
792     *
793     * @return Returns the relation in the parent managed object which contains
794     *         the aggregated managed objects.
795     */
796    public final InstantiableRelationDefinition<C, S> getRelationDefinition() {
797        return relationDefinition;
798    }
799
800    /**
801     * Gets the constraint which should be enforced on the aggregating managed
802     * object.
803     *
804     * @return Returns the constraint which should be enforced on the
805     *         aggregating managed object.
806     */
807    public final Constraint getSourceConstraint() {
808        return sourceConstraint;
809    }
810
811    /**
812     * Gets the optional constraint synopsis of this aggregation property
813     * definition in the default locale. The constraint synopsis describes when
814     * and how referenced managed objects must be enabled. When there are no
815     * constraints between the source managed object and the objects it
816     * references through this aggregation, <code>null</code> is returned.
817     *
818     * @return Returns the optional constraint synopsis of this aggregation
819     *         property definition in the default locale, or <code>null</code>
820     *         if there is no constraint synopsis.
821     */
822    public final LocalizableMessage getSourceConstraintSynopsis() {
823        return getSourceConstraintSynopsis(Locale.getDefault());
824    }
825
826    /**
827     * Gets the optional constraint synopsis of this aggregation property
828     * definition in the specified locale.The constraint synopsis describes when
829     * and how referenced managed objects must be enabled. When there are no
830     * constraints between the source managed object and the objects it
831     * references through this aggregation, <code>null</code> is returned.
832     *
833     * @param locale
834     *            The locale.
835     * @return Returns the optional constraint synopsis of this aggregation
836     *         property definition in the specified locale, or <code>null</code>
837     *         if there is no constraint synopsis.
838     */
839    public final LocalizableMessage getSourceConstraintSynopsis(Locale locale) {
840        ManagedObjectDefinitionI18NResource resource = ManagedObjectDefinitionI18NResource.getInstance();
841        String property = "property." + getName() + ".syntax.aggregation.constraint-synopsis";
842        try {
843            return resource.getMessage(getManagedObjectDefinition(), property, locale);
844        } catch (MissingResourceException e) {
845            return null;
846        }
847    }
848
849    /**
850     * Gets the condition which is used to determine if a referenced managed
851     * object is enabled.
852     *
853     * @return Returns the condition which is used to determine if a referenced
854     *         managed object is enabled.
855     */
856    public final Condition getTargetIsEnabledCondition() {
857        return targetIsEnabledCondition;
858    }
859
860    /**
861     * Gets the condition which is used to determine whether referenced
862     * managed objects need to be enabled.
863     *
864     * @return Returns the condition which is used to determine whether
865     *         referenced managed objects need to be enabled.
866     */
867    public final Condition getTargetNeedsEnablingCondition() {
868        return targetNeedsEnablingCondition;
869    }
870
871    @Override
872    public String normalizeValue(String value) {
873        try {
874            Reference<C, S> reference = Reference.parseName(parentPath, relationDefinition, value);
875            return reference.getNormalizedName();
876        } catch (IllegalArgumentException e) {
877            throw PropertyException.illegalPropertyValueException(this, value);
878        }
879    }
880
881    @Override
882    public void toString(StringBuilder builder) {
883        super.toString(builder);
884
885        builder.append(" parentPath=");
886        builder.append(parentPath);
887
888        builder.append(" relationDefinition=");
889        builder.append(relationDefinition.getName());
890
891        builder.append(" targetNeedsEnablingCondition=");
892        builder.append(targetNeedsEnablingCondition);
893
894        builder.append(" targetIsEnabledCondition=");
895        builder.append(targetIsEnabledCondition);
896    }
897
898    @Override
899    public void validateValue(String value) {
900        try {
901            Reference.parseName(parentPath, relationDefinition, value);
902        } catch (IllegalArgumentException e) {
903            throw PropertyException.illegalPropertyValueException(this, value);
904        }
905    }
906
907    /** {@inheritDoc} */
908    @SuppressWarnings("unchecked")
909    @Override
910    public void initialize() throws Exception {
911        // Decode the path.
912        parentPath = ManagedObjectPath.valueOf(parentPathString);
913
914        // Decode the relation definition.
915        AbstractManagedObjectDefinition<?, ?> parent = parentPath.getManagedObjectDefinition();
916        RelationDefinition<?, ?> rd = parent.getRelationDefinition(rdName);
917        relationDefinition = (InstantiableRelationDefinition<C, S>) rd;
918
919        // Now decode the conditions.
920        targetNeedsEnablingCondition.initialize(getManagedObjectDefinition());
921        targetIsEnabledCondition.initialize(rd.getChildDefinition());
922
923        // Register a client-side constraint with the referenced
924        // definition. This will be used to enforce referential integrity
925        // for actions performed against referenced managed objects.
926        Constraint constraint = new Constraint() {
927
928            @Override
929            public Collection<ClientConstraintHandler> getClientConstraintHandlers() {
930                ClientConstraintHandler handler = new TargetClientHandler();
931                return Collections.singleton(handler);
932            }
933
934            @Override
935            public Collection<ServerConstraintHandler> getServerConstraintHandlers() {
936                return Collections.emptyList();
937            }
938        };
939
940        rd.getChildDefinition().registerConstraint(constraint);
941    }
942
943}