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