001    /*
002     * Copyright 2008-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2008-2015 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     */
021    package com.unboundid.ldap.sdk.examples;
022    
023    
024    
025    import java.io.File;
026    import java.io.FileInputStream;
027    import java.io.InputStream;
028    import java.io.IOException;
029    import java.io.OutputStream;
030    import java.util.ArrayList;
031    import java.util.Iterator;
032    import java.util.TreeMap;
033    import java.util.LinkedHashMap;
034    import java.util.List;
035    import java.util.concurrent.atomic.AtomicLong;
036    import java.util.zip.GZIPInputStream;
037    
038    import com.unboundid.ldap.sdk.Entry;
039    import com.unboundid.ldap.sdk.LDAPConnection;
040    import com.unboundid.ldap.sdk.LDAPException;
041    import com.unboundid.ldap.sdk.ResultCode;
042    import com.unboundid.ldap.sdk.Version;
043    import com.unboundid.ldap.sdk.schema.Schema;
044    import com.unboundid.ldap.sdk.schema.EntryValidator;
045    import com.unboundid.ldif.DuplicateValueBehavior;
046    import com.unboundid.ldif.LDIFException;
047    import com.unboundid.ldif.LDIFReader;
048    import com.unboundid.ldif.LDIFReaderEntryTranslator;
049    import com.unboundid.ldif.LDIFWriter;
050    import com.unboundid.util.LDAPCommandLineTool;
051    import com.unboundid.util.ThreadSafety;
052    import com.unboundid.util.ThreadSafetyLevel;
053    import com.unboundid.util.args.ArgumentException;
054    import com.unboundid.util.args.ArgumentParser;
055    import com.unboundid.util.args.BooleanArgument;
056    import com.unboundid.util.args.FileArgument;
057    import com.unboundid.util.args.IntegerArgument;
058    
059    import 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)
137    public 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    }