001 /*
002 * Copyright 2008-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2016 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 * Indicates whether this tool should provide support for an interactive mode,
283 * in which the tool offers a mode in which the arguments can be provided in
284 * a text-driven menu rather than requiring them to be given on the command
285 * line. If interactive mode is supported, it may be invoked using the
286 * "--interactive" argument. Alternately, if interactive mode is supported
287 * and {@link #defaultsToInteractiveMode()} returns {@code true}, then
288 * interactive mode may be invoked by simply launching the tool without any
289 * arguments.
290 *
291 * @return {@code true} if this tool supports interactive mode, or
292 * {@code false} if not.
293 */
294 @Override()
295 public boolean supportsInteractiveMode()
296 {
297 return true;
298 }
299
300
301
302 /**
303 * Indicates whether this tool defaults to launching in interactive mode if
304 * the tool is invoked without any command-line arguments. This will only be
305 * used if {@link #supportsInteractiveMode()} returns {@code true}.
306 *
307 * @return {@code true} if this tool defaults to using interactive mode if
308 * launched without any command-line arguments, or {@code false} if
309 * not.
310 */
311 @Override()
312 public boolean defaultsToInteractiveMode()
313 {
314 return true;
315 }
316
317
318
319 /**
320 * Indicates whether this tool supports the use of a properties file for
321 * specifying default values for arguments that aren't specified on the
322 * command line.
323 *
324 * @return {@code true} if this tool supports the use of a properties file
325 * for specifying default values for arguments that aren't specified
326 * on the command line, or {@code false} if not.
327 */
328 @Override()
329 public boolean supportsPropertiesFile()
330 {
331 return true;
332 }
333
334
335
336 /**
337 * Indicates whether the LDAP-specific arguments should include alternate
338 * versions of all long identifiers that consist of multiple words so that
339 * they are available in both camelCase and dash-separated versions.
340 *
341 * @return {@code true} if this tool should provide multiple versions of
342 * long identifiers for LDAP-specific arguments, or {@code false} if
343 * not.
344 */
345 @Override()
346 protected boolean includeAlternateLongIdentifiers()
347 {
348 return true;
349 }
350
351
352
353 /**
354 * Adds the arguments used by this program that aren't already provided by the
355 * generic {@code LDAPCommandLineTool} framework.
356 *
357 * @param parser The argument parser to which the arguments should be added.
358 *
359 * @throws ArgumentException If a problem occurs while adding the arguments.
360 */
361 @Override()
362 public void addNonLDAPArguments(final ArgumentParser parser)
363 throws ArgumentException
364 {
365 String description = "The path to the LDIF file to process.";
366 ldifFile = new FileArgument('f', "ldifFile", true, 1, "{path}", description,
367 true, true, true, false);
368 ldifFile.addLongIdentifier("ldif-file");
369 parser.addArgument(ldifFile);
370
371 description = "Indicates that the specified LDIF file is compressed " +
372 "using gzip compression.";
373 isCompressed = new BooleanArgument('c', "isCompressed", description);
374 isCompressed.addLongIdentifier("is-compressed");
375 parser.addArgument(isCompressed);
376
377 description = "The path to the file to which rejected entries should be " +
378 "written.";
379 rejectFile = new FileArgument('R', "rejectFile", false, 1, "{path}",
380 description, false, true, true, false);
381 rejectFile.addLongIdentifier("reject-file");
382 parser.addArgument(rejectFile);
383
384 description = "The path to a directory containing one or more LDIF files " +
385 "with the schema information to use. If this is provided, " +
386 "then no LDAP communication will be performed.";
387 schemaDirectory = new FileArgument(null, "schemaDirectory", false, 1,
388 "{path}", description, true, true, false, true);
389 schemaDirectory.addLongIdentifier("schema-directory");
390 parser.addArgument(schemaDirectory);
391
392 description = "The number of threads to use when processing the LDIF file.";
393 numThreads = new IntegerArgument('t', "numThreads", true, 1, "{num}",
394 description, 1, Integer.MAX_VALUE, 1);
395 numThreads.addLongIdentifier("num-threads");
396 parser.addArgument(numThreads);
397
398 description = "Ignore validation failures due to entries containing " +
399 "duplicate values for the same attribute.";
400 ignoreDuplicateValues =
401 new BooleanArgument(null, "ignoreDuplicateValues", description);
402 ignoreDuplicateValues.setArgumentGroupName(
403 "Validation Strictness Arguments");
404 ignoreDuplicateValues.addLongIdentifier("ignore-duplicate-values");
405 parser.addArgument(ignoreDuplicateValues);
406
407 description = "Ignore validation failures due to object classes not " +
408 "defined in the schema.";
409 ignoreUndefinedObjectClasses =
410 new BooleanArgument(null, "ignoreUndefinedObjectClasses", description);
411 ignoreUndefinedObjectClasses.setArgumentGroupName(
412 "Validation Strictness Arguments");
413 ignoreUndefinedObjectClasses.addLongIdentifier(
414 "ignore-undefined-object-classes");
415 parser.addArgument(ignoreUndefinedObjectClasses);
416
417 description = "Ignore validation failures due to attributes not defined " +
418 "in the schema.";
419 ignoreUndefinedAttributes =
420 new BooleanArgument(null, "ignoreUndefinedAttributes", description);
421 ignoreUndefinedAttributes.setArgumentGroupName(
422 "Validation Strictness Arguments");
423 ignoreUndefinedAttributes.addLongIdentifier("ignore-undefined-attributes");
424 parser.addArgument(ignoreUndefinedAttributes);
425
426 description = "Ignore validation failures due to entries with malformed " +
427 "DNs.";
428 ignoreMalformedDNs =
429 new BooleanArgument(null, "ignoreMalformedDNs", description);
430 ignoreMalformedDNs.setArgumentGroupName("Validation Strictness Arguments");
431 ignoreMalformedDNs.addLongIdentifier("ignore-malformed-dns");
432 parser.addArgument(ignoreMalformedDNs);
433
434 description = "Ignore validation failures due to entries without exactly " +
435 "structural object class.";
436 ignoreStructuralObjectClasses =
437 new BooleanArgument(null, "ignoreStructuralObjectClasses",
438 description);
439 ignoreStructuralObjectClasses.setArgumentGroupName(
440 "Validation Strictness Arguments");
441 ignoreStructuralObjectClasses.addLongIdentifier(
442 "ignore-structural-object-classes");
443 parser.addArgument(ignoreStructuralObjectClasses);
444
445 description = "Ignore validation failures due to entries with object " +
446 "classes that are not allowed.";
447 ignoreProhibitedObjectClasses =
448 new BooleanArgument(null, "ignoreProhibitedObjectClasses",
449 description);
450 ignoreProhibitedObjectClasses.setArgumentGroupName(
451 "Validation Strictness Arguments");
452 ignoreProhibitedObjectClasses.addLongIdentifier(
453 "ignore-prohibited-object-classes");
454 parser.addArgument(ignoreProhibitedObjectClasses);
455
456 description = "Ignore validation failures due to entries that are " +
457 "one or more superior object classes.";
458 ignoreMissingSuperiorObjectClasses =
459 new BooleanArgument(null, "ignoreMissingSuperiorObjectClasses",
460 description);
461 ignoreMissingSuperiorObjectClasses.setArgumentGroupName(
462 "Validation Strictness Arguments");
463 ignoreMissingSuperiorObjectClasses.addLongIdentifier(
464 "ignore-missing-superior-object-classes");
465 parser.addArgument(ignoreMissingSuperiorObjectClasses);
466
467 description = "Ignore validation failures due to entries with attributes " +
468 "that are not allowed.";
469 ignoreProhibitedAttributes =
470 new BooleanArgument(null, "ignoreProhibitedAttributes", description);
471 ignoreProhibitedAttributes.setArgumentGroupName(
472 "Validation Strictness Arguments");
473 ignoreProhibitedAttributes.addLongIdentifier(
474 "ignore-prohibited-attributes");
475 parser.addArgument(ignoreProhibitedAttributes);
476
477 description = "Ignore validation failures due to entries missing " +
478 "required attributes.";
479 ignoreMissingAttributes =
480 new BooleanArgument(null, "ignoreMissingAttributes", description);
481 ignoreMissingAttributes.setArgumentGroupName(
482 "Validation Strictness Arguments");
483 ignoreMissingAttributes.addLongIdentifier("ignore-missing-attributes");
484 parser.addArgument(ignoreMissingAttributes);
485
486 description = "Ignore validation failures due to entries with multiple " +
487 "values for single-valued attributes.";
488 ignoreSingleValuedAttributes =
489 new BooleanArgument(null, "ignoreSingleValuedAttributes", description);
490 ignoreSingleValuedAttributes.setArgumentGroupName(
491 "Validation Strictness Arguments");
492 ignoreSingleValuedAttributes.addLongIdentifier(
493 "ignore-single-valued-attributes");
494 parser.addArgument(ignoreSingleValuedAttributes);
495
496 description = "Ignore validation failures due to entries with attribute " +
497 "values that violate their associated syntax.";
498 ignoreAttributeSyntax =
499 new BooleanArgument(null, "ignoreAttributeSyntax", description);
500 ignoreAttributeSyntax.setArgumentGroupName(
501 "Validation Strictness Arguments");
502 ignoreAttributeSyntax.addLongIdentifier("ignore-attribute-syntax");
503 parser.addArgument(ignoreAttributeSyntax);
504
505 description = "Ignore validation failures due to entries with RDNs " +
506 "that violate the associated name form definition.";
507 ignoreNameForms = new BooleanArgument(null, "ignoreNameForms", description);
508 ignoreNameForms.setArgumentGroupName("Validation Strictness Arguments");
509 ignoreNameForms.addLongIdentifier("ignore-name-forms");
510 parser.addArgument(ignoreNameForms);
511 }
512
513
514
515 /**
516 * Performs the actual processing for this tool. In this case, it gets a
517 * connection to the directory server and uses it to retrieve the server
518 * schema. It then reads the LDIF file and validates each entry accordingly.
519 *
520 * @return The result code for the processing that was performed.
521 */
522 @Override()
523 public ResultCode doToolProcessing()
524 {
525 // Get the connection to the directory server and use it to read the schema.
526 final Schema schema;
527 if (schemaDirectory.isPresent())
528 {
529 final File schemaDir = schemaDirectory.getValue();
530
531 try
532 {
533 final TreeMap<String,File> fileMap = new TreeMap<String,File>();
534 for (final File f : schemaDir.listFiles())
535 {
536 final String name = f.getName();
537 if (f.isFile() && name.endsWith(".ldif"))
538 {
539 fileMap.put(name, f);
540 }
541 }
542
543 if (fileMap.isEmpty())
544 {
545 err("No LDIF files found in directory " +
546 schemaDir.getAbsolutePath());
547 return ResultCode.PARAM_ERROR;
548 }
549
550 final ArrayList<File> fileList = new ArrayList<File>(fileMap.values());
551 schema = Schema.getSchema(fileList);
552 }
553 catch (Exception e)
554 {
555 err("Unable to read schema from files in directory " +
556 schemaDir.getAbsolutePath() + ": " + getExceptionMessage(e));
557 return ResultCode.LOCAL_ERROR;
558 }
559 }
560 else
561 {
562 try
563 {
564 final LDAPConnection connection = getConnection();
565 schema = connection.getSchema();
566 connection.close();
567 }
568 catch (LDAPException le)
569 {
570 err("Unable to connect to the directory server and read the schema: ",
571 le.getMessage());
572 return le.getResultCode();
573 }
574 }
575
576
577 // Create the entry validator and initialize its configuration.
578 entryValidator = new EntryValidator(schema);
579 entryValidator.setCheckAttributeSyntax(!ignoreAttributeSyntax.isPresent());
580 entryValidator.setCheckMalformedDNs(!ignoreMalformedDNs.isPresent());
581 entryValidator.setCheckMissingAttributes(
582 !ignoreMissingAttributes.isPresent());
583 entryValidator.setCheckNameForms(!ignoreNameForms.isPresent());
584 entryValidator.setCheckProhibitedAttributes(
585 !ignoreProhibitedAttributes.isPresent());
586 entryValidator.setCheckProhibitedObjectClasses(
587 !ignoreProhibitedObjectClasses.isPresent());
588 entryValidator.setCheckMissingSuperiorObjectClasses(
589 !ignoreMissingSuperiorObjectClasses.isPresent());
590 entryValidator.setCheckSingleValuedAttributes(
591 !ignoreSingleValuedAttributes.isPresent());
592 entryValidator.setCheckStructuralObjectClasses(
593 !ignoreStructuralObjectClasses.isPresent());
594 entryValidator.setCheckUndefinedAttributes(
595 !ignoreUndefinedAttributes.isPresent());
596 entryValidator.setCheckUndefinedObjectClasses(
597 !ignoreUndefinedObjectClasses.isPresent());
598
599
600 // Create an LDIF reader that can be used to read through the LDIF file.
601 final LDIFReader ldifReader;
602 rejectWriter = null;
603 try
604 {
605 InputStream inputStream = new FileInputStream(ldifFile.getValue());
606 if (isCompressed.isPresent())
607 {
608 inputStream = new GZIPInputStream(inputStream);
609 }
610 ldifReader = new LDIFReader(inputStream, numThreads.getValue(), this);
611 }
612 catch (Exception e)
613 {
614 err("Unable to open the LDIF reader: ", getExceptionMessage(e));
615 return ResultCode.LOCAL_ERROR;
616 }
617
618 ldifReader.setSchema(schema);
619 if (ignoreDuplicateValues.isPresent())
620 {
621 ldifReader.setDuplicateValueBehavior(DuplicateValueBehavior.STRIP);
622 }
623 else
624 {
625 ldifReader.setDuplicateValueBehavior(DuplicateValueBehavior.REJECT);
626 }
627
628 try
629 {
630 // Create an LDIF writer that can be used to write information about
631 // rejected entries.
632 try
633 {
634 if (rejectFile.isPresent())
635 {
636 rejectWriter = new LDIFWriter(rejectFile.getValue());
637 }
638 }
639 catch (Exception e)
640 {
641 err("Unable to create the reject writer: ", getExceptionMessage(e));
642 return ResultCode.LOCAL_ERROR;
643 }
644
645 ResultCode resultCode = ResultCode.SUCCESS;
646 while (true)
647 {
648 try
649 {
650 final Entry e = ldifReader.readEntry();
651 if (e == null)
652 {
653 // Because we're performing parallel processing and returning null
654 // from the translate method, LDIFReader.readEntry() should never
655 // return a non-null value. However, it can throw an LDIFException
656 // if it encounters an invalid entry, or an IOException if there's
657 // a problem reading from the file, so we should still iterate
658 // through all of the entries to catch and report on those problems.
659 break;
660 }
661 }
662 catch (LDIFException le)
663 {
664 malformedEntries.incrementAndGet();
665
666 if (resultCode == ResultCode.SUCCESS)
667 {
668 resultCode = ResultCode.DECODING_ERROR;
669 }
670
671 if (rejectWriter != null)
672 {
673 try
674 {
675 rejectWriter.writeComment(
676 "Unable to parse an entry read from LDIF:", false, false);
677 if (le.mayContinueReading())
678 {
679 rejectWriter.writeComment(getExceptionMessage(le), false, true);
680 }
681 else
682 {
683 rejectWriter.writeComment(getExceptionMessage(le), false,
684 false);
685 rejectWriter.writeComment("Unable to continue LDIF processing.",
686 false, true);
687 err("Aborting LDIF processing: ", getExceptionMessage(le));
688 return ResultCode.LOCAL_ERROR;
689 }
690 }
691 catch (IOException ioe)
692 {
693 err("Unable to write to the reject file:",
694 getExceptionMessage(ioe));
695 err("LDIF parse failure that triggered the rejection: ",
696 getExceptionMessage(le));
697 return ResultCode.LOCAL_ERROR;
698 }
699 }
700 }
701 catch (IOException ioe)
702 {
703
704 if (rejectWriter != null)
705 {
706 try
707 {
708 rejectWriter.writeComment("I/O error reading from LDIF:", false,
709 false);
710 rejectWriter.writeComment(getExceptionMessage(ioe), false,
711 true);
712 return ResultCode.LOCAL_ERROR;
713 }
714 catch (Exception ex)
715 {
716 err("I/O error reading from LDIF:", getExceptionMessage(ioe));
717 return ResultCode.LOCAL_ERROR;
718 }
719 }
720 }
721 }
722
723 if (malformedEntries.get() > 0)
724 {
725 out(malformedEntries.get() + " entries were malformed and could not " +
726 "be read from the LDIF file.");
727 }
728
729 if (entryValidator.getInvalidEntries() > 0)
730 {
731 if (resultCode == ResultCode.SUCCESS)
732 {
733 resultCode = ResultCode.OBJECT_CLASS_VIOLATION;
734 }
735
736 for (final String s : entryValidator.getInvalidEntrySummary(true))
737 {
738 out(s);
739 }
740 }
741 else
742 {
743 if (malformedEntries.get() == 0)
744 {
745 out("No errors were encountered.");
746 }
747 }
748
749 return resultCode;
750 }
751 finally
752 {
753 try
754 {
755 ldifReader.close();
756 }
757 catch (Exception e) {}
758
759 try
760 {
761 if (rejectWriter != null)
762 {
763 rejectWriter.close();
764 }
765 }
766 catch (Exception e) {}
767 }
768 }
769
770
771
772 /**
773 * Examines the provided entry to determine whether it conforms to the
774 * server schema.
775 *
776 * @param entry The entry to be examined.
777 * @param firstLineNumber The line number of the LDIF source on which the
778 * provided entry begins.
779 *
780 * @return The updated entry. This method will always return {@code null}
781 * because all of the real processing needed for the entry is
782 * performed in this method and the entry isn't needed any more
783 * after this method is done.
784 */
785 public Entry translate(final Entry entry, final long firstLineNumber)
786 {
787 final ArrayList<String> invalidReasons = new ArrayList<String>(5);
788 if (! entryValidator.entryIsValid(entry, invalidReasons))
789 {
790 if (rejectWriter != null)
791 {
792 synchronized (this)
793 {
794 try
795 {
796 rejectWriter.writeEntry(entry, listToString(invalidReasons));
797 }
798 catch (IOException ioe) {}
799 }
800 }
801 }
802
803 final long numEntries = entriesProcessed.incrementAndGet();
804 if ((numEntries % 1000L) == 0L)
805 {
806 out("Processed ", numEntries, " entries.");
807 }
808
809 return null;
810 }
811
812
813
814 /**
815 * Converts the provided list of strings into a single string. It will
816 * contain line breaks after all but the last element.
817 *
818 * @param l The list of strings to convert to a single string.
819 *
820 * @return The string from the provided list, or {@code null} if the provided
821 * list is empty or {@code null}.
822 */
823 private static String listToString(final List<String> l)
824 {
825 if ((l == null) || (l.isEmpty()))
826 {
827 return null;
828 }
829
830 final StringBuilder buffer = new StringBuilder();
831 final Iterator<String> iterator = l.iterator();
832 while (iterator.hasNext())
833 {
834 buffer.append(iterator.next());
835 if (iterator.hasNext())
836 {
837 buffer.append(EOL);
838 }
839 }
840
841 return buffer.toString();
842 }
843
844
845
846 /**
847 * {@inheritDoc}
848 */
849 @Override()
850 public LinkedHashMap<String[],String> getExampleUsages()
851 {
852 final LinkedHashMap<String[],String> examples =
853 new LinkedHashMap<String[],String>(2);
854
855 String[] args =
856 {
857 "--hostname", "server.example.com",
858 "--port", "389",
859 "--ldifFile", "data.ldif",
860 "--rejectFile", "rejects.ldif",
861 "--numThreads", "4"
862 };
863 String description =
864 "Validate the contents of the 'data.ldif' file using the schema " +
865 "defined in the specified directory server using four concurrent " +
866 "threads. All types of validation will be performed, and " +
867 "information about any errors will be written to the 'rejects.ldif' " +
868 "file.";
869 examples.put(args, description);
870
871
872 args = new String[]
873 {
874 "--schemaDirectory", "/ds/config/schema",
875 "--ldifFile", "data.ldif",
876 "--rejectFile", "rejects.ldif",
877 "--ignoreStructuralObjectClasses",
878 "--ignoreAttributeSyntax"
879 };
880 description =
881 "Validate the contents of the 'data.ldif' file using the schema " +
882 "defined in LDIF files contained in the /ds/config/schema directory " +
883 "using a single thread. Any errors resulting from entries that do " +
884 "not have exactly one structural object class or from values which " +
885 "violate the syntax for their associated attribute types will be " +
886 "ignored. Information about any other failures will be written to " +
887 "the 'rejects.ldif' file.";
888 examples.put(args, description);
889
890 return examples;
891 }
892
893
894
895 /**
896 * @return EntryValidator
897 *
898 * Returns the EntryValidator
899 */
900 public EntryValidator getEntryValidator()
901 {
902 return entryValidator;
903 }
904 }