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