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