001/*
002 * Copyright 2008-2014 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2014 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.sdk.examples;
022
023
024
025import java.io.File;
026import java.io.FileInputStream;
027import java.io.InputStream;
028import java.io.IOException;
029import java.io.OutputStream;
030import java.util.ArrayList;
031import java.util.Iterator;
032import java.util.TreeMap;
033import java.util.LinkedHashMap;
034import java.util.List;
035import java.util.concurrent.atomic.AtomicLong;
036import java.util.zip.GZIPInputStream;
037
038import com.unboundid.ldap.sdk.Entry;
039import com.unboundid.ldap.sdk.LDAPConnection;
040import com.unboundid.ldap.sdk.LDAPException;
041import com.unboundid.ldap.sdk.ResultCode;
042import com.unboundid.ldap.sdk.Version;
043import com.unboundid.ldap.sdk.schema.Schema;
044import com.unboundid.ldap.sdk.schema.EntryValidator;
045import com.unboundid.ldif.DuplicateValueBehavior;
046import com.unboundid.ldif.LDIFException;
047import com.unboundid.ldif.LDIFReader;
048import com.unboundid.ldif.LDIFReaderEntryTranslator;
049import com.unboundid.ldif.LDIFWriter;
050import com.unboundid.util.LDAPCommandLineTool;
051import com.unboundid.util.ThreadSafety;
052import com.unboundid.util.ThreadSafetyLevel;
053import com.unboundid.util.args.ArgumentException;
054import com.unboundid.util.args.ArgumentParser;
055import com.unboundid.util.args.BooleanArgument;
056import com.unboundid.util.args.FileArgument;
057import com.unboundid.util.args.IntegerArgument;
058
059import static com.unboundid.util.StaticUtils.*;
060
061
062
063/**
064 * This class provides a simple tool that can be used to validate that the
065 * contents of an LDIF file are valid.  This includes ensuring that the contents
066 * can be parsed as valid LDIF, and it can also ensure that the LDIF content
067 * conforms to the server schema.  It will obtain the schema by connecting to
068 * the server and retrieving the default schema (i.e., the schema which governs
069 * the root DSE).  By default, a thorough set of validation will be performed,
070 * but it is possible to disable certain types of validation.
071 * <BR><BR>
072 * Some of the APIs demonstrated by this example include:
073 * <UL>
074 *   <LI>Argument Parsing (from the {@code com.unboundid.util.args}
075 *       package)</LI>
076 *   <LI>LDAP Command-Line Tool (from the {@code com.unboundid.util}
077 *       package)</LI>
078 *   <LI>LDIF Processing (from the {@code com.unboundid.ldif} package)</LI>
079 *   <LI>Schema Parsing (from the {@code com.unboundid.ldap.sdk.schema}
080 *       package)</LI>
081 * </UL>
082 * <BR><BR>
083 * Supported arguments include those allowed by the {@link LDAPCommandLineTool}
084 * class (to obtain the information to use to connect to the server to read the
085 * schema), as well as the following additional arguments:
086 * <UL>
087 *   <LI>"--schemaDirectory {path}" -- specifies the path to a directory
088 *       containing files with schema definitions.  If this argument is
089 *       provided, then no attempt will be made to communicate with a directory
090 *       server.</LI>
091 *   <LI>"-f {path}" or "--ldifFile {path}" -- specifies the path to the LDIF
092 *       file to be validated.</LI>
093 *   <LI>"-c" or "--isCompressed" -- indicates that the LDIF file is
094 *       compressed.</LI>
095 *   <LI>"-R {path}" or "--rejectFile {path}" -- specifies the path to the file
096 *       to be written with information about all entries that failed
097 *       validation.</LI>
098 *   <LI>"-t {num}" or "--numThreads {num}" -- specifies the number of
099 *       concurrent threads to use when processing the LDIF.  If this is not
100 *       provided, then a default of one thread will be used.</LI>
101 *   <LI>"--ignoreUndefinedObjectClasses" -- indicates that the validation
102 *       process should ignore validation failures due to entries that contain
103 *       object classes not defined in the server schema.</LI>
104 *   <LI>"--ignoreUndefinedAttributes" -- indicates that the validation process
105 *       should ignore validation failures due to entries that contain
106 *       attributes not defined in the server schema.</LI>
107 *   <LI>"--ignoreMalformedDNs" -- indicates that the validation process should
108 *       ignore validation failures due to entries with malformed DNs.</LI>
109 *   <LI>"--ignoreStructuralObjectClasses" -- indicates that the validation
110 *       process should ignore validation failures due to entries that either do
111 *       not have a structural object class or that have multiple structural
112 *       object classes.</LI>
113 *   <LI>"--ignoreProhibitedObjectClasses" -- indicates that the validation
114 *       process should ignore validation failures due to entries containing
115 *       auxiliary classes that are not allowed by a DIT content rule, or
116 *       abstract classes that are not subclassed by an auxiliary or structural
117 *       class contained in the entry.</LI>
118 *   <LI>"--ignoreProhibitedAttributes" -- indicates that the validation process
119 *       should ignore validation failures due to entries including attributes
120 *       that are not allowed or are explicitly prohibited by a DIT content
121 *       rule.</LI>
122 *   <LI>"--ignoreMissingAttributes" -- indicates that the validation process
123 *       should ignore validation failures due to entries missing required
124 *       attributes.</LI>
125 *   <LI>"--ignoreSingleValuedAttributes" -- indicates that the validation
126 *       process should ignore validation failures due to single-valued
127 *       attributes containing multiple values.</LI>
128 *   <LI>"--ignoreAttributeSyntax" -- indicates that the validation process
129 *       should ignore validation failures due to attribute values which violate
130 *       the associated attribute syntax.</LI>
131 *   <LI>"--ignoreNameForms" -- indicates that the validation process should
132 *       ignore validation failures due to name form violations (in which the
133 *       entry's RDN does not comply with the associated name form).</LI>
134 * </UL>
135 */
136@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
137public final class ValidateLDIF
138       extends LDAPCommandLineTool
139       implements LDIFReaderEntryTranslator
140{
141  /**
142   * The end-of-line character for this platform.
143   */
144  private static final String EOL = System.getProperty("line.separator", "\n");
145
146
147
148  // The arguments used by this program.
149  private BooleanArgument ignoreDuplicateValues;
150  private BooleanArgument ignoreUndefinedObjectClasses;
151  private BooleanArgument ignoreUndefinedAttributes;
152  private BooleanArgument ignoreMalformedDNs;
153  private BooleanArgument ignoreMissingSuperiorObjectClasses;
154  private BooleanArgument ignoreStructuralObjectClasses;
155  private BooleanArgument ignoreProhibitedObjectClasses;
156  private BooleanArgument ignoreProhibitedAttributes;
157  private BooleanArgument ignoreMissingAttributes;
158  private BooleanArgument ignoreSingleValuedAttributes;
159  private BooleanArgument ignoreAttributeSyntax;
160  private BooleanArgument ignoreNameForms;
161  private BooleanArgument isCompressed;
162  private FileArgument    schemaDirectory;
163  private FileArgument    ldifFile;
164  private FileArgument    rejectFile;
165  private IntegerArgument numThreads;
166
167  // The counter used to keep track of the number of entries processed.
168  private final AtomicLong entriesProcessed = new AtomicLong(0L);
169
170  // The counter used to keep track of the number of entries that could not be
171  // parsed as valid entries.
172  private final AtomicLong malformedEntries = new AtomicLong(0L);
173
174  // The entry validator that will be used to validate the entries.
175  private EntryValidator entryValidator;
176
177  // The LDIF writer that will be used to write rejected entries.
178  private LDIFWriter rejectWriter;
179
180
181
182  /**
183   * Parse the provided command line arguments and make the appropriate set of
184   * changes.
185   *
186   * @param  args  The command line arguments provided to this program.
187   */
188  public static void main(final String[] args)
189  {
190    final ResultCode resultCode = main(args, System.out, System.err);
191    if (resultCode != ResultCode.SUCCESS)
192    {
193      System.exit(resultCode.intValue());
194    }
195  }
196
197
198
199  /**
200   * Parse the provided command line arguments and make the appropriate set of
201   * changes.
202   *
203   * @param  args       The command line arguments provided to this program.
204   * @param  outStream  The output stream to which standard out should be
205   *                    written.  It may be {@code null} if output should be
206   *                    suppressed.
207   * @param  errStream  The output stream to which standard error should be
208   *                    written.  It may be {@code null} if error messages
209   *                    should be suppressed.
210   *
211   * @return  A result code indicating whether the processing was successful.
212   */
213  public static ResultCode main(final String[] args,
214                                final OutputStream outStream,
215                                final OutputStream errStream)
216  {
217    final ValidateLDIF validateLDIF = new ValidateLDIF(outStream, errStream);
218    return validateLDIF.runTool(args);
219  }
220
221
222
223  /**
224   * Creates a new instance of this tool.
225   *
226   * @param  outStream  The output stream to which standard out should be
227   *                    written.  It may be {@code null} if output should be
228   *                    suppressed.
229   * @param  errStream  The output stream to which standard error should be
230   *                    written.  It may be {@code null} if error messages
231   *                    should be suppressed.
232   */
233  public ValidateLDIF(final OutputStream outStream,
234                      final OutputStream errStream)
235  {
236    super(outStream, errStream);
237  }
238
239
240
241  /**
242   * Retrieves the name for this tool.
243   *
244   * @return  The name for this tool.
245   */
246  @Override()
247  public String getToolName()
248  {
249    return "validate-ldif";
250  }
251
252
253
254  /**
255   * Retrieves the description for this tool.
256   *
257   * @return  The description for this tool.
258   */
259  @Override()
260  public String getToolDescription()
261  {
262    return "Validate the contents of an LDIF file " +
263           "against the server schema.";
264  }
265
266
267
268  /**
269   * Retrieves the version string for this tool.
270   *
271   * @return  The version string for this tool.
272   */
273  @Override()
274  public String getToolVersion()
275  {
276    return Version.NUMERIC_VERSION_STRING;
277  }
278
279
280
281  /**
282   * Adds the arguments used by this program that aren't already provided by the
283   * generic {@code LDAPCommandLineTool} framework.
284   *
285   * @param  parser  The argument parser to which the arguments should be added.
286   *
287   * @throws  ArgumentException  If a problem occurs while adding the arguments.
288   */
289  @Override()
290  public void addNonLDAPArguments(final ArgumentParser parser)
291         throws ArgumentException
292  {
293    String description = "The path to the LDIF file to process.";
294    ldifFile = new FileArgument('f', "ldifFile", true, 1, "{path}", description,
295                                true, true, true, false);
296    parser.addArgument(ldifFile);
297
298    description = "Indicates that the specified LDIF file is compressed " +
299                  "using gzip compression.";
300    isCompressed = new BooleanArgument('c', "isCompressed", description);
301    parser.addArgument(isCompressed);
302
303    description = "The path to the file to which rejected entries should be " +
304                  "written.";
305    rejectFile = new FileArgument('R', "rejectFile", false, 1, "{path}",
306                                  description, false, true, true, false);
307    parser.addArgument(rejectFile);
308
309    description = "The path to a directory containing one or more LDIF files " +
310                  "with the schema information to use.  If this is provided, " +
311                  "then no LDAP communication will be performed.";
312    schemaDirectory = new FileArgument(null, "schemaDirectory", false, 1,
313         "{path}", description, true, true, false, true);
314    parser.addArgument(schemaDirectory);
315
316    description = "The number of threads to use when processing the LDIF file.";
317    numThreads = new IntegerArgument('t', "numThreads", true, 1, "{num}",
318         description, 1, Integer.MAX_VALUE, 1);
319    parser.addArgument(numThreads);
320
321    description = "Ignore validation failures due to entries containing " +
322                  "duplicate values for the same attribute.";
323    ignoreDuplicateValues =
324         new BooleanArgument(null, "ignoreDuplicateValues", description);
325    parser.addArgument(ignoreDuplicateValues);
326
327    description = "Ignore validation failures due to object classes not " +
328                  "defined in the schema.";
329    ignoreUndefinedObjectClasses =
330         new BooleanArgument(null, "ignoreUndefinedObjectClasses", description);
331    parser.addArgument(ignoreUndefinedObjectClasses);
332
333    description = "Ignore validation failures due to attributes not defined " +
334                  "in the schema.";
335    ignoreUndefinedAttributes =
336         new BooleanArgument(null, "ignoreUndefinedAttributes", description);
337    parser.addArgument(ignoreUndefinedAttributes);
338
339    description = "Ignore validation failures due to entries with malformed " +
340                  "DNs.";
341    ignoreMalformedDNs =
342         new BooleanArgument(null, "ignoreMalformedDNs", description);
343    parser.addArgument(ignoreMalformedDNs);
344
345    description = "Ignore validation failures due to entries without exactly " +
346                  "structural object class.";
347    ignoreStructuralObjectClasses =
348         new BooleanArgument(null, "ignoreStructuralObjectClasses",
349                             description);
350    parser.addArgument(ignoreStructuralObjectClasses);
351
352    description = "Ignore validation failures due to entries with object " +
353                  "classes that are not allowed.";
354    ignoreProhibitedObjectClasses =
355         new BooleanArgument(null, "ignoreProhibitedObjectClasses",
356                             description);
357    parser.addArgument(ignoreProhibitedObjectClasses);
358
359    description = "Ignore validation failures due to entries that are " +
360                  "one or more superior object classes.";
361    ignoreMissingSuperiorObjectClasses =
362         new BooleanArgument(null, "ignoreMissingSuperiorObjectClasses",
363              description);
364    parser.addArgument(ignoreMissingSuperiorObjectClasses);
365
366    description = "Ignore validation failures due to entries with attributes " +
367                  "that are not allowed.";
368    ignoreProhibitedAttributes =
369         new BooleanArgument(null, "ignoreProhibitedAttributes", description);
370    parser.addArgument(ignoreProhibitedAttributes);
371
372    description = "Ignore validation failures due to entries missing " +
373                  "required attributes.";
374    ignoreMissingAttributes =
375         new BooleanArgument(null, "ignoreMissingAttributes", description);
376    parser.addArgument(ignoreMissingAttributes);
377
378    description = "Ignore validation failures due to entries with multiple " +
379                  "values for single-valued attributes.";
380    ignoreSingleValuedAttributes =
381         new BooleanArgument(null, "ignoreSingleValuedAttributes", description);
382    parser.addArgument(ignoreSingleValuedAttributes);
383
384    description = "Ignore validation failures due to entries with attribute " +
385                  "values that violate their associated syntax.";
386    ignoreAttributeSyntax =
387         new BooleanArgument(null, "ignoreAttributeSyntax", description);
388    parser.addArgument(ignoreAttributeSyntax);
389
390    description = "Ignore validation failures due to entries with RDNs " +
391                  "that violate the associated name form definition.";
392    ignoreNameForms = new BooleanArgument(null, "ignoreNameForms", description);
393    parser.addArgument(ignoreNameForms);
394  }
395
396
397
398  /**
399   * Performs the actual processing for this tool.  In this case, it gets a
400   * connection to the directory server and uses it to retrieve the server
401   * schema.  It then reads the LDIF file and validates each entry accordingly.
402   *
403   * @return  The result code for the processing that was performed.
404   */
405  @Override()
406  public ResultCode doToolProcessing()
407  {
408    // Get the connection to the directory server and use it to read the schema.
409    final Schema schema;
410    if (schemaDirectory.isPresent())
411    {
412      final File schemaDir = schemaDirectory.getValue();
413
414      try
415      {
416        final TreeMap<String,File> fileMap = new TreeMap<String,File>();
417        for (final File f : schemaDir.listFiles())
418        {
419          final String name = f.getName();
420          if (f.isFile() && name.endsWith(".ldif"))
421          {
422            fileMap.put(name, f);
423          }
424        }
425
426        if (fileMap.isEmpty())
427        {
428          err("No LDIF files found in directory " +
429              schemaDir.getAbsolutePath());
430          return ResultCode.PARAM_ERROR;
431        }
432
433        final ArrayList<File> fileList = new ArrayList<File>(fileMap.values());
434        schema = Schema.getSchema(fileList);
435      }
436      catch (Exception e)
437      {
438        err("Unable to read schema from files in directory " +
439            schemaDir.getAbsolutePath() + ":  " + getExceptionMessage(e));
440        return ResultCode.LOCAL_ERROR;
441      }
442    }
443    else
444    {
445      try
446      {
447        final LDAPConnection connection = getConnection();
448        schema = connection.getSchema();
449        connection.close();
450      }
451      catch (LDAPException le)
452      {
453        err("Unable to connect to the directory server and read the schema:  ",
454            le.getMessage());
455        return le.getResultCode();
456      }
457    }
458
459
460    // Create the entry validator and initialize its configuration.
461    entryValidator = new EntryValidator(schema);
462    entryValidator.setCheckAttributeSyntax(!ignoreAttributeSyntax.isPresent());
463    entryValidator.setCheckMalformedDNs(!ignoreMalformedDNs.isPresent());
464    entryValidator.setCheckMissingAttributes(
465         !ignoreMissingAttributes.isPresent());
466    entryValidator.setCheckNameForms(!ignoreNameForms.isPresent());
467    entryValidator.setCheckProhibitedAttributes(
468         !ignoreProhibitedAttributes.isPresent());
469    entryValidator.setCheckProhibitedObjectClasses(
470         !ignoreProhibitedObjectClasses.isPresent());
471    entryValidator.setCheckMissingSuperiorObjectClasses(
472         !ignoreMissingSuperiorObjectClasses.isPresent());
473    entryValidator.setCheckSingleValuedAttributes(
474         !ignoreSingleValuedAttributes.isPresent());
475    entryValidator.setCheckStructuralObjectClasses(
476         !ignoreStructuralObjectClasses.isPresent());
477    entryValidator.setCheckUndefinedAttributes(
478         !ignoreUndefinedAttributes.isPresent());
479    entryValidator.setCheckUndefinedObjectClasses(
480         !ignoreUndefinedObjectClasses.isPresent());
481
482
483    // Create an LDIF reader that can be used to read through the LDIF file.
484    final LDIFReader ldifReader;
485    rejectWriter = null;
486    try
487    {
488      InputStream inputStream = new FileInputStream(ldifFile.getValue());
489      if (isCompressed.isPresent())
490      {
491        inputStream = new GZIPInputStream(inputStream);
492      }
493      ldifReader = new LDIFReader(inputStream, numThreads.getValue(), this);
494    }
495    catch (Exception e)
496    {
497      err("Unable to open the LDIF reader:  ", getExceptionMessage(e));
498      return ResultCode.LOCAL_ERROR;
499    }
500
501    ldifReader.setSchema(schema);
502    if (ignoreDuplicateValues.isPresent())
503    {
504      ldifReader.setDuplicateValueBehavior(DuplicateValueBehavior.STRIP);
505    }
506    else
507    {
508      ldifReader.setDuplicateValueBehavior(DuplicateValueBehavior.REJECT);
509    }
510
511    try
512    {
513      // Create an LDIF writer that can be used to write information about
514      // rejected entries.
515      try
516      {
517        if (rejectFile.isPresent())
518        {
519          rejectWriter = new LDIFWriter(rejectFile.getValue());
520        }
521      }
522      catch (Exception e)
523      {
524        err("Unable to create the reject writer:  ", getExceptionMessage(e));
525        return ResultCode.LOCAL_ERROR;
526      }
527
528      ResultCode resultCode = ResultCode.SUCCESS;
529      while (true)
530      {
531        try
532        {
533          final Entry e = ldifReader.readEntry();
534          if (e == null)
535          {
536            // Because we're performing parallel processing and returning null
537            // from the translate method, LDIFReader.readEntry() should never
538            // return a non-null value.  However, it can throw an LDIFException
539            // if it encounters an invalid entry, or an IOException if there's
540            // a problem reading from the file, so we should still iterate
541            // through all of the entries to catch and report on those problems.
542            break;
543          }
544        }
545        catch (LDIFException le)
546        {
547          malformedEntries.incrementAndGet();
548
549          if (resultCode == ResultCode.SUCCESS)
550          {
551            resultCode = ResultCode.DECODING_ERROR;
552          }
553
554          if (rejectWriter != null)
555          {
556            try
557            {
558              rejectWriter.writeComment(
559                   "Unable to parse an entry read from LDIF:", false, false);
560              if (le.mayContinueReading())
561              {
562                rejectWriter.writeComment(getExceptionMessage(le), false, true);
563              }
564              else
565              {
566                rejectWriter.writeComment(getExceptionMessage(le), false,
567                                          false);
568                rejectWriter.writeComment("Unable to continue LDIF processing.",
569                                          false, true);
570                err("Aborting LDIF processing:  ", getExceptionMessage(le));
571                return ResultCode.LOCAL_ERROR;
572              }
573            }
574            catch (IOException ioe)
575            {
576              err("Unable to write to the reject file:",
577                  getExceptionMessage(ioe));
578              err("LDIF parse failure that triggered the rejection:  ",
579                  getExceptionMessage(le));
580              return ResultCode.LOCAL_ERROR;
581            }
582          }
583        }
584        catch (IOException ioe)
585        {
586
587          if (rejectWriter != null)
588          {
589            try
590            {
591              rejectWriter.writeComment("I/O error reading from LDIF:", false,
592                                        false);
593              rejectWriter.writeComment(getExceptionMessage(ioe), false,
594                                        true);
595              return ResultCode.LOCAL_ERROR;
596            }
597            catch (Exception ex)
598            {
599              err("I/O error reading from LDIF:", getExceptionMessage(ioe));
600              return ResultCode.LOCAL_ERROR;
601            }
602          }
603        }
604      }
605
606      if (malformedEntries.get() > 0)
607      {
608        out(malformedEntries.get() + " entries were malformed and could not " +
609            "be read from the LDIF file.");
610      }
611
612      if (entryValidator.getInvalidEntries() > 0)
613      {
614        if (resultCode == ResultCode.SUCCESS)
615        {
616          resultCode = ResultCode.OBJECT_CLASS_VIOLATION;
617        }
618
619        for (final String s : entryValidator.getInvalidEntrySummary(true))
620        {
621          out(s);
622        }
623      }
624      else
625      {
626        if (malformedEntries.get() == 0)
627        {
628          out("No errors were encountered.");
629        }
630      }
631
632      return resultCode;
633    }
634    finally
635    {
636      try
637      {
638        ldifReader.close();
639      }
640      catch (Exception e) {}
641
642      try
643      {
644        if (rejectWriter != null)
645        {
646          rejectWriter.close();
647        }
648      }
649      catch (Exception e) {}
650    }
651  }
652
653
654
655  /**
656   * Examines the provided entry to determine whether it conforms to the
657   * server schema.
658   *
659   * @param  entry           The entry to be examined.
660   * @param  firstLineNumber The line number of the LDIF source on which the
661   *                         provided entry begins.
662   *
663   * @return  The updated entry.  This method will always return {@code null}
664   *          because all of the real processing needed for the entry is
665   *          performed in this method and the entry isn't needed any more
666   *          after this method is done.
667   */
668  public Entry translate(final Entry entry, final long firstLineNumber)
669  {
670    final ArrayList<String> invalidReasons = new ArrayList<String>(5);
671    if (! entryValidator.entryIsValid(entry, invalidReasons))
672    {
673      if (rejectWriter != null)
674      {
675        synchronized (this)
676        {
677          try
678          {
679            rejectWriter.writeEntry(entry, listToString(invalidReasons));
680          }
681          catch (IOException ioe) {}
682        }
683      }
684    }
685
686    final long numEntries = entriesProcessed.incrementAndGet();
687    if ((numEntries % 1000L) == 0L)
688    {
689      out("Processed ", numEntries, " entries.");
690    }
691
692    return null;
693  }
694
695
696
697  /**
698   * Converts the provided list of strings into a single string.  It will
699   * contain line breaks after all but the last element.
700   *
701   * @param  l  The list of strings to convert to a single string.
702   *
703   * @return  The string from the provided list, or {@code null} if the provided
704   *          list is empty or {@code null}.
705   */
706  private static String listToString(final List<String> l)
707  {
708    if ((l == null) || (l.isEmpty()))
709    {
710      return null;
711    }
712
713    final StringBuilder buffer = new StringBuilder();
714    final Iterator<String> iterator = l.iterator();
715    while (iterator.hasNext())
716    {
717      buffer.append(iterator.next());
718      if (iterator.hasNext())
719      {
720        buffer.append(EOL);
721      }
722    }
723
724    return buffer.toString();
725  }
726
727
728
729  /**
730   * {@inheritDoc}
731   */
732  @Override()
733  public LinkedHashMap<String[],String> getExampleUsages()
734  {
735    final LinkedHashMap<String[],String> examples =
736         new LinkedHashMap<String[],String>(2);
737
738    String[] args =
739    {
740      "--hostname", "server.example.com",
741      "--port", "389",
742      "--ldifFile", "data.ldif",
743      "--rejectFile", "rejects.ldif",
744      "--numThreads", "4"
745    };
746    String description =
747         "Validate the contents of the 'data.ldif' file using the schema " +
748         "defined in the specified directory server using four concurrent " +
749         "threads.  All types of validation will be performed, and " +
750         "information about any errors will be written to the 'rejects.ldif' " +
751         "file.";
752    examples.put(args, description);
753
754
755    args = new String[]
756    {
757      "--schemaDirectory", "/ds/config/schema",
758      "--ldifFile", "data.ldif",
759      "--rejectFile", "rejects.ldif",
760      "--ignoreStructuralObjectClasses",
761      "--ignoreAttributeSyntax"
762    };
763    description =
764         "Validate the contents of the 'data.ldif' file using the schema " +
765         "defined in LDIF files contained in the /ds/config/schema directory " +
766         "using a single thread.  Any errors resulting from entries that do " +
767         "not have exactly one structural object class or from values which " +
768         "violate the syntax for their associated attribute types will be " +
769         "ignored.  Information about any other failures will be written to " +
770         "the 'rejects.ldif' file.";
771    examples.put(args, description);
772
773    return examples;
774  }
775
776
777
778  /**
779   * @return EntryValidator
780   *
781   * Returns the EntryValidator
782   */
783  public EntryValidator getEntryValidator()
784  {
785    return entryValidator;
786  }
787}