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 2009 Sun Microsystems, Inc.
015 * Portions Copyright 2013-2016 ForgeRock AS.
016 */
017package org.opends.guitools.controlpanel.task;
018
019import static org.forgerock.util.Utils.*;
020import static org.opends.messages.AdminToolMessages.*;
021
022import java.io.File;
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.Collections;
026import java.util.HashSet;
027import java.util.Iterator;
028import java.util.LinkedHashMap;
029import java.util.LinkedHashSet;
030import java.util.List;
031import java.util.Map;
032import java.util.Set;
033
034import javax.naming.NamingException;
035import javax.naming.directory.BasicAttribute;
036import javax.naming.directory.DirContext;
037import javax.naming.directory.ModificationItem;
038import javax.swing.SwingUtilities;
039
040import org.forgerock.i18n.LocalizableMessage;
041import org.forgerock.opendj.ldap.ModificationType;
042import org.forgerock.opendj.ldap.schema.AttributeType;
043import org.forgerock.opendj.ldap.schema.MatchingRule;
044import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
045import org.opends.guitools.controlpanel.ui.ColorAndFontConstants;
046import org.opends.guitools.controlpanel.ui.ProgressDialog;
047import org.opends.guitools.controlpanel.util.Utilities;
048import org.opends.server.config.ConfigConstants;
049import org.opends.server.core.DirectoryServer;
050import org.opends.server.schema.SomeSchemaElement;
051import org.opends.server.types.Attributes;
052import org.opends.server.types.DirectoryException;
053import org.opends.server.types.Entry;
054import org.opends.server.types.ExistingFileBehavior;
055import org.opends.server.types.LDIFExportConfig;
056import org.opends.server.types.LDIFImportConfig;
057import org.opends.server.types.Modification;
058import org.opends.server.types.ObjectClass;
059import org.opends.server.types.OpenDsException;
060import org.opends.server.util.LDIFReader;
061import org.opends.server.util.LDIFWriter;
062import org.opends.server.util.ServerConstants;
063import org.opends.server.util.StaticUtils;
064
065/**
066 * An abstract class used to re-factor some code between the different tasks
067 * that create elements in the schema.
068 */
069public class NewSchemaElementsTask extends Task
070{
071  private final Set<ObjectClass> ocsToAdd = new LinkedHashSet<>();
072  private final Set<AttributeType> attrsToAdd = new LinkedHashSet<>();
073
074  /**
075   * Constructor of the task.
076   *
077   * @param info
078   *          the control panel information.
079   * @param dlg
080   *          the progress dialog where the task progress will be displayed.
081   * @param ocsToAdd
082   *          the object classes that must be created in order.
083   * @param attrsToAdd
084   *          the attributes that must be created in order.
085   */
086  public NewSchemaElementsTask(
087      ControlPanelInfo info, ProgressDialog dlg, Set<ObjectClass> ocsToAdd, Set<AttributeType> attrsToAdd)
088  {
089    super(info, dlg);
090    this.ocsToAdd.addAll(ocsToAdd);
091    this.attrsToAdd.addAll(attrsToAdd);
092  }
093
094  @Override
095  public Set<String> getBackends()
096  {
097    return Collections.emptySet();
098  }
099
100  @Override
101  public boolean canLaunch(Task taskToBeLaunched, Collection<LocalizableMessage> incompatibilityReasons)
102  {
103    if (state == State.RUNNING &&
104        (taskToBeLaunched.getType() == Task.Type.DELETE_SCHEMA_ELEMENT ||
105         taskToBeLaunched.getType() == Task.Type.MODIFY_SCHEMA_ELEMENT ||
106         taskToBeLaunched.getType() == Task.Type.NEW_SCHEMA_ELEMENT))
107    {
108      incompatibilityReasons.add(getIncompatibilityMessage(this, taskToBeLaunched));
109      return false;
110    }
111    return true;
112  }
113
114  @Override
115  public void runTask()
116  {
117    state = State.RUNNING;
118    lastException = null;
119
120    try
121    {
122      updateSchema();
123      state = State.FINISHED_SUCCESSFULLY;
124    }
125    catch (Throwable t)
126    {
127      lastException = t;
128      state = State.FINISHED_WITH_ERROR;
129    }
130  }
131
132  @Override
133  public Type getType()
134  {
135    return Type.NEW_SCHEMA_ELEMENT;
136  }
137
138  @Override
139  public LocalizableMessage getTaskDescription()
140  {
141    if (attrsToAdd.size() == 1 && ocsToAdd.isEmpty())
142    {
143      return INFO_CTRL_PANEL_NEW_ATTRIBUTE_TASK_DESCRIPTION.get(attrsToAdd.iterator().next().getNameOrOID());
144    }
145    else if (ocsToAdd.size() == 1 && attrsToAdd.isEmpty())
146    {
147      return INFO_CTRL_PANEL_NEW_OBJECTCLASS_TASK_DESCRIPTION.get(ocsToAdd.iterator().next().getNameOrOID());
148    }
149    else
150    {
151      final List<String> attrNames = getElementsNameOrOID(attributeTypesToSchemaElements(attrsToAdd));
152      final List<String> ocNames = getElementsNameOrOID(objectClassesToSchemaElements(ocsToAdd));
153      if (ocNames.isEmpty())
154      {
155        return INFO_CTRL_PANEL_NEW_ATTRIBUTES_TASK_DESCRIPTION.get(joinAsString(", ", attrNames));
156      }
157      else if (attrNames.isEmpty())
158      {
159        return INFO_CTRL_PANEL_NEW_OBJECTCLASSES_TASK_DESCRIPTION.get(joinAsString(", ", ocNames));
160      }
161      else
162      {
163        return INFO_CTRL_PANEL_NEW_SCHEMA_ELEMENTS_TASK_DESCRIPTION.get(
164            joinAsString(", ", attrNames), joinAsString(", ", ocNames));
165      }
166    }
167  }
168
169  private List<String> getElementsNameOrOID(final Collection<SomeSchemaElement> schemaElements)
170  {
171    final List<String> nameOrOIDs = new ArrayList<>();
172    for (SomeSchemaElement schemaElement : schemaElements)
173    {
174      nameOrOIDs.add(schemaElement.getNameOrOID());
175    }
176    return nameOrOIDs;
177  }
178
179  /**
180   * Update the schema.
181   *
182   * @throws OpenDsException
183   *           if an error occurs.
184   */
185  private void updateSchema() throws OpenDsException
186  {
187    if (isServerRunning())
188    {
189      updateSchemaOnline();
190    }
191    else
192    {
193      updateSchemaOffline();
194    }
195  }
196
197  @Override
198  protected String getCommandLinePath()
199  {
200    return null;
201  }
202
203  @Override
204  protected List<String> getCommandLineArguments()
205  {
206    return Collections.emptyList();
207  }
208
209  /**
210   * Add the schema elements one by one: we are not sure that the server will
211   * handle the adds sequentially if we only send one modification.
212   *
213   * @throws OpenDsException
214   */
215  private void updateSchemaOnline() throws OpenDsException
216  {
217    for (AttributeType attr : attrsToAdd)
218    {
219      addAttributeOnline(attr);
220      appendNewLinesToProgress();
221    }
222
223    for (ObjectClass oc : ocsToAdd)
224    {
225      addObjectClassOnline(oc);
226      appendNewLinesToProgress();
227    }
228  }
229
230  private void appendNewLinesToProgress()
231  {
232    SwingUtilities.invokeLater(new Runnable()
233    {
234      @Override
235      public void run()
236      {
237        getProgressDialog().appendProgressHtml(Utilities.applyFont("<br><br>", ColorAndFontConstants.progressFont));
238      }
239    });
240  }
241
242  private void updateSchemaOffline() throws OpenDsException
243  {
244    // Group the changes in the same schema file.
245    final Map<String, List<SomeSchemaElement>> mapAttrs = copy(attributeTypesToSchemaElements(attrsToAdd));
246    final Map<String, List<SomeSchemaElement>> mapClasses = copy(objectClassesToSchemaElements(ocsToAdd));
247    final Set<String> allFileNames = new LinkedHashSet<>(mapAttrs.keySet());
248    allFileNames.addAll(mapClasses.keySet());
249
250    for (String fileName : allFileNames)
251    {
252      List<AttributeType> attrs = schemaElementsToAttributeTypes(get(mapAttrs, fileName));
253      List<ObjectClass> ocs = schemaElementsToObjectClasses(get(mapClasses, fileName));
254
255      if ("".equals(fileName))
256      {
257        fileName = null;
258      }
259      updateSchemaOffline(fileName, attrs, ocs);
260      appendNewLinesToProgress();
261    }
262  }
263
264  private List<SomeSchemaElement> get(Map<String, List<SomeSchemaElement>> hmElems, String fileName)
265  {
266    List<SomeSchemaElement> elems = hmElems.get(fileName);
267    if (elems != null)
268    {
269      return elems;
270    }
271    return Collections.emptyList();
272  }
273
274  private Map<String, List<SomeSchemaElement>> copy(Set<SomeSchemaElement> elemsToAdd)
275  {
276    Map<String, List<SomeSchemaElement>> hmElems = new LinkedHashMap<>();
277    for (SomeSchemaElement elem : elemsToAdd)
278    {
279      String fileName = elem.getSchemaFile();
280      if (fileName == null)
281      {
282        fileName = "";
283      }
284      List<SomeSchemaElement> elems = hmElems.get(fileName);
285      if (elems == null)
286      {
287        elems = new ArrayList<>();
288        hmElems.put(fileName, elems);
289      }
290      elems.add(elem);
291    }
292    return hmElems;
293  }
294
295  private void addAttributeOnline(final AttributeType attribute) throws OpenDsException
296  {
297    addSchemaElementOnline(new SomeSchemaElement(attribute),
298        INFO_CTRL_PANEL_CREATING_ATTRIBUTE_PROGRESS.get(attribute.getNameOrOID()));
299  }
300
301  private void addObjectClassOnline(final ObjectClass objectClass) throws OpenDsException
302  {
303    addSchemaElementOnline(new SomeSchemaElement(objectClass),
304        INFO_CTRL_PANEL_CREATING_OBJECTCLASS_PROGRESS.get(objectClass.getNameOrOID()));
305  }
306
307  private void addSchemaElementOnline(final SomeSchemaElement schemaElement, final LocalizableMessage progressMsg)
308      throws OpenDsException
309  {
310    SwingUtilities.invokeLater(new Runnable()
311    {
312      @Override
313      public void run()
314      {
315        printEquivalentCommandLineToAddOnline(schemaElement);
316        getProgressDialog().appendProgressHtml(
317            Utilities.getProgressWithPoints(progressMsg, ColorAndFontConstants.progressFont));
318      }
319    });
320    try
321    {
322      final BasicAttribute attr = new BasicAttribute(schemaElement.getAttributeName());
323      attr.add(getElementDefinition(schemaElement));
324      final ModificationItem mod = new ModificationItem(DirContext.ADD_ATTRIBUTE, attr);
325      getInfo().getDirContext().modifyAttributes(
326          ConfigConstants.DN_DEFAULT_SCHEMA_ROOT, new ModificationItem[] { mod });
327    }
328    catch (NamingException ne)
329    {
330      throw new OnlineUpdateException(ERR_CTRL_PANEL_ERROR_UPDATING_SCHEMA.get(ne), ne);
331    }
332    notifyConfigurationElementCreated(schemaElement);
333    SwingUtilities.invokeLater(new Runnable()
334    {
335      @Override
336      public void run()
337      {
338        getProgressDialog().appendProgressHtml(Utilities.getProgressDone(ColorAndFontConstants.progressFont));
339      }
340    });
341  }
342
343  private String getValueOffline(SomeSchemaElement element)
344  {
345    final Map<String, List<String>> props = element.getExtraProperties();
346    List<String> previousValues = props.get(ServerConstants.SCHEMA_PROPERTY_FILENAME);
347    element.setExtraPropertySingleValue(null, ServerConstants.SCHEMA_PROPERTY_FILENAME, null);
348    String attributeWithoutFileDefinition = getElementDefinition(element);
349
350    if (previousValues != null && !previousValues.isEmpty())
351    {
352      element.setExtraPropertyMultipleValues(null,
353          ServerConstants.SCHEMA_PROPERTY_FILENAME, new ArrayList<String>(previousValues));
354    }
355    return attributeWithoutFileDefinition;
356  }
357
358  private String getElementDefinition(SomeSchemaElement element)
359  {
360    final List<String> names = new ArrayList<>();
361    for (final String name : element.getNames())
362    {
363      names.add(StaticUtils.toLowerCase(name));
364    }
365    return element.isAttributeType()
366        ? getAttributeTypeDefinition(element.getAttributeType(), names)
367        : getObjectClassDefinition(element.getObjectClass(), names);
368  }
369
370  private String getAttributeTypeDefinition(final AttributeType attributeType, final List<String> names)
371  {
372    final StringBuilder buffer = new StringBuilder();
373    buffer.append("( ").append(attributeType.getOID());
374    appendCollection(buffer, "NAME", names);
375    appendDescription(buffer, attributeType.getDescription());
376    appendIfTrue(buffer, " OBSOLETE", attributeType.isObsolete());
377
378    final AttributeType superiorType = attributeType.getSuperiorType();
379    final String superiorTypeOID = superiorType != null ? superiorType.getOID() : null;
380    appendIfNotNull(buffer, " SUP ", superiorTypeOID);
381    addMatchingRuleIfNotNull(buffer, " EQUALITY ", attributeType.getEqualityMatchingRule());
382    addMatchingRuleIfNotNull(buffer, " ORDERING ", attributeType.getOrderingMatchingRule());
383    addMatchingRuleIfNotNull(buffer, " SUBSTR ", attributeType.getSubstringMatchingRule());
384    appendIfNotNull(buffer, " SYNTAX ", attributeType.getSyntax().getOID());
385    appendIfTrue(buffer, " SINGLE-VALUE", attributeType.isSingleValue());
386    appendIfTrue(buffer, " COLLECTIVE", attributeType.isCollective());
387    appendIfTrue(buffer, " NO-USER-MODIFICATION", attributeType.isNoUserModification());
388    appendIfNotNull(buffer, " USAGE ", attributeType.getUsage());
389
390    final MatchingRule approximateMatchingRule = attributeType.getApproximateMatchingRule();
391    if (approximateMatchingRule != null)
392    {
393      buffer.append(" ").append(ServerConstants.SCHEMA_PROPERTY_APPROX_RULE).append(" '")
394            .append(approximateMatchingRule.getOID()).append("'");
395    }
396    appendExtraProperties(buffer, attributeType.getExtraProperties());
397    buffer.append(")");
398
399    return buffer.toString();
400  }
401
402  private void addMatchingRuleIfNotNull(final StringBuilder buffer, final String label, final MatchingRule matchingRule)
403  {
404    if (matchingRule != null)
405    {
406      append(buffer, label, matchingRule.getOID());
407    }
408  }
409
410  private String getObjectClassDefinition(final ObjectClass objectClass, final List<String> names)
411  {
412    final StringBuilder buffer = new StringBuilder();
413    buffer.append("( ");
414    buffer.append(objectClass.getOID());
415    appendCollection(buffer, "NAME", names);
416    appendDescription(buffer, objectClass.getDescription());
417    appendIfTrue(buffer, " OBSOLETE", objectClass.isObsolete());
418    appendOIDs(buffer, "SUP", objectClassesToSchemaElements(objectClass.getSuperiorClasses()));
419    appendIfNotNull(buffer, " ", objectClass.getObjectClassType());
420    appendOIDs(buffer, "MUST", attributeTypesToSchemaElements(objectClass.getRequiredAttributes()));
421    appendOIDs(buffer, "MAY", attributeTypesToSchemaElements(objectClass.getOptionalAttributes()));
422    appendExtraProperties(buffer, objectClass.getExtraProperties());
423    buffer.append(")");
424
425    return buffer.toString();
426  }
427
428  private void appendOIDs(final StringBuilder buffer, final String label,
429      final Collection<SomeSchemaElement> schemaElements)
430  {
431    if (!schemaElements.isEmpty())
432    {
433      final Iterator<SomeSchemaElement> iterator = schemaElements.iterator();
434      final String firstOID = iterator.next().getOID();
435      buffer.append(" ").append(label).append(" ( ").append(firstOID);
436      while (iterator.hasNext())
437      {
438        buffer.append(" $ ").append(iterator.next().getOID());
439      }
440      buffer.append(" )");
441    }
442  }
443
444  private Set<SomeSchemaElement> objectClassesToSchemaElements(final Collection<ObjectClass> classes)
445  {
446    Set<SomeSchemaElement> elements = new HashSet<>();
447    for (ObjectClass objectClass : classes)
448    {
449      elements.add(new SomeSchemaElement(objectClass));
450    }
451    return elements;
452  }
453
454  private Set<SomeSchemaElement> attributeTypesToSchemaElements(final Collection<AttributeType> types)
455  {
456    Set<SomeSchemaElement> elements = new HashSet<>();
457    for (AttributeType type : types)
458    {
459      elements.add(new SomeSchemaElement(type));
460    }
461    return elements;
462  }
463
464  private List<AttributeType> schemaElementsToAttributeTypes(final Collection<SomeSchemaElement> elements)
465  {
466    List<AttributeType> types = new ArrayList<>();
467    for (SomeSchemaElement element : elements)
468    {
469      types.add(element.getAttributeType());
470    }
471    return types;
472  }
473
474  private List<ObjectClass> schemaElementsToObjectClasses(final Collection<SomeSchemaElement> elements)
475  {
476    List<ObjectClass> classes = new ArrayList<>();
477    for (SomeSchemaElement element : elements)
478    {
479      classes.add(element.getObjectClass());
480    }
481    return classes;
482  }
483
484  private void appendIfTrue(final StringBuilder buffer, final String label, final boolean labelIsActive)
485  {
486    if (labelIsActive)
487    {
488      buffer.append(label);
489    }
490  }
491
492  private void appendIfNotNull(final StringBuilder buffer, final String label, final Object value)
493  {
494    if (value != null)
495    {
496      append(buffer, label, value.toString());
497    }
498  }
499
500  private void append(final StringBuilder buffer, final String label, final String value)
501  {
502    buffer.append(label).append(value);
503  }
504
505  private void appendDescription(final StringBuilder buffer, final String description)
506  {
507    if (description != null && !description.isEmpty())
508    {
509      buffer.append(" DESC '");
510      buffer.append(description);
511      buffer.append("'");
512    }
513  }
514
515  private void appendExtraProperties(
516      final StringBuilder buffer, final Map<String, List<String>> extraProperties)
517  {
518    for (final Map.Entry<String, List<String>> e : extraProperties.entrySet())
519    {
520      appendCollection(buffer, e.getKey(), e.getValue());
521    }
522  }
523
524  private void appendCollection(final StringBuilder buffer, final String property, final Collection<String> values)
525  {
526    final Iterator<String> iterator = values.iterator();
527    final boolean isMultiValued = values.size() > 1;
528    if (iterator.hasNext())
529    {
530      final String first = iterator.next();
531      buffer.append(" ").append(property);
532      buffer.append(isMultiValued ? " ( '" : " '").append(first).append("' ");
533      while (iterator.hasNext())
534      {
535        buffer.append("'").append(iterator.next()).append("' ");
536      }
537      if (isMultiValued)
538      {
539        buffer.append(")");
540      }
541    }
542  }
543
544  private void printEquivalentCommandLineToAddOnline(SomeSchemaElement element)
545  {
546    List<String> args = new ArrayList<>();
547    args.add("-a");
548    args.addAll(getObfuscatedCommandLineArguments(getConnectionCommandLineArguments(true, true)));
549    args.add(getNoPropertiesFileArgument());
550
551    final String equivalentCmdLine = getEquivalentCommandLine(getCommandLinePath("ldapmodify"), args);
552    final StringBuilder sb = new StringBuilder();
553    final String attName = element.getAttributeName();
554    final String elementId = element.getNameOrOID();
555    final LocalizableMessage message = element.isAttributeType()
556        ? INFO_CTRL_PANEL_EQUIVALENT_CMD_TO_ADD_ATTRIBUTE_ONLINE.get(elementId)
557        : INFO_CTRL_PANEL_EQUIVALENT_CMD_TO_ADD_OBJECTCLASS_ONLINE.get(elementId);
558    sb.append(message).append("<br><b>")
559      .append(equivalentCmdLine).append("<br>")
560      .append("dn: cn=schema<br>")
561      .append("changetype: modify<br>")
562      .append("add: ").append(attName).append("<br>")
563      .append(attName).append(": ").append(getElementDefinition(element)).append("</b><br><br>");
564    getProgressDialog().appendProgressHtml(Utilities.applyFont(sb.toString(), ColorAndFontConstants.progressFont));
565  }
566
567  private void updateSchemaOffline(
568      String file, final List<AttributeType> attributes, final List<ObjectClass> objectClasses) throws OpenDsException
569  {
570    final List<SomeSchemaElement> schemaElements =
571        new ArrayList<SomeSchemaElement>(attributeTypesToSchemaElements(attributes));
572    schemaElements.addAll(objectClassesToSchemaElements(objectClasses));
573    if (file == null)
574    {
575      file = ConfigConstants.FILE_USER_SCHEMA_ELEMENTS;
576    }
577    File f = new File(file);
578    if (!f.isAbsolute())
579    {
580      f = new File(DirectoryServer.getEnvironmentConfig().getSchemaDirectory(), file);
581    }
582    final String fileName = f.getAbsolutePath();
583    final boolean isSchemaFileDefined = isSchemaFileDefined(fileName);
584    SwingUtilities.invokeLater(new Runnable()
585    {
586      @Override
587      public void run()
588      {
589        final ProgressDialog progressDialog = getProgressDialog();
590        final String command = equivalentCommandToAddOffline(fileName, isSchemaFileDefined, schemaElements);
591        progressDialog.appendProgressHtml(Utilities.applyFont(command, ColorAndFontConstants.progressFont));
592
593        if (attributes.size() == 1 && objectClasses.isEmpty())
594        {
595          String attributeName = attributes.get(0).getNameOrOID();
596          progressDialog.appendProgressHtml(Utilities.getProgressWithPoints(
597              INFO_CTRL_PANEL_CREATING_ATTRIBUTE_PROGRESS.get(attributeName), ColorAndFontConstants.progressFont));
598        }
599        else if (objectClasses.size() == 1 && attributes.isEmpty())
600        {
601          String ocName = objectClasses.get(0).getNameOrOID();
602          progressDialog.appendProgressHtml(Utilities.getProgressWithPoints(
603              INFO_CTRL_PANEL_CREATING_OBJECTCLASS_PROGRESS.get(ocName), ColorAndFontConstants.progressFont));
604        }
605        else
606        {
607          progressDialog.appendProgressHtml(Utilities.getProgressWithPoints(
608              INFO_CTRL_PANEL_UPDATING_SCHEMA_FILE_PROGRESS.get(fileName), ColorAndFontConstants.progressFont));
609        }
610      }
611    });
612
613    if (isSchemaFileDefined)
614    {
615      updateSchemaFile(fileName, schemaElements);
616    }
617    else
618    {
619      updateSchemaUndefinedFile(fileName, schemaElements);
620    }
621
622    for (SomeSchemaElement schemaElement : schemaElements)
623    {
624      notifyConfigurationElementCreated(schemaElement);
625    }
626    SwingUtilities.invokeLater(new Runnable()
627    {
628      @Override
629      public void run()
630      {
631        getProgressDialog().appendProgressHtml(Utilities.getProgressDone(ColorAndFontConstants.progressFont));
632      }
633    });
634  }
635
636  private String equivalentCommandToAddOffline(
637      String schemaFile, boolean isSchemaFileDefined, List<SomeSchemaElement> schemaElements)
638  {
639    List<String> names = getElementsNameOrOID(schemaElements);
640
641    final String namesString = joinAsString(", ", names);
642    final StringBuilder sb = new StringBuilder();
643    if (isSchemaFileDefined)
644    {
645      sb.append(INFO_CTRL_PANEL_EQUIVALENT_CMD_TO_ADD_SCHEMA_ELEMENT_OFFLINE.get(namesString, schemaFile))
646        .append("<br><b>");
647    }
648    else
649    {
650      sb.append(INFO_CTRL_PANEL_EQUIVALENT_CMD_TO_ADD_SCHEMA_ENTRY_OFFLINE.get(namesString, schemaFile))
651        .append("<br><b>");
652      for (String line : getSchemaEntryLines())
653      {
654        sb.append(line);
655        sb.append("<br>");
656      }
657    }
658
659    for (SomeSchemaElement schemaElement : schemaElements)
660    {
661      sb.append(schemaElement.getAttributeName()).append(": ").append(getValueOffline(schemaElement)).append("<br>");
662    }
663    sb.append("</b><br><br>");
664
665    return sb.toString();
666  }
667
668  /**
669   * Returns whether the file defined in the schema element exists or not.
670   *
671   * @param schemaFile
672   *          the path to the schema file.
673   * @return <CODE>true</CODE> if the schema file is defined and
674   *         <CODE>false</CODE> otherwise.
675   */
676  private boolean isSchemaFileDefined(String schemaFile)
677  {
678    try (LDIFReader reader = new LDIFReader(new LDIFImportConfig(schemaFile)))
679    {
680      return reader.readEntry() != null;
681    }
682    catch (Throwable t)
683    {
684      return false;
685    }
686  }
687
688  /**
689   * Returns the list of LDIF lines that are enough to create the entry
690   * containing only the schema element associated with this task.
691   *
692   * @return the list of LDIF lines that are enough to create the entry
693   *         containing only the schema element associated with this task.
694   */
695  private List<String> getSchemaEntryLines()
696  {
697    List<String> lines = new ArrayList<>();
698    lines.add("dn: cn=schema");
699    lines.add("objectClass: top");
700    lines.add("objectClass: ldapSubentry");
701    lines.add("objectClass: subschema");
702    return lines;
703  }
704
705  /**
706   * Updates the contents of the schema file.
707   *
708   * @param schemaFile
709   *          the schema file.
710   * @param isSchemaFileDefined
711   *          whether the schema is defined or not.
712   * @param attributes
713   *          the attributes to add.
714   * @param objectClasses
715   *          the object classes to add.
716   * @throws OpenDsException
717   *           if an error occurs updating the schema file.
718   */
719  private void updateSchemaFile(String schemaFile, List<SomeSchemaElement> schemaElements)
720      throws OpenDsException
721  {
722    try (final LDIFExportConfig exportConfig = new LDIFExportConfig(schemaFile, ExistingFileBehavior.OVERWRITE))
723    {
724      try (final LDIFReader reader = new LDIFReader(new LDIFImportConfig(schemaFile)))
725      {
726        final Entry schemaEntry = reader.readEntry();
727        addElementsToEntry(schemaElements, schemaEntry);
728        try (final LDIFWriter writer = new LDIFWriter(exportConfig))
729        {
730          writer.writeEntry(schemaEntry);
731          exportConfig.getWriter().newLine();
732        }
733      }
734      catch (Throwable t)
735      {
736        throw new OfflineUpdateException(ERR_CTRL_PANEL_ERROR_UPDATING_SCHEMA.get(t), t);
737      }
738    }
739  }
740
741  private void addElementsToEntry(List<SomeSchemaElement> schemaElements, Entry schemaEntry)
742      throws DirectoryException
743  {
744    for (SomeSchemaElement schemaElement : schemaElements)
745    {
746      final Modification mod = new Modification(ModificationType.ADD,
747          Attributes.create(schemaElement.getAttributeName().toLowerCase(), getValueOffline(schemaElement)));
748      schemaEntry.applyModification(mod);
749    }
750  }
751
752  private void updateSchemaUndefinedFile(String schemaFile, List<SomeSchemaElement> schemaElements)
753      throws OfflineUpdateException
754  {
755    try (LDIFExportConfig exportConfig = new LDIFExportConfig(schemaFile, ExistingFileBehavior.FAIL))
756    {
757      List<String> lines = getSchemaEntryLines();
758      for (final SomeSchemaElement schemaElement : schemaElements)
759      {
760        lines.add(schemaElement.getAttributeName() + ": " + getValueOffline(schemaElement));
761      }
762      for (String line : lines)
763      {
764        final boolean wrapLines = exportConfig.getWrapColumn() > 1;
765        LDIFWriter.writeLDIFLine(
766            new StringBuilder(line), exportConfig.getWriter(), wrapLines, exportConfig.getWrapColumn());
767      }
768      exportConfig.getWriter().newLine();
769    }
770    catch (Throwable t)
771    {
772      throw new OfflineUpdateException(ERR_CTRL_PANEL_ERROR_UPDATING_SCHEMA.get(t), t);
773    }
774  }
775}