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