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