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