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