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