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