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 */ 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 * 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}