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.opendj.ldap.ModificationType.*;
020import static org.forgerock.util.Utils.*;
021import static org.opends.messages.AdminToolMessages.*;
022
023import java.io.File;
024import java.util.ArrayList;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.HashSet;
028import java.util.Iterator;
029import java.util.LinkedHashMap;
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.opendj.ldap.schema.AttributeType;
043import org.forgerock.opendj.ldap.schema.MatchingRule;
044import org.forgerock.opendj.ldap.schema.ObjectClass;
045import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
046import org.opends.guitools.controlpanel.datamodel.SomeSchemaElement;
047import org.opends.guitools.controlpanel.ui.ColorAndFontConstants;
048import org.opends.guitools.controlpanel.ui.ProgressDialog;
049import org.opends.guitools.controlpanel.util.Utilities;
050import org.opends.server.config.ConfigConstants;
051import org.opends.server.core.DirectoryServer;
052import org.opends.server.types.Attribute;
053import org.opends.server.types.Attributes;
054import org.opends.server.types.DirectoryException;
055import org.opends.server.types.Entry;
056import org.opends.server.types.ExistingFileBehavior;
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.util.LDIFReader;
062import org.opends.server.util.LDIFWriter;
063import org.opends.server.util.ServerConstants;
064import org.opends.server.util.StaticUtils;
065
066/**
067 * An abstract class used to re-factor some code between the different tasks
068 * that create elements in the schema.
069 */
070public class NewSchemaElementsTask extends Task
071{
072  private final Set<ObjectClass> ocsToAdd = new LinkedHashSet<>();
073  private final Set<AttributeType> attrsToAdd = new LinkedHashSet<>();
074
075  /**
076   * Constructor of the task.
077   *
078   * @param info
079   *          the control panel information.
080   * @param dlg
081   *          the progress dialog where the task progress will be displayed.
082   * @param ocsToAdd
083   *          the object classes that must be created in order.
084   * @param attrsToAdd
085   *          the attributes that must be created in order.
086   */
087  public NewSchemaElementsTask(
088      ControlPanelInfo info, ProgressDialog dlg, Set<ObjectClass> ocsToAdd, Set<AttributeType> attrsToAdd)
089  {
090    super(info, dlg);
091    this.ocsToAdd.addAll(ocsToAdd);
092    this.attrsToAdd.addAll(attrsToAdd);
093  }
094
095  @Override
096  public Set<String> getBackends()
097  {
098    return Collections.emptySet();
099  }
100
101  @Override
102  public boolean canLaunch(Task taskToBeLaunched, Collection<LocalizableMessage> incompatibilityReasons)
103  {
104    Type taskTypeToBeLaunched = taskToBeLaunched.getType();
105    if (state == State.RUNNING &&
106        (taskTypeToBeLaunched == Task.Type.DELETE_SCHEMA_ELEMENT
107            || taskTypeToBeLaunched == Task.Type.MODIFY_SCHEMA_ELEMENT
108            || taskTypeToBeLaunched == Task.Type.NEW_SCHEMA_ELEMENT))
109    {
110      incompatibilityReasons.add(getIncompatibilityMessage(this, taskToBeLaunched));
111      return false;
112    }
113    return true;
114  }
115
116  @Override
117  public void runTask()
118  {
119    state = State.RUNNING;
120    lastException = null;
121
122    try
123    {
124      updateSchema();
125      state = State.FINISHED_SUCCESSFULLY;
126    }
127    catch (Throwable t)
128    {
129      lastException = t;
130      state = State.FINISHED_WITH_ERROR;
131    }
132  }
133
134  @Override
135  public Type getType()
136  {
137    return Type.NEW_SCHEMA_ELEMENT;
138  }
139
140  @Override
141  public LocalizableMessage getTaskDescription()
142  {
143    if (attrsToAdd.size() == 1 && ocsToAdd.isEmpty())
144    {
145      return INFO_CTRL_PANEL_NEW_ATTRIBUTE_TASK_DESCRIPTION.get(attrsToAdd.iterator().next().getNameOrOID());
146    }
147    else if (ocsToAdd.size() == 1 && attrsToAdd.isEmpty())
148    {
149      return INFO_CTRL_PANEL_NEW_OBJECTCLASS_TASK_DESCRIPTION.get(ocsToAdd.iterator().next().getNameOrOID());
150    }
151    else
152    {
153      final List<String> attrNames = getElementsNameOrOID(attributeTypesToSchemaElements(attrsToAdd));
154      final List<String> ocNames = getElementsNameOrOID(objectClassesToSchemaElements(ocsToAdd));
155      String attrNamesStr = joinAsString(", ", attrNames);
156      String ocNamesStr = joinAsString(", ", ocNames);
157      if (ocNames.isEmpty())
158      {
159        return INFO_CTRL_PANEL_NEW_ATTRIBUTES_TASK_DESCRIPTION.get(attrNamesStr);
160      }
161      else if (attrNames.isEmpty())
162      {
163        return INFO_CTRL_PANEL_NEW_OBJECTCLASSES_TASK_DESCRIPTION.get(ocNamesStr);
164      }
165      else
166      {
167        return INFO_CTRL_PANEL_NEW_SCHEMA_ELEMENTS_TASK_DESCRIPTION.get(attrNamesStr, ocNamesStr);
168      }
169    }
170  }
171
172  private List<String> getElementsNameOrOID(final Collection<SomeSchemaElement> schemaElements)
173  {
174    final List<String> nameOrOIDs = new ArrayList<>();
175    for (SomeSchemaElement schemaElement : schemaElements)
176    {
177      nameOrOIDs.add(schemaElement.getNameOrOID());
178    }
179    return nameOrOIDs;
180  }
181
182  /**
183   * Update the schema.
184   *
185   * @throws OpenDsException
186   *           if an error occurs.
187   */
188  private void updateSchema() throws OpenDsException
189  {
190    if (isServerRunning())
191    {
192      updateSchemaOnline();
193    }
194    else
195    {
196      updateSchemaOffline();
197    }
198  }
199
200  @Override
201  protected String getCommandLinePath()
202  {
203    return null;
204  }
205
206  @Override
207  protected List<String> getCommandLineArguments()
208  {
209    return Collections.emptyList();
210  }
211
212  /**
213   * Add the schema elements one by one: we are not sure that the server will
214   * handle the adds sequentially if we only send one modification.
215   *
216   * @throws OpenDsException
217   */
218  private void updateSchemaOnline() throws OpenDsException
219  {
220    for (AttributeType attr : attrsToAdd)
221    {
222      addAttributeOnline(attr);
223      appendNewLinesToProgress();
224    }
225
226    for (ObjectClass oc : ocsToAdd)
227    {
228      addObjectClassOnline(oc);
229      appendNewLinesToProgress();
230    }
231  }
232
233  private void appendNewLinesToProgress()
234  {
235    SwingUtilities.invokeLater(new Runnable()
236    {
237      @Override
238      public void run()
239      {
240        getProgressDialog().appendProgressHtml(Utilities.applyFont("<br><br>", ColorAndFontConstants.progressFont));
241      }
242    });
243  }
244
245  private void updateSchemaOffline() throws OpenDsException
246  {
247    // Group the changes in the same schema file.
248    final Map<String, List<SomeSchemaElement>> mapAttrs = copy(attributeTypesToSchemaElements(attrsToAdd));
249    final Map<String, List<SomeSchemaElement>> mapClasses = copy(objectClassesToSchemaElements(ocsToAdd));
250    final Set<String> allFileNames = new LinkedHashSet<>(mapAttrs.keySet());
251    allFileNames.addAll(mapClasses.keySet());
252
253    for (String fileName : allFileNames)
254    {
255      List<AttributeType> attrs = schemaElementsToAttributeTypes(get(mapAttrs, fileName));
256      List<ObjectClass> ocs = schemaElementsToObjectClasses(get(mapClasses, fileName));
257
258      if ("".equals(fileName))
259      {
260        fileName = null;
261      }
262      updateSchemaOffline(fileName, attrs, ocs);
263      appendNewLinesToProgress();
264    }
265  }
266
267  private List<SomeSchemaElement> get(Map<String, List<SomeSchemaElement>> hmElems, String fileName)
268  {
269    List<SomeSchemaElement> elems = hmElems.get(fileName);
270    return elems != null ? elems : Collections.<SomeSchemaElement> emptyList();
271  }
272
273  private Map<String, List<SomeSchemaElement>> copy(Set<SomeSchemaElement> elemsToAdd)
274  {
275    Map<String, List<SomeSchemaElement>> hmElems = new LinkedHashMap<>();
276    for (SomeSchemaElement elem : elemsToAdd)
277    {
278      String fileName = elem.getSchemaFile();
279      if (fileName == null)
280      {
281        fileName = "";
282      }
283      List<SomeSchemaElement> elems = hmElems.get(fileName);
284      if (elems == null)
285      {
286        elems = new ArrayList<>();
287        hmElems.put(fileName, elems);
288      }
289      elems.add(elem);
290    }
291    return hmElems;
292  }
293
294  private void addAttributeOnline(final AttributeType attribute) throws OpenDsException
295  {
296    addSchemaElementOnline(new SomeSchemaElement(attribute),
297        INFO_CTRL_PANEL_CREATING_ATTRIBUTE_PROGRESS.get(attribute.getNameOrOID()));
298  }
299
300  private void addObjectClassOnline(final ObjectClass objectClass) throws OpenDsException
301  {
302    addSchemaElementOnline(new SomeSchemaElement(objectClass),
303        INFO_CTRL_PANEL_CREATING_OBJECTCLASS_PROGRESS.get(objectClass.getNameOrOID()));
304  }
305
306  private void addSchemaElementOnline(final SomeSchemaElement schemaElement, final LocalizableMessage progressMsg)
307      throws OpenDsException
308  {
309    SwingUtilities.invokeLater(new Runnable()
310    {
311      @Override
312      public void run()
313      {
314        printEquivalentCommandLineToAddOnline(schemaElement);
315        getProgressDialog().appendProgressHtml(
316            Utilities.getProgressWithPoints(progressMsg, ColorAndFontConstants.progressFont));
317      }
318    });
319    try
320    {
321      final BasicAttribute attr = new BasicAttribute(schemaElement.getAttributeName());
322      attr.add(getElementDefinition(schemaElement));
323      final ModificationItem mod = new ModificationItem(DirContext.ADD_ATTRIBUTE, attr);
324      getInfo().getConnection().getLdapContext().modifyAttributes(
325          ConfigConstants.DN_DEFAULT_SCHEMA_ROOT, new ModificationItem[] { mod });
326    }
327    catch (NamingException ne)
328    {
329      throw new OnlineUpdateException(ERR_CTRL_PANEL_ERROR_UPDATING_SCHEMA.get(ne), ne);
330    }
331    notifyConfigurationElementCreated(schemaElement);
332    SwingUtilities.invokeLater(new Runnable()
333    {
334      @Override
335      public void run()
336      {
337        getProgressDialog().appendProgressHtml(Utilities.getProgressDone(ColorAndFontConstants.progressFont));
338      }
339    });
340  }
341
342  private String getValueOffline(SomeSchemaElement element)
343  {
344    final Map<String, List<String>> props = element.getExtraProperties();
345    List<String> previousValues = props.get(ServerConstants.SCHEMA_PROPERTY_FILENAME);
346    element.setExtraPropertySingleValue(null, ServerConstants.SCHEMA_PROPERTY_FILENAME, null);
347    String attributeWithoutFileDefinition = getElementDefinition(element);
348
349    if (previousValues != null && !previousValues.isEmpty())
350    {
351      element.setExtraPropertyMultipleValues(null,
352          ServerConstants.SCHEMA_PROPERTY_FILENAME, new ArrayList<String>(previousValues));
353    }
354    return attributeWithoutFileDefinition;
355  }
356
357  private String getElementDefinition(SomeSchemaElement element)
358  {
359    final List<String> names = new ArrayList<>();
360    for (final String name : element.getNames())
361    {
362      names.add(StaticUtils.toLowerCase(name));
363    }
364    return element.isAttributeType()
365        ? getAttributeTypeDefinition(element.getAttributeType(), names)
366        : getObjectClassDefinition(element.getObjectClass(), names);
367  }
368
369  private String getAttributeTypeDefinition(final AttributeType attributeType, final List<String> names)
370  {
371    final StringBuilder buffer = new StringBuilder();
372    buffer.append("( ").append(attributeType.getOID());
373    appendCollection(buffer, "NAME", names);
374    appendDescription(buffer, attributeType.getDescription());
375    appendIfTrue(buffer, " OBSOLETE", attributeType.isObsolete());
376
377    final AttributeType superiorType = attributeType.getSuperiorType();
378    final String superiorTypeOID = superiorType != null ? superiorType.getOID() : null;
379    appendIfNotNull(buffer, " SUP ", superiorTypeOID);
380    addMatchingRuleIfNotNull(buffer, " EQUALITY ", attributeType.getEqualityMatchingRule());
381    addMatchingRuleIfNotNull(buffer, " ORDERING ", attributeType.getOrderingMatchingRule());
382    addMatchingRuleIfNotNull(buffer, " SUBSTR ", attributeType.getSubstringMatchingRule());
383    appendIfNotNull(buffer, " SYNTAX ", attributeType.getSyntax().getOID());
384    appendIfTrue(buffer, " SINGLE-VALUE", attributeType.isSingleValue());
385    appendIfTrue(buffer, " COLLECTIVE", attributeType.isCollective());
386    appendIfTrue(buffer, " NO-USER-MODIFICATION", attributeType.isNoUserModification());
387    appendIfNotNull(buffer, " USAGE ", attributeType.getUsage());
388
389    final MatchingRule approximateMatchingRule = attributeType.getApproximateMatchingRule();
390    if (approximateMatchingRule != null)
391    {
392      buffer.append(" ").append(ServerConstants.SCHEMA_PROPERTY_APPROX_RULE).append(" '")
393            .append(approximateMatchingRule.getOID()).append("'");
394    }
395    appendExtraProperties(buffer, attributeType.getExtraProperties());
396    buffer.append(")");
397
398    return buffer.toString();
399  }
400
401  private void addMatchingRuleIfNotNull(final StringBuilder buffer, final String label, final MatchingRule matchingRule)
402  {
403    if (matchingRule != null)
404    {
405      append(buffer, label, matchingRule.getOID());
406    }
407  }
408
409  private String getObjectClassDefinition(final ObjectClass objectClass, final List<String> names)
410  {
411    final StringBuilder buffer = new StringBuilder();
412    buffer.append("( ");
413    buffer.append(objectClass.getOID());
414    appendCollection(buffer, "NAME", names);
415    appendDescription(buffer, objectClass.getDescription());
416    appendIfTrue(buffer, " OBSOLETE", objectClass.isObsolete());
417    appendOIDs(buffer, "SUP", objectClassesToSchemaElements(objectClass.getSuperiorClasses()));
418    appendIfNotNull(buffer, " ", objectClass.getObjectClassType());
419    appendOIDs(buffer, "MUST", attributeTypesToSchemaElements(objectClass.getDeclaredRequiredAttributes()));
420    appendOIDs(buffer, "MAY", attributeTypesToSchemaElements(objectClass.getDeclaredOptionalAttributes()));
421    appendExtraProperties(buffer, objectClass.getExtraProperties());
422    buffer.append(")");
423
424    return buffer.toString();
425  }
426
427  private void appendOIDs(final StringBuilder buffer, final String label,
428      final Collection<SomeSchemaElement> schemaElements)
429  {
430    if (!schemaElements.isEmpty())
431    {
432      buffer.append(" ").append(label).append(" ( ");
433
434      final Iterator<SomeSchemaElement> it = schemaElements.iterator();
435      buffer.append(it.next().getOID());
436      while (it.hasNext())
437      {
438        buffer.append(" $ ").append(it.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 boolean isMultiValued = values.size() > 1;
527    if (!values.isEmpty())
528    {
529      buffer.append(" ").append(property);
530      buffer.append(isMultiValued ? " ( '" : " '");
531      final Iterator<String> it = values.iterator();
532      buffer.append(it.next()).append("' ");
533      while (it.hasNext())
534      {
535        buffer.append("'").append(it.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      Attribute attr = Attributes.create(schemaElement.getAttributeName(), getValueOffline(schemaElement));
747      schemaEntry.applyModification(new Modification(ADD, attr));
748    }
749  }
750
751  private void updateSchemaUndefinedFile(String schemaFile, List<SomeSchemaElement> schemaElements)
752      throws OfflineUpdateException
753  {
754    try (LDIFExportConfig exportConfig = new LDIFExportConfig(schemaFile, ExistingFileBehavior.FAIL))
755    {
756      List<String> lines = getSchemaEntryLines();
757      for (final SomeSchemaElement schemaElement : schemaElements)
758      {
759        lines.add(schemaElement.getAttributeName() + ": " + getValueOffline(schemaElement));
760      }
761      for (String line : lines)
762      {
763        final boolean wrapLines = exportConfig.getWrapColumn() > 1;
764        LDIFWriter.writeLDIFLine(
765            new StringBuilder(line), exportConfig.getWriter(), wrapLines, exportConfig.getWrapColumn());
766      }
767      exportConfig.getWriter().newLine();
768    }
769    catch (Throwable t)
770    {
771      throw new OfflineUpdateException(ERR_CTRL_PANEL_ERROR_UPDATING_SCHEMA.get(t), t);
772    }
773  }
774}