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}