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 2008-2010 Sun Microsystems, Inc. 015 * Portions Copyright 2013-2016 ForgeRock AS. 016 */ 017package org.opends.guitools.controlpanel.task; 018 019import static org.opends.messages.AdminToolMessages.*; 020import static org.opends.server.types.ExistingFileBehavior.*; 021import static org.opends.server.util.SchemaUtils.*; 022 023import java.io.File; 024import java.io.IOException; 025import java.util.ArrayList; 026import java.util.Collection; 027import java.util.Collections; 028import java.util.HashMap; 029import java.util.HashSet; 030import java.util.LinkedHashSet; 031import java.util.List; 032import java.util.Map; 033import java.util.Set; 034 035import javax.naming.NamingException; 036import javax.naming.directory.BasicAttribute; 037import javax.naming.directory.DirContext; 038import javax.naming.directory.ModificationItem; 039import javax.swing.SwingUtilities; 040 041import org.forgerock.i18n.LocalizableMessage; 042import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1; 043import org.forgerock.opendj.ldap.ModificationType; 044import org.forgerock.opendj.ldap.schema.AttributeType; 045import org.forgerock.opendj.ldap.schema.ObjectClass; 046import org.forgerock.opendj.ldap.schema.SchemaBuilder; 047import org.forgerock.opendj.ldap.schema.SchemaElement; 048import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo; 049import org.opends.guitools.controlpanel.datamodel.SomeSchemaElement; 050import org.opends.guitools.controlpanel.ui.ColorAndFontConstants; 051import org.opends.guitools.controlpanel.ui.ProgressDialog; 052import org.opends.guitools.controlpanel.util.Utilities; 053import org.opends.server.config.ConfigConstants; 054import org.opends.server.core.DirectoryServer; 055import org.opends.server.types.Attributes; 056import org.opends.server.types.Entry; 057import org.opends.server.types.LDIFExportConfig; 058import org.opends.server.types.LDIFImportConfig; 059import org.opends.server.types.Modification; 060import org.opends.server.types.OpenDsException; 061import org.opends.server.types.Schema; 062import org.opends.server.util.LDIFReader; 063import org.opends.server.util.LDIFWriter; 064 065/** The task that is launched when a schema element must be deleted. */ 066public class DeleteSchemaElementsTask extends Task 067{ 068 /** The list of object classes that the user asked to delete. */ 069 private Set<ObjectClass> providedOcsToDelete = new LinkedHashSet<>(); 070 /** The list of attributes that the user asked to delete. */ 071 private Set<AttributeType> providedAttrsToDelete = new LinkedHashSet<>(); 072 /** The list of object classes that will be actually deleted (some might be recreated). */ 073 private Set<ObjectClass> ocsToDelete = new LinkedHashSet<>(); 074 /** The list of attributes that will be actually deleted (some might be recreated). */ 075 private Set<AttributeType> attrsToDelete = new LinkedHashSet<>(); 076 /** The list of object classes that will be recreated. */ 077 private Set<ObjectClass> ocsToAdd = new LinkedHashSet<>(); 078 /** The list of attributes that will be recreated. */ 079 private Set<AttributeType> attrsToAdd = new LinkedHashSet<>(); 080 081 /** 082 * Constructor of the task. 083 * @param info the control panel information. 084 * @param dlg the progress dialog where the task progress will be displayed. 085 * @param ocsToDelete the object classes that must be deleted (ordered). 086 * @param attrsToDelete the attributes that must be deleted (ordered). 087 */ 088 public DeleteSchemaElementsTask(ControlPanelInfo info, ProgressDialog dlg, 089 Set<ObjectClass> ocsToDelete, Set<AttributeType> attrsToDelete) 090 { 091 super(info, dlg); 092 093 this.providedOcsToDelete.addAll(ocsToDelete); 094 this.providedAttrsToDelete.addAll(attrsToDelete); 095 096 Schema schema = info.getServerDescriptor().getSchema(); 097 LinkedHashSet<AttributeType> allAttrsToDelete = 098 DeleteSchemaElementsTask.getOrderedAttributesToDelete(attrsToDelete, 099 schema); 100 LinkedHashSet<ObjectClass> allOcsToDelete = null; 101 if (!attrsToDelete.isEmpty()) 102 { 103 allOcsToDelete = 104 DeleteSchemaElementsTask.getOrderedObjectClassesToDeleteFromAttrs( 105 attrsToDelete, schema); 106 } 107 if (!ocsToDelete.isEmpty()) 108 { 109 LinkedHashSet<ObjectClass> orderedOCs = 110 DeleteSchemaElementsTask.getOrderedObjectClassesToDelete(ocsToDelete, schema); 111 if (allOcsToDelete == null) 112 { 113 allOcsToDelete = orderedOCs; 114 } 115 else 116 { 117 allOcsToDelete.addAll(orderedOCs); 118 } 119 } 120 ArrayList<AttributeType> lAttrsToDelete = new ArrayList<>(allAttrsToDelete); 121 for (int i = lAttrsToDelete.size() - 1; i >= 0; i--) 122 { 123 AttributeType attrToDelete = lAttrsToDelete.get(i); 124 if (!attrsToDelete.contains(attrToDelete)) 125 { 126 AttributeType attrToAdd = getAttributeToAdd(attrToDelete); 127 if (attrToAdd != null) 128 { 129 attrsToAdd.add(attrToAdd); 130 } 131 } 132 } 133 134 assert allOcsToDelete != null; 135 ArrayList<ObjectClass> lOcsToDelete = new ArrayList<>(allOcsToDelete); 136 for (int i = lOcsToDelete.size() - 1; i >= 0; i--) 137 { 138 ObjectClass ocToDelete = lOcsToDelete.get(i); 139 if (!ocsToDelete.contains(ocToDelete)) 140 { 141 ocsToAdd.add(getObjectClassToAdd(lOcsToDelete.get(i))); 142 } 143 } 144 145 this.ocsToDelete.addAll(allOcsToDelete); 146 this.attrsToDelete.addAll(allAttrsToDelete); 147 } 148 149 @Override 150 public Set<String> getBackends() 151 { 152 return Collections.emptySet(); 153 } 154 155 @Override 156 public boolean canLaunch(Task taskToBeLaunched, 157 Collection<LocalizableMessage> incompatibilityReasons) 158 { 159 boolean canLaunch = true; 160 if (state == State.RUNNING && 161 (taskToBeLaunched.getType() == Task.Type.DELETE_SCHEMA_ELEMENT || 162 taskToBeLaunched.getType() == Task.Type.MODIFY_SCHEMA_ELEMENT || 163 taskToBeLaunched.getType() == Task.Type.NEW_SCHEMA_ELEMENT)) 164 { 165 incompatibilityReasons.add(getIncompatibilityMessage(this, 166 taskToBeLaunched)); 167 canLaunch = false; 168 } 169 return canLaunch; 170 } 171 172 @Override 173 public Type getType() 174 { 175 return Type.NEW_SCHEMA_ELEMENT; 176 } 177 178 @Override 179 public void runTask() 180 { 181 state = State.RUNNING; 182 lastException = null; 183 184 try 185 { 186 updateSchema(); 187 state = State.FINISHED_SUCCESSFULLY; 188 } 189 catch (Throwable t) 190 { 191 lastException = t; 192 state = State.FINISHED_WITH_ERROR; 193 } 194 } 195 196 @Override 197 protected String getCommandLinePath() 198 { 199 return null; 200 } 201 202 @Override 203 protected List<String> getCommandLineArguments() 204 { 205 return Collections.emptyList(); 206 } 207 208 @Override 209 public LocalizableMessage getTaskDescription() 210 { 211 return INFO_CTRL_PANEL_DELETE_SCHEMA_ELEMENT_TASK_DESCRIPTION.get(); 212 } 213 214 /** 215 * Updates the schema. 216 * @throws OpenDsException if an error occurs. 217 */ 218 private void updateSchema() throws OpenDsException 219 { 220 final int totalNumber = ocsToDelete.size() + attrsToDelete.size(); 221 int numberDeleted = 0; 222 for (ObjectClass objectClass : ocsToDelete) 223 { 224 final SomeSchemaElement element = new SomeSchemaElement(objectClass); 225 deleteSchemaElement(element, numberDeleted, totalNumber, INFO_CTRL_PANEL_DELETING_OBJECTCLASS); 226 numberDeleted++; 227 } 228 229 for (AttributeType attribute : attrsToDelete) 230 { 231 final SomeSchemaElement element = new SomeSchemaElement(attribute); 232 deleteSchemaElement(element, numberDeleted, totalNumber, INFO_CTRL_PANEL_DELETING_ATTRIBUTE); 233 numberDeleted++; 234 } 235 236 if (!ocsToAdd.isEmpty() || !attrsToAdd.isEmpty()) 237 { 238 SwingUtilities.invokeLater(new Runnable() 239 { 240 @Override 241 public void run() 242 { 243 getProgressDialog().appendProgressHtml(Utilities.applyFont( 244 "<br><br>"+ 245 INFO_CTRL_PANEL_EXPLANATION_TO_DELETE_REFERENCED_ELEMENTS.get()+ 246 "<br><br>", 247 ColorAndFontConstants.progressFont)); 248 } 249 }); 250 251 NewSchemaElementsTask createTask = 252 new NewSchemaElementsTask(getInfo(), getProgressDialog(), ocsToAdd, 253 attrsToAdd); 254 createTask.runTask(); 255 } 256 } 257 258 private void deleteSchemaElement(final SomeSchemaElement element, final int numberDeleted, final int totalNumber, 259 final Arg1<Object> deletingElementMsg) throws OnlineUpdateException, OpenDsException 260 { 261 SwingUtilities.invokeLater(new Runnable() 262 { 263 @Override 264 public void run() 265 { 266 final boolean isFirst = numberDeleted == 0; 267 if (!isFirst) 268 { 269 getProgressDialog().appendProgressHtml("<br><br>"); 270 } 271 printEquivalentCommandToDelete(element); 272 getProgressDialog().appendProgressHtml( 273 Utilities.getProgressWithPoints( 274 deletingElementMsg.get(element.getNameOrOID()), ColorAndFontConstants.progressFont)); 275 } 276 }); 277 278 if (isServerRunning()) 279 { 280 try 281 { 282 BasicAttribute attr = new BasicAttribute(element.getAttributeName()); 283 attr.add(getSchemaFileAttributeValue(element)); 284 ModificationItem mod = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, attr); 285 getInfo().getConnection().getLdapContext().modifyAttributes( 286 ConfigConstants.DN_DEFAULT_SCHEMA_ROOT, 287 new ModificationItem[] { mod }); 288 } 289 catch (NamingException ne) 290 { 291 throw new OnlineUpdateException(ERR_CTRL_PANEL_ERROR_UPDATING_SCHEMA.get(ne), ne); 292 } 293 } 294 else 295 { 296 updateSchemaFile(element); 297 } 298 299 final int fNumberDeleted = numberDeleted + 1; 300 SwingUtilities.invokeLater(new Runnable() 301 { 302 @Override 303 public void run() 304 { 305 getProgressDialog().getProgressBar().setIndeterminate(false); 306 getProgressDialog().getProgressBar().setValue((fNumberDeleted * 100) / totalNumber); 307 getProgressDialog().appendProgressHtml( 308 Utilities.getProgressDone(ColorAndFontConstants.progressFont)); 309 } 310 }); 311 } 312 313 /** 314 * Updates the schema file by deleting the provided schema element. 315 * @param schemaElement the schema element to be deleted. 316 * @throws OpenDsException if an error occurs. 317 */ 318 private void updateSchemaFile(SomeSchemaElement schemaElement) throws OpenDsException 319 { 320 String schemaFile = getSchemaFile(schemaElement); 321 322 try (LDIFExportConfig exportConfig = new LDIFExportConfig(schemaFile, OVERWRITE); 323 LDIFReader reader = new LDIFReader(new LDIFImportConfig(schemaFile))) 324 { 325 Entry schemaEntry = reader.readEntry(); 326 327 Modification mod = new Modification(ModificationType.DELETE, 328 Attributes.create( 329 schemaElement.getAttributeName(), 330 getSchemaFileAttributeValue(schemaElement))); 331 schemaEntry.applyModification(mod); 332 try (LDIFWriter writer = new LDIFWriter(exportConfig)) 333 { 334 writer.writeEntry(schemaEntry); 335 exportConfig.getWriter().newLine(); 336 } 337 } 338 catch (IOException e) 339 { 340 throw new OfflineUpdateException( 341 ERR_CTRL_PANEL_ERROR_UPDATING_SCHEMA.get(e), e); 342 } 343 } 344 345 /** 346 * Returns the schema file for a given schema element. 347 * @param element the schema element. 348 * @return the schema file for a given schema element. 349 */ 350 private String getSchemaFile(SomeSchemaElement element) 351 { 352 String schemaFile = element.getSchemaFile(); 353 if (schemaFile == null) 354 { 355 schemaFile = ConfigConstants.FILE_USER_SCHEMA_ELEMENTS; 356 } 357 File f = new File(schemaFile); 358 if (!f.isAbsolute()) 359 { 360 f = new File( 361 DirectoryServer.getEnvironmentConfig().getSchemaDirectory(), 362 schemaFile); 363 } 364 return f.getAbsolutePath(); 365 } 366 367 /** 368 * Returns the value in the schema file for the provided element. 369 * @param element the schema element. 370 * @return the value in the schema file for the provided element. 371 */ 372 private String getSchemaFileAttributeValue(SomeSchemaElement element) 373 { 374 return element.toString(); 375 } 376 377 /** 378 * Prints the equivalent command-line to delete the schema element in the 379 * progress dialog. 380 * @param element the schema element to be deleted. 381 */ 382 private void printEquivalentCommandToDelete(SomeSchemaElement element) 383 { 384 String schemaFile = getSchemaFile(element); 385 String attrName = element.getAttributeName(); 386 String attrValue = getSchemaFileAttributeValue(element); 387 388 String msg; 389 if (!isServerRunning()) 390 { 391 msg = getEquivalentCommandOfflineMsg(element, schemaFile) 392 + "<br><b>" + attrName + ": " + attrValue + "</b><br><br>"; 393 } 394 else 395 { 396 ArrayList<String> args = new ArrayList<>(); 397 args.add("-a"); 398 args.addAll(getObfuscatedCommandLineArguments( 399 getConnectionCommandLineArguments(true, true))); 400 args.add(getNoPropertiesFileArgument()); 401 String equiv = getEquivalentCommandLine(getCommandLinePath("ldapmodify"), args); 402 403 msg = getEquivalentCommandOnlineMsg(element) 404 + "<br><b>" + equiv + "<br>" 405 + "dn: cn=schema<br>" 406 + "changetype: modify<br>" 407 + "delete: " + attrName + "<br>" 408 + attrName + ": " + attrValue 409 + "</b>" 410 + "<br><br>"; 411 } 412 getProgressDialog().appendProgressHtml(Utilities.applyFont(msg, ColorAndFontConstants.progressFont)); 413 } 414 415 private LocalizableMessage getEquivalentCommandOfflineMsg(SomeSchemaElement element, String schemaFile) 416 { 417 if (element.isAttributeType()) 418 { 419 return INFO_CTRL_PANEL_EQUIVALENT_CMD_TO_DELETE_ATTRIBUTE_OFFLINE.get(element.getNameOrOID(), schemaFile); 420 } 421 return INFO_CTRL_PANEL_EQUIVALENT_CMD_TO_DELETE_OBJECTCLASS_OFFLINE.get(element.getNameOrOID(), schemaFile); 422 } 423 424 private LocalizableMessage getEquivalentCommandOnlineMsg(SomeSchemaElement element) 425 { 426 if (element.isAttributeType()) 427 { 428 return INFO_CTRL_PANEL_EQUIVALENT_CMD_TO_DELETE_ATTRIBUTE_ONLINE.get(element.getNameOrOID()); 429 } 430 return INFO_CTRL_PANEL_EQUIVALENT_CMD_TO_DELETE_OBJECTCLASS_ONLINE.get(element.getNameOrOID()); 431 } 432 433 private AttributeType getAttributeToAdd(AttributeType attrToDelete) 434 { 435 boolean isSuperior = false; 436 for (AttributeType attr : providedAttrsToDelete) 437 { 438 if (attr.equals(attrToDelete.getSuperiorType())) 439 { 440 isSuperior = true; 441 break; 442 } 443 } 444 if (isSuperior) 445 { 446 // get a new attribute without the superior type 447 return SomeSchemaElement.changeSuperiorType(attrToDelete, null); 448 } 449 else 450 { 451 // Nothing to be changed in the definition of the attribute itself. 452 return attrToDelete; 453 } 454 } 455 456 private ObjectClass getObjectClassToAdd(ObjectClass ocToDelete) 457 { 458 boolean containsAttribute = containsAttributeToDelete(ocToDelete); 459 boolean hasSuperior = false; 460 Set<ObjectClass> newSuperiors = new LinkedHashSet<>(); 461 for (ObjectClass sup : ocToDelete.getSuperiorClasses()) 462 { 463 boolean isFound = false; 464 for (ObjectClass oc: providedOcsToDelete) 465 { 466 if(sup.equals(oc)) 467 { 468 hasSuperior = true; 469 isFound = true; 470 newSuperiors.addAll(getNewSuperiors(oc)); 471 break; 472 } 473 } 474 if (!isFound) 475 { 476 //Use the same super if not found in the list. 477 newSuperiors.add(sup); 478 } 479 } 480 481 if (containsAttribute || hasSuperior) 482 { 483 Map<String, List<String>> extraProperties = cloneExtraProperties(ocToDelete); 484 Set<AttributeType> required; 485 Set<AttributeType> optional; 486 if (containsAttribute) 487 { 488 required = new HashSet<>(ocToDelete.getDeclaredRequiredAttributes()); 489 optional = new HashSet<>(ocToDelete.getDeclaredOptionalAttributes()); 490 required.removeAll(providedAttrsToDelete); 491 optional.removeAll(providedAttrsToDelete); 492 } 493 else 494 { 495 required = ocToDelete.getDeclaredRequiredAttributes(); 496 optional = ocToDelete.getDeclaredOptionalAttributes(); 497 } 498 final String oid = ocToDelete.getOID(); 499 final Schema schema = getInfo().getServerDescriptor().getSchema(); 500 return new SchemaBuilder(schema.getSchemaNG()).buildObjectClass(oid) 501 .names(ocToDelete.getNames()) 502 .description(ocToDelete.getDescription()) 503 .superiorObjectClasses(getNameOrOIDsForOCs(newSuperiors)) 504 .requiredAttributes(getNameOrOIDsForATs(required)) 505 .optionalAttributes(getNameOrOIDsForATs(optional)) 506 .type(ocToDelete.getObjectClassType()) 507 .obsolete(ocToDelete.isObsolete()) 508 .extraProperties(extraProperties) 509 .addToSchema() 510 .toSchema() 511 .getObjectClass(oid); 512 } 513 else 514 { 515 // Nothing to be changed in the definition of the object class itself. 516 return ocToDelete; 517 } 518 } 519 520 private boolean containsAttributeToDelete(ObjectClass ocToDelete) 521 { 522 for (AttributeType attr : providedAttrsToDelete) 523 { 524 if (ocToDelete.getRequiredAttributes().contains(attr) 525 || ocToDelete.getOptionalAttributes().contains(attr)) 526 { 527 return true; 528 } 529 } 530 return false; 531 } 532 533 private Set<ObjectClass> getNewSuperiors(ObjectClass currentSup) 534 { 535 Set<ObjectClass> newSuperiors = new LinkedHashSet<>(); 536 if (currentSup.getSuperiorClasses() != null && 537 !currentSup.getSuperiorClasses().isEmpty()) 538 { 539 for (ObjectClass o : currentSup.getSuperiorClasses()) 540 { 541 if (providedOcsToDelete.contains(o)) 542 { 543 newSuperiors.addAll(getNewSuperiors(o)); 544 } 545 else 546 { 547 newSuperiors.add(o); 548 } 549 } 550 } 551 return newSuperiors; 552 } 553 554 /** 555 * Returns an ordered set of the attributes that must be deleted. 556 * @param attrsToDelete the attributes to be deleted. 557 * @param schema the server schema. 558 * @return an ordered list of the attributes that must be deleted. 559 */ 560 public static LinkedHashSet<AttributeType> getOrderedAttributesToDelete( 561 Collection<AttributeType> attrsToDelete, Schema schema) 562 { 563 LinkedHashSet<AttributeType> orderedAttributes = new LinkedHashSet<>(); 564 for (AttributeType attribute : attrsToDelete) 565 { 566 orderedAttributes.addAll(getOrderedChildrenToDelete(attribute, schema)); 567 orderedAttributes.add(attribute); 568 } 569 return orderedAttributes; 570 } 571 572 /** 573 * Returns an ordered list of the object classes that must be deleted. 574 * @param ocsToDelete the object classes to be deleted. 575 * @param schema the server schema. 576 * @return an ordered list of the object classes that must be deleted. 577 */ 578 public static LinkedHashSet<ObjectClass> getOrderedObjectClassesToDelete( 579 Collection<ObjectClass> ocsToDelete, Schema schema) 580 { 581 LinkedHashSet<ObjectClass> orderedOcs = new LinkedHashSet<>(); 582 for (ObjectClass oc : ocsToDelete) 583 { 584 orderedOcs.addAll(getOrderedChildrenToDelete(oc, schema)); 585 orderedOcs.add(oc); 586 } 587 return orderedOcs; 588 } 589 590 /** 591 * Returns an ordered list of the object classes that must be deleted when 592 * deleting a list of attributes that must be deleted. 593 * @param attrsToDelete the attributes to be deleted. 594 * @param schema the server schema. 595 * @return an ordered list of the object classes that must be deleted when 596 * deleting a list of attributes that must be deleted. 597 */ 598 public static LinkedHashSet<ObjectClass> 599 getOrderedObjectClassesToDeleteFromAttrs( 600 Collection<AttributeType> attrsToDelete, Schema schema) 601 { 602 LinkedHashSet<ObjectClass> orderedOcs = new LinkedHashSet<>(); 603 ArrayList<ObjectClass> dependentClasses = new ArrayList<>(); 604 for (AttributeType attr : attrsToDelete) 605 { 606 for (ObjectClass oc : schema.getObjectClasses()) 607 { 608 if (oc.getRequiredAttributes().contains(attr) 609 || oc.getOptionalAttributes().contains(attr)) 610 { 611 dependentClasses.add(oc); 612 } 613 } 614 } 615 for (ObjectClass oc : dependentClasses) 616 { 617 orderedOcs.addAll(getOrderedChildrenToDelete(oc, schema)); 618 orderedOcs.add(oc); 619 } 620 return orderedOcs; 621 } 622 623 /** 624 * Clones the extra properties of the provided schema element. This can 625 * be used when copying schema elements. 626 * @param element the schema element. 627 * @return the extra properties of the provided schema element. 628 */ 629 public static Map<String, List<String>> cloneExtraProperties(SchemaElement element) 630 { 631 Map<String, List<String>> extraProperties = new HashMap<>(); 632 Map<String, List<String>> props = element.getExtraProperties(); 633 for (String name : props.keySet()) 634 { 635 extraProperties.put(name, new ArrayList<>(props.get(name))); 636 } 637 return extraProperties; 638 } 639 640 private static LinkedHashSet<AttributeType> getOrderedChildrenToDelete( 641 AttributeType attribute, Schema schema) 642 { 643 LinkedHashSet<AttributeType> children = new LinkedHashSet<>(); 644 for (AttributeType attr : schema.getAttributeTypes()) 645 { 646 if (attribute.equals(attr.getSuperiorType())) 647 { 648 children.addAll(getOrderedChildrenToDelete(attr, schema)); 649 children.add(attr); 650 } 651 } 652 return children; 653 } 654 655 private static LinkedHashSet<ObjectClass> getOrderedChildrenToDelete( 656 ObjectClass objectClass, Schema schema) 657 { 658 LinkedHashSet<ObjectClass> children = new LinkedHashSet<>(); 659 for (ObjectClass oc : schema.getObjectClasses()) 660 { 661 if (oc.getSuperiorClasses().contains(objectClass)) 662 { 663 children.addAll(getOrderedChildrenToDelete(oc, schema)); 664 children.add(oc); 665 } 666 } 667 return children; 668 } 669}