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 }