001/* 002 * Copyright 2016-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2016-2018 Ping Identity Corporation 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.transformations; 022 023 024 025import java.io.ByteArrayInputStream; 026import java.io.File; 027import java.io.FileInputStream; 028import java.io.FileOutputStream; 029import java.io.InputStream; 030import java.io.OutputStream; 031import java.util.ArrayList; 032import java.util.Iterator; 033import java.util.LinkedHashMap; 034import java.util.List; 035import java.util.TreeMap; 036import java.util.concurrent.atomic.AtomicLong; 037import java.util.zip.GZIPInputStream; 038import java.util.zip.GZIPOutputStream; 039 040import com.unboundid.ldap.sdk.Attribute; 041import com.unboundid.ldap.sdk.DN; 042import com.unboundid.ldap.sdk.Entry; 043import com.unboundid.ldap.sdk.LDAPException; 044import com.unboundid.ldap.sdk.ResultCode; 045import com.unboundid.ldap.sdk.Version; 046import com.unboundid.ldap.sdk.schema.Schema; 047import com.unboundid.ldif.AggregateLDIFReaderChangeRecordTranslator; 048import com.unboundid.ldif.AggregateLDIFReaderEntryTranslator; 049import com.unboundid.ldif.LDIFException; 050import com.unboundid.ldif.LDIFReader; 051import com.unboundid.ldif.LDIFReaderChangeRecordTranslator; 052import com.unboundid.ldif.LDIFReaderEntryTranslator; 053import com.unboundid.ldif.LDIFRecord; 054import com.unboundid.util.AggregateInputStream; 055import com.unboundid.util.ByteStringBuffer; 056import com.unboundid.util.CommandLineTool; 057import com.unboundid.util.Debug; 058import com.unboundid.util.StaticUtils; 059import com.unboundid.util.ThreadSafety; 060import com.unboundid.util.ThreadSafetyLevel; 061import com.unboundid.util.args.ArgumentException; 062import com.unboundid.util.args.ArgumentParser; 063import com.unboundid.util.args.BooleanArgument; 064import com.unboundid.util.args.DNArgument; 065import com.unboundid.util.args.FileArgument; 066import com.unboundid.util.args.FilterArgument; 067import com.unboundid.util.args.IntegerArgument; 068import com.unboundid.util.args.ScopeArgument; 069import com.unboundid.util.args.StringArgument; 070 071import static com.unboundid.ldap.sdk.transformations.TransformationMessages.*; 072 073 074 075/** 076 * This class provides a command-line tool that can be used to apply a number of 077 * transformations to an LDIF file. The transformations that can be applied 078 * include: 079 * <UL> 080 * <LI> 081 * It can scramble the values of a specified set of attributes in a manner 082 * that attempts to preserve the syntax and consistently scrambles the same 083 * value to the same representation. 084 * </LI> 085 * <LI> 086 * It can strip a specified set of attributes out of entries. 087 * </LI> 088 * <LI> 089 * It can redact the values of a specified set of attributes, to indicate 090 * that the values are there but providing no information about what their 091 * values are. 092 * </LI> 093 * <LI> 094 * It can replace the values of a specified attribute with a given set of 095 * values. 096 * </LI> 097 * <LI> 098 * It can add an attribute with a given set of values to any entry that does 099 * not contain that attribute. 100 * </LI> 101 * <LI> 102 * It can replace the values of a specified attribute with a value that 103 * contains a sequentially-incrementing counter. 104 * </LI> 105 * <LI> 106 * It can strip entries matching a given base DN, scope, and filter out of 107 * the LDIF file. 108 * </LI> 109 * <LI> 110 * It can perform DN mapping, so that entries that exist below one base DN 111 * are moved below a different base DN. 112 * </LI> 113 * <LI> 114 * It can perform attribute mapping, to replace uses of one attribute name 115 * with another. 116 * </LI> 117 * </UL> 118 */ 119@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 120public final class TransformLDIF 121 extends CommandLineTool 122 implements LDIFReaderEntryTranslator 123{ 124 /** 125 * The maximum length of any message to write to standard output or standard 126 * error. 127 */ 128 private static final int MAX_OUTPUT_LINE_LENGTH = 129 StaticUtils.TERMINAL_WIDTH_COLUMNS - 1; 130 131 132 133 // The arguments for use by this program. 134 private BooleanArgument addToExistingValues = null; 135 private BooleanArgument appendToTargetLDIF = null; 136 private BooleanArgument compressTarget = null; 137 private BooleanArgument excludeNonMatchingEntries = null; 138 private BooleanArgument flattenAddOmittedRDNAttributesToEntry = null; 139 private BooleanArgument flattenAddOmittedRDNAttributesToRDN = null; 140 private BooleanArgument hideRedactedValueCount = null; 141 private BooleanArgument processDNs = null; 142 private BooleanArgument sourceCompressed = null; 143 private BooleanArgument sourceContainsChangeRecords = null; 144 private BooleanArgument sourceFromStandardInput = null; 145 private BooleanArgument targetToStandardOutput = null; 146 private DNArgument addAttributeBaseDN = null; 147 private DNArgument excludeEntryBaseDN = null; 148 private DNArgument flattenBaseDN = null; 149 private DNArgument moveSubtreeFrom = null; 150 private DNArgument moveSubtreeTo = null; 151 private FileArgument schemaPath = null; 152 private FileArgument sourceLDIF = null; 153 private FileArgument targetLDIF = null; 154 private FilterArgument addAttributeFilter = null; 155 private FilterArgument excludeEntryFilter = null; 156 private FilterArgument flattenExcludeFilter = null; 157 private IntegerArgument initialSequentialValue = null; 158 private IntegerArgument numThreads = null; 159 private IntegerArgument randomSeed = null; 160 private IntegerArgument sequentialValueIncrement = null; 161 private IntegerArgument wrapColumn = null; 162 private ScopeArgument addAttributeScope = null; 163 private ScopeArgument excludeEntryScope = null; 164 private StringArgument addAttributeName = null; 165 private StringArgument addAttributeValue = null; 166 private StringArgument excludeAttribute = null; 167 private StringArgument redactAttribute = null; 168 private StringArgument renameAttributeFrom = null; 169 private StringArgument renameAttributeTo = null; 170 private StringArgument replaceValuesAttribute = null; 171 private StringArgument replacementValue = null; 172 private StringArgument scrambleAttribute = null; 173 private StringArgument scrambleJSONField = null; 174 private StringArgument sequentialAttribute = null; 175 private StringArgument textAfterSequentialValue = null; 176 private StringArgument textBeforeSequentialValue = null; 177 178 // A set of thread-local byte stream buffers that will be used to construct 179 // the LDIF representations of records. 180 private final ThreadLocal<ByteStringBuffer> byteStringBuffers = 181 new ThreadLocal<ByteStringBuffer>(); 182 183 184 185 /** 186 * Invokes this tool with the provided set of arguments. 187 * 188 * @param args The command-line arguments provided to this program. 189 */ 190 public static void main(final String... args) 191 { 192 final ResultCode resultCode = main(System.out, System.err, args); 193 if (resultCode != ResultCode.SUCCESS) 194 { 195 System.exit(resultCode.intValue()); 196 } 197 } 198 199 200 201 /** 202 * Invokes this tool with the provided set of arguments. 203 * 204 * @param out The output stream to use for standard output. It may be 205 * {@code null} if standard output should be suppressed. 206 * @param err The output stream to use for standard error. It may be 207 * {@code null} if standard error should be suppressed. 208 * @param args The command-line arguments provided to this program. 209 * 210 * @return A result code indicating whether processing completed 211 * successfully. 212 */ 213 public static ResultCode main(final OutputStream out, final OutputStream err, 214 final String... args) 215 { 216 final TransformLDIF tool = new TransformLDIF(out, err); 217 return tool.runTool(args); 218 } 219 220 221 222 /** 223 * Creates a new instance of this tool with the provided information. 224 * 225 * @param out The output stream to use for standard output. It may be 226 * {@code null} if standard output should be suppressed. 227 * @param err The output stream to use for standard error. It may be 228 * {@code null} if standard error should be suppressed. 229 */ 230 public TransformLDIF(final OutputStream out, final OutputStream err) 231 { 232 super(out, err); 233 } 234 235 236 237 /** 238 * {@inheritDoc} 239 */ 240 @Override() 241 public String getToolName() 242 { 243 return "transform-ldif"; 244 } 245 246 247 248 /** 249 * {@inheritDoc} 250 */ 251 @Override() 252 public String getToolDescription() 253 { 254 return INFO_TRANSFORM_LDIF_TOOL_DESCRIPTION.get(); 255 } 256 257 258 259 /** 260 * {@inheritDoc} 261 */ 262 @Override() 263 public String getToolVersion() 264 { 265 return Version.NUMERIC_VERSION_STRING; 266 } 267 268 269 270 /** 271 * {@inheritDoc} 272 */ 273 @Override() 274 public boolean supportsInteractiveMode() 275 { 276 return true; 277 } 278 279 280 281 /** 282 * {@inheritDoc} 283 */ 284 @Override() 285 public boolean defaultsToInteractiveMode() 286 { 287 return true; 288 } 289 290 291 292 /** 293 * {@inheritDoc} 294 */ 295 @Override() 296 public boolean supportsPropertiesFile() 297 { 298 return true; 299 } 300 301 302 303 /** 304 * {@inheritDoc} 305 */ 306 @Override() 307 public void addToolArguments(final ArgumentParser parser) 308 throws ArgumentException 309 { 310 // Add arguments pertaining to the source and target LDIF files. 311 sourceLDIF = new FileArgument('l', "sourceLDIF", false, 0, null, 312 INFO_TRANSFORM_LDIF_ARG_DESC_SOURCE_LDIF.get(), true, true, true, 313 false); 314 sourceLDIF.addLongIdentifier("inputLDIF", true); 315 sourceLDIF.addLongIdentifier("source-ldif", true); 316 sourceLDIF.addLongIdentifier("input-ldif", true); 317 sourceLDIF.setArgumentGroupName(INFO_TRANSFORM_LDIF_ARG_GROUP_LDIF.get()); 318 parser.addArgument(sourceLDIF); 319 320 sourceFromStandardInput = new BooleanArgument(null, 321 "sourceFromStandardInput", 1, 322 INFO_TRANSFORM_LDIF_ARG_DESC_SOURCE_STD_IN.get()); 323 sourceFromStandardInput.addLongIdentifier("source-from-standard-input", 324 true); 325 sourceFromStandardInput.setArgumentGroupName( 326 INFO_TRANSFORM_LDIF_ARG_GROUP_LDIF.get()); 327 parser.addArgument(sourceFromStandardInput); 328 parser.addRequiredArgumentSet(sourceLDIF, sourceFromStandardInput); 329 parser.addExclusiveArgumentSet(sourceLDIF, sourceFromStandardInput); 330 331 targetLDIF = new FileArgument('o', "targetLDIF", false, 1, null, 332 INFO_TRANSFORM_LDIF_ARG_DESC_TARGET_LDIF.get(), false, true, true, 333 false); 334 targetLDIF.addLongIdentifier("outputLDIF", true); 335 targetLDIF.addLongIdentifier("target-ldif", true); 336 targetLDIF.addLongIdentifier("output-ldif", true); 337 targetLDIF.setArgumentGroupName(INFO_TRANSFORM_LDIF_ARG_GROUP_LDIF.get()); 338 parser.addArgument(targetLDIF); 339 340 targetToStandardOutput = new BooleanArgument(null, "targetToStandardOutput", 341 1, INFO_TRANSFORM_LDIF_ARG_DESC_TARGET_STD_OUT.get()); 342 targetToStandardOutput.addLongIdentifier("target-to-standard-output", true); 343 targetToStandardOutput.setArgumentGroupName( 344 INFO_TRANSFORM_LDIF_ARG_GROUP_LDIF.get()); 345 parser.addArgument(targetToStandardOutput); 346 parser.addExclusiveArgumentSet(targetLDIF, targetToStandardOutput); 347 348 sourceContainsChangeRecords = new BooleanArgument(null, 349 "sourceContainsChangeRecords", 350 INFO_TRANSFORM_LDIF_ARG_DESC_SOURCE_CONTAINS_CHANGE_RECORDS.get()); 351 sourceContainsChangeRecords.addLongIdentifier( 352 "source-contains-change-records", true); 353 sourceContainsChangeRecords.setArgumentGroupName( 354 INFO_TRANSFORM_LDIF_ARG_GROUP_LDIF.get()); 355 parser.addArgument(sourceContainsChangeRecords); 356 357 appendToTargetLDIF = new BooleanArgument(null, "appendToTargetLDIF", 358 INFO_TRANSFORM_LDIF_ARG_DESC_APPEND_TO_TARGET.get()); 359 appendToTargetLDIF.addLongIdentifier("append-to-target-ldif", true); 360 appendToTargetLDIF.setArgumentGroupName( 361 INFO_TRANSFORM_LDIF_ARG_GROUP_LDIF.get()); 362 parser.addArgument(appendToTargetLDIF); 363 parser.addExclusiveArgumentSet(targetToStandardOutput, appendToTargetLDIF); 364 365 wrapColumn = new IntegerArgument(null, "wrapColumn", false, 1, null, 366 INFO_TRANSFORM_LDIF_ARG_DESC_WRAP_COLUMN.get(), 5, Integer.MAX_VALUE); 367 wrapColumn.addLongIdentifier("wrap-column", true); 368 wrapColumn.setArgumentGroupName(INFO_TRANSFORM_LDIF_ARG_GROUP_LDIF.get()); 369 parser.addArgument(wrapColumn); 370 371 sourceCompressed = new BooleanArgument('C', "sourceCompressed", 372 INFO_TRANSFORM_LDIF_ARG_DESC_SOURCE_COMPRESSED.get()); 373 sourceCompressed.addLongIdentifier("inputCompressed", true); 374 sourceCompressed.addLongIdentifier("source-compressed", true); 375 sourceCompressed.addLongIdentifier("input-compressed", true); 376 sourceCompressed.setArgumentGroupName( 377 INFO_TRANSFORM_LDIF_ARG_GROUP_LDIF.get()); 378 parser.addArgument(sourceCompressed); 379 380 compressTarget = new BooleanArgument('c', "compressTarget", 381 INFO_TRANSFORM_LDIF_ARG_DESC_COMPRESS_TARGET.get()); 382 compressTarget.addLongIdentifier("compressOutput", true); 383 compressTarget.addLongIdentifier("compress", true); 384 compressTarget.addLongIdentifier("compress-target", true); 385 compressTarget.addLongIdentifier("compress-output", true); 386 compressTarget.setArgumentGroupName( 387 INFO_TRANSFORM_LDIF_ARG_GROUP_LDIF.get()); 388 parser.addArgument(compressTarget); 389 390 391 // Add arguments pertaining to attribute scrambling. 392 scrambleAttribute = new StringArgument('a', "scrambleAttribute", false, 0, 393 INFO_TRANSFORM_LDIF_PLACEHOLDER_ATTR_NAME.get(), 394 INFO_TRANSFORM_LDIF_ARG_DESC_SCRAMBLE_ATTR.get()); 395 scrambleAttribute.addLongIdentifier("attributeName", true); 396 scrambleAttribute.addLongIdentifier("scramble-attribute", true); 397 scrambleAttribute.addLongIdentifier("attribute-name", true); 398 scrambleAttribute.setArgumentGroupName( 399 INFO_TRANSFORM_LDIF_ARG_GROUP_SCRAMBLE.get()); 400 parser.addArgument(scrambleAttribute); 401 402 scrambleJSONField = new StringArgument(null, "scrambleJSONField", false, 0, 403 INFO_TRANSFORM_LDIF_PLACEHOLDER_FIELD_NAME.get(), 404 INFO_TRANSFORM_LDIF_ARG_DESC_SCRAMBLE_JSON_FIELD.get( 405 scrambleAttribute.getIdentifierString())); 406 scrambleJSONField.addLongIdentifier("scramble-json-field", true); 407 scrambleJSONField.setArgumentGroupName( 408 INFO_TRANSFORM_LDIF_ARG_GROUP_SCRAMBLE.get()); 409 parser.addArgument(scrambleJSONField); 410 parser.addDependentArgumentSet(scrambleJSONField, scrambleAttribute); 411 412 randomSeed = new IntegerArgument('s', "randomSeed", false, 1, null, 413 INFO_TRANSFORM_LDIF_ARG_DESC_RANDOM_SEED.get()); 414 randomSeed.addLongIdentifier("random-seed", true); 415 randomSeed.setArgumentGroupName( 416 INFO_TRANSFORM_LDIF_ARG_GROUP_SCRAMBLE.get()); 417 parser.addArgument(randomSeed); 418 419 420 // Add arguments pertaining to replacing attribute values with a generated 421 // value using a sequential counter. 422 sequentialAttribute = new StringArgument('S', "sequentialAttribute", 423 false, 0, INFO_TRANSFORM_LDIF_PLACEHOLDER_ATTR_NAME.get(), 424 INFO_TRANSFORM_LDIF_ARG_DESC_SEQUENTIAL_ATTR.get( 425 sourceContainsChangeRecords.getIdentifierString())); 426 sequentialAttribute.addLongIdentifier("sequentialAttributeName", true); 427 sequentialAttribute.addLongIdentifier("sequential-attribute", true); 428 sequentialAttribute.addLongIdentifier("sequential-attribute-name", true); 429 sequentialAttribute.setArgumentGroupName( 430 INFO_TRANSFORM_LDIF_ARG_GROUP_SEQUENTIAL.get()); 431 parser.addArgument(sequentialAttribute); 432 parser.addExclusiveArgumentSet(sourceContainsChangeRecords, 433 sequentialAttribute); 434 435 initialSequentialValue = new IntegerArgument('i', "initialSequentialValue", 436 false, 1, null, 437 INFO_TRANSFORM_LDIF_ARG_DESC_INITIAL_SEQUENTIAL_VALUE.get( 438 sequentialAttribute.getIdentifierString())); 439 initialSequentialValue.addLongIdentifier("initial-sequential-value", true); 440 initialSequentialValue.setArgumentGroupName( 441 INFO_TRANSFORM_LDIF_ARG_GROUP_SEQUENTIAL.get()); 442 parser.addArgument(initialSequentialValue); 443 parser.addDependentArgumentSet(initialSequentialValue, sequentialAttribute); 444 445 sequentialValueIncrement = new IntegerArgument(null, 446 "sequentialValueIncrement", false, 1, null, 447 INFO_TRANSFORM_LDIF_ARG_DESC_SEQUENTIAL_INCREMENT.get( 448 sequentialAttribute.getIdentifierString())); 449 sequentialValueIncrement.addLongIdentifier("sequential-value-increment", 450 true); 451 sequentialValueIncrement.setArgumentGroupName( 452 INFO_TRANSFORM_LDIF_ARG_GROUP_SEQUENTIAL.get()); 453 parser.addArgument(sequentialValueIncrement); 454 parser.addDependentArgumentSet(sequentialValueIncrement, 455 sequentialAttribute); 456 457 textBeforeSequentialValue = new StringArgument(null, 458 "textBeforeSequentialValue", false, 1, null, 459 INFO_TRANSFORM_LDIF_ARG_DESC_SEQUENTIAL_TEXT_BEFORE.get( 460 sequentialAttribute.getIdentifierString())); 461 textBeforeSequentialValue.addLongIdentifier("text-before-sequential-value", 462 true); 463 textBeforeSequentialValue.setArgumentGroupName( 464 INFO_TRANSFORM_LDIF_ARG_GROUP_SEQUENTIAL.get()); 465 parser.addArgument(textBeforeSequentialValue); 466 parser.addDependentArgumentSet(textBeforeSequentialValue, 467 sequentialAttribute); 468 469 textAfterSequentialValue = new StringArgument(null, 470 "textAfterSequentialValue", false, 1, null, 471 INFO_TRANSFORM_LDIF_ARG_DESC_SEQUENTIAL_TEXT_AFTER.get( 472 sequentialAttribute.getIdentifierString())); 473 textAfterSequentialValue.addLongIdentifier("text-after-sequential-value", 474 true); 475 textAfterSequentialValue.setArgumentGroupName( 476 INFO_TRANSFORM_LDIF_ARG_GROUP_SEQUENTIAL.get()); 477 parser.addArgument(textAfterSequentialValue); 478 parser.addDependentArgumentSet(textAfterSequentialValue, 479 sequentialAttribute); 480 481 482 // Add arguments pertaining to attribute value replacement. 483 replaceValuesAttribute = new StringArgument(null, "replaceValuesAttribute", 484 false, 1, INFO_TRANSFORM_LDIF_PLACEHOLDER_ATTR_NAME.get(), 485 INFO_TRANSFORM_LDIF_ARG_DESC_REPLACE_VALUES_ATTR.get( 486 sourceContainsChangeRecords.getIdentifierString())); 487 replaceValuesAttribute.addLongIdentifier("replace-values-attribute", true); 488 replaceValuesAttribute.setArgumentGroupName( 489 INFO_TRANSFORM_LDIF_ARG_GROUP_REPLACE_VALUES.get()); 490 parser.addArgument(replaceValuesAttribute); 491 parser.addExclusiveArgumentSet(sourceContainsChangeRecords, 492 replaceValuesAttribute); 493 494 replacementValue = new StringArgument(null, "replacementValue", false, 0, 495 null, 496 INFO_TRANSFORM_LDIF_ARG_DESC_REPLACEMENT_VALUE.get( 497 replaceValuesAttribute.getIdentifierString())); 498 replacementValue.addLongIdentifier("replacement-value", true); 499 replacementValue.setArgumentGroupName( 500 INFO_TRANSFORM_LDIF_ARG_GROUP_REPLACE_VALUES.get()); 501 parser.addArgument(replacementValue); 502 parser.addDependentArgumentSet(replaceValuesAttribute, replacementValue); 503 parser.addDependentArgumentSet(replacementValue, replaceValuesAttribute); 504 505 506 // Add arguments pertaining to adding missing attributes. 507 addAttributeName = new StringArgument(null, "addAttributeName", false, 1, 508 INFO_TRANSFORM_LDIF_PLACEHOLDER_ATTR_NAME.get(), 509 INFO_TRANSFORM_LDIF_ARG_DESC_ADD_ATTR.get( 510 "--addAttributeValue", 511 sourceContainsChangeRecords.getIdentifierString())); 512 addAttributeName.addLongIdentifier("add-attribute-name", true); 513 addAttributeName.setArgumentGroupName( 514 INFO_TRANSFORM_LDIF_ARG_GROUP_ADD_ATTR.get()); 515 parser.addArgument(addAttributeName); 516 parser.addExclusiveArgumentSet(sourceContainsChangeRecords, 517 addAttributeName); 518 519 addAttributeValue = new StringArgument(null, "addAttributeValue", false, 0, 520 null, 521 INFO_TRANSFORM_LDIF_ARG_DESC_ADD_VALUE.get( 522 addAttributeName.getIdentifierString())); 523 addAttributeValue.addLongIdentifier("add-attribute-value", true); 524 addAttributeValue.setArgumentGroupName( 525 INFO_TRANSFORM_LDIF_ARG_GROUP_ADD_ATTR.get()); 526 parser.addArgument(addAttributeValue); 527 parser.addDependentArgumentSet(addAttributeName, addAttributeValue); 528 parser.addDependentArgumentSet(addAttributeValue, addAttributeName); 529 530 addToExistingValues = new BooleanArgument(null, "addToExistingValues", 531 INFO_TRANSFORM_LDIF_ARG_DESC_ADD_MERGE_VALUES.get( 532 addAttributeName.getIdentifierString(), 533 addAttributeValue.getIdentifierString())); 534 addToExistingValues.addLongIdentifier("add-to-existing-values", true); 535 addToExistingValues.setArgumentGroupName( 536 INFO_TRANSFORM_LDIF_ARG_GROUP_ADD_ATTR.get()); 537 parser.addArgument(addToExistingValues); 538 parser.addDependentArgumentSet(addToExistingValues, addAttributeName); 539 540 addAttributeBaseDN = new DNArgument(null, "addAttributeBaseDN", false, 1, 541 null, 542 INFO_TRANSFORM_LDIF_ARG_DESC_ADD_BASE_DN.get( 543 addAttributeName.getIdentifierString())); 544 addAttributeBaseDN.addLongIdentifier("add-attribute-base-dn", true); 545 addAttributeBaseDN.setArgumentGroupName( 546 INFO_TRANSFORM_LDIF_ARG_GROUP_ADD_ATTR.get()); 547 parser.addArgument(addAttributeBaseDN); 548 parser.addDependentArgumentSet(addAttributeBaseDN, addAttributeName); 549 550 addAttributeScope = new ScopeArgument(null, "addAttributeScope", false, 551 null, 552 INFO_TRANSFORM_LDIF_ARG_DESC_ADD_SCOPE.get( 553 addAttributeBaseDN.getIdentifierString(), 554 addAttributeName.getIdentifierString())); 555 addAttributeScope.addLongIdentifier("add-attribute-scope", true); 556 addAttributeScope.setArgumentGroupName( 557 INFO_TRANSFORM_LDIF_ARG_GROUP_ADD_ATTR.get()); 558 parser.addArgument(addAttributeScope); 559 parser.addDependentArgumentSet(addAttributeScope, addAttributeName); 560 561 addAttributeFilter = new FilterArgument(null, "addAttributeFilter", false, 562 1, null, 563 INFO_TRANSFORM_LDIF_ARG_DESC_ADD_FILTER.get( 564 addAttributeName.getIdentifierString())); 565 addAttributeFilter.addLongIdentifier("add-attribute-filter", true); 566 addAttributeFilter.setArgumentGroupName( 567 INFO_TRANSFORM_LDIF_ARG_GROUP_ADD_ATTR.get()); 568 parser.addArgument(addAttributeFilter); 569 parser.addDependentArgumentSet(addAttributeFilter, addAttributeName); 570 571 572 // Add arguments pertaining to renaming attributes. 573 renameAttributeFrom = new StringArgument(null, "renameAttributeFrom", 574 false, 0, INFO_TRANSFORM_LDIF_PLACEHOLDER_ATTR_NAME.get(), 575 INFO_TRANSFORM_LDIF_ARG_DESC_RENAME_FROM.get()); 576 renameAttributeFrom.addLongIdentifier("rename-attribute-from", true); 577 renameAttributeFrom.setArgumentGroupName( 578 INFO_TRANSFORM_LDIF_ARG_GROUP_RENAME.get()); 579 parser.addArgument(renameAttributeFrom); 580 581 renameAttributeTo = new StringArgument(null, "renameAttributeTo", 582 false, 0, INFO_TRANSFORM_LDIF_PLACEHOLDER_ATTR_NAME.get(), 583 INFO_TRANSFORM_LDIF_ARG_DESC_RENAME_TO.get( 584 renameAttributeFrom.getIdentifierString())); 585 renameAttributeTo.addLongIdentifier("rename-attribute-to", true); 586 renameAttributeTo.setArgumentGroupName( 587 INFO_TRANSFORM_LDIF_ARG_GROUP_RENAME.get()); 588 parser.addArgument(renameAttributeTo); 589 parser.addDependentArgumentSet(renameAttributeFrom, renameAttributeTo); 590 parser.addDependentArgumentSet(renameAttributeTo, renameAttributeFrom); 591 592 593 // Add arguments pertaining to flattening subtrees. 594 flattenBaseDN = new DNArgument(null, "flattenBaseDN", false, 1, null, 595 INFO_TRANSFORM_LDIF_ARG_DESC_FLATTEN_BASE_DN.get()); 596 flattenBaseDN.addLongIdentifier("flatten-base-dn", true); 597 flattenBaseDN.setArgumentGroupName( 598 INFO_TRANSFORM_LDIF_ARG_GROUP_FLATTEN.get()); 599 parser.addArgument(flattenBaseDN); 600 parser.addExclusiveArgumentSet(sourceContainsChangeRecords, 601 flattenBaseDN); 602 603 flattenAddOmittedRDNAttributesToEntry = new BooleanArgument(null, 604 "flattenAddOmittedRDNAttributesToEntry", 1, 605 INFO_TRANSFORM_LDIF_ARG_DESC_FLATTEN_ADD_OMITTED_TO_ENTRY.get()); 606 flattenAddOmittedRDNAttributesToEntry.addLongIdentifier( 607 "flatten-add-omitted-rdn-attributes-to-entry", true); 608 flattenAddOmittedRDNAttributesToEntry.setArgumentGroupName( 609 INFO_TRANSFORM_LDIF_ARG_GROUP_FLATTEN.get()); 610 parser.addArgument(flattenAddOmittedRDNAttributesToEntry); 611 parser.addDependentArgumentSet(flattenAddOmittedRDNAttributesToEntry, 612 flattenBaseDN); 613 614 flattenAddOmittedRDNAttributesToRDN = new BooleanArgument(null, 615 "flattenAddOmittedRDNAttributesToRDN", 1, 616 INFO_TRANSFORM_LDIF_ARG_DESC_FLATTEN_ADD_OMITTED_TO_RDN.get()); 617 flattenAddOmittedRDNAttributesToRDN.addLongIdentifier( 618 "flatten-add-omitted-rdn-attributes-to-rdn", true); 619 flattenAddOmittedRDNAttributesToRDN.setArgumentGroupName( 620 INFO_TRANSFORM_LDIF_ARG_GROUP_FLATTEN.get()); 621 parser.addArgument(flattenAddOmittedRDNAttributesToRDN); 622 parser.addDependentArgumentSet(flattenAddOmittedRDNAttributesToRDN, 623 flattenBaseDN); 624 625 flattenExcludeFilter = new FilterArgument(null, "flattenExcludeFilter", 626 false, 1, null, 627 INFO_TRANSFORM_LDIF_ARG_DESC_FLATTEN_EXCLUDE_FILTER.get()); 628 flattenExcludeFilter.addLongIdentifier("flatten-exclude-filter", true); 629 flattenExcludeFilter.setArgumentGroupName( 630 INFO_TRANSFORM_LDIF_ARG_GROUP_FLATTEN.get()); 631 parser.addArgument(flattenExcludeFilter); 632 parser.addDependentArgumentSet(flattenExcludeFilter, flattenBaseDN); 633 634 635 // Add arguments pertaining to moving subtrees. 636 moveSubtreeFrom = new DNArgument(null, "moveSubtreeFrom", false, 0, null, 637 INFO_TRANSFORM_LDIF_ARG_DESC_MOVE_SUBTREE_FROM.get()); 638 moveSubtreeFrom.addLongIdentifier("move-subtree-from", true); 639 moveSubtreeFrom.setArgumentGroupName( 640 INFO_TRANSFORM_LDIF_ARG_GROUP_MOVE.get()); 641 parser.addArgument(moveSubtreeFrom); 642 643 moveSubtreeTo = new DNArgument(null, "moveSubtreeTo", false, 0, null, 644 INFO_TRANSFORM_LDIF_ARG_DESC_MOVE_SUBTREE_TO.get( 645 moveSubtreeFrom.getIdentifierString())); 646 moveSubtreeTo.addLongIdentifier("move-subtree-to", true); 647 moveSubtreeTo.setArgumentGroupName( 648 INFO_TRANSFORM_LDIF_ARG_GROUP_MOVE.get()); 649 parser.addArgument(moveSubtreeTo); 650 parser.addDependentArgumentSet(moveSubtreeFrom, moveSubtreeTo); 651 parser.addDependentArgumentSet(moveSubtreeTo, moveSubtreeFrom); 652 653 654 // Add arguments pertaining to redacting attribute values. 655 redactAttribute = new StringArgument(null, "redactAttribute", false, 0, 656 INFO_TRANSFORM_LDIF_PLACEHOLDER_ATTR_NAME.get(), 657 INFO_TRANSFORM_LDIF_ARG_DESC_REDACT_ATTR.get()); 658 redactAttribute.addLongIdentifier("redact-attribute", true); 659 redactAttribute.setArgumentGroupName( 660 INFO_TRANSFORM_LDIF_ARG_GROUP_REDACT.get()); 661 parser.addArgument(redactAttribute); 662 663 hideRedactedValueCount = new BooleanArgument(null, "hideRedactedValueCount", 664 INFO_TRANSFORM_LDIF_ARG_DESC_HIDE_REDACTED_COUNT.get()); 665 hideRedactedValueCount.addLongIdentifier("hide-redacted-value-count", 666 true); 667 hideRedactedValueCount.setArgumentGroupName( 668 INFO_TRANSFORM_LDIF_ARG_GROUP_REDACT.get()); 669 parser.addArgument(hideRedactedValueCount); 670 parser.addDependentArgumentSet(hideRedactedValueCount, redactAttribute); 671 672 673 // Add arguments pertaining to excluding attributes and entries. 674 excludeAttribute = new StringArgument(null, "excludeAttribute", false, 0, 675 INFO_TRANSFORM_LDIF_PLACEHOLDER_ATTR_NAME.get(), 676 INFO_TRANSFORM_LDIF_ARG_DESC_EXCLUDE_ATTR.get()); 677 excludeAttribute.addLongIdentifier("suppressAttribute", true); 678 excludeAttribute.addLongIdentifier("exclude-attribute", true); 679 excludeAttribute.addLongIdentifier("suppress-attribute", true); 680 excludeAttribute.setArgumentGroupName( 681 INFO_TRANSFORM_LDIF_ARG_GROUP_EXCLUDE.get()); 682 parser.addArgument(excludeAttribute); 683 684 excludeEntryBaseDN = new DNArgument(null, "excludeEntryBaseDN", false, 1, 685 null, 686 INFO_TRANSFORM_LDIF_ARG_DESC_EXCLUDE_ENTRY_BASE_DN.get( 687 sourceContainsChangeRecords.getIdentifierString())); 688 excludeEntryBaseDN.addLongIdentifier("suppressEntryBaseDN", true); 689 excludeEntryBaseDN.addLongIdentifier("exclude-entry-base-dn", true); 690 excludeEntryBaseDN.addLongIdentifier("suppress-entry-base-dn", true); 691 excludeEntryBaseDN.setArgumentGroupName( 692 INFO_TRANSFORM_LDIF_ARG_GROUP_EXCLUDE.get()); 693 parser.addArgument(excludeEntryBaseDN); 694 parser.addExclusiveArgumentSet(sourceContainsChangeRecords, 695 excludeEntryBaseDN); 696 697 excludeEntryScope = new ScopeArgument(null, "excludeEntryScope", false, 698 null, 699 INFO_TRANSFORM_LDIF_ARG_DESC_EXCLUDE_ENTRY_SCOPE.get( 700 sourceContainsChangeRecords.getIdentifierString())); 701 excludeEntryScope.addLongIdentifier("suppressEntryScope", true); 702 excludeEntryScope.addLongIdentifier("exclude-entry-scope", true); 703 excludeEntryScope.addLongIdentifier("suppress-entry-scope", true); 704 excludeEntryScope.setArgumentGroupName( 705 INFO_TRANSFORM_LDIF_ARG_GROUP_EXCLUDE.get()); 706 parser.addArgument(excludeEntryScope); 707 parser.addExclusiveArgumentSet(sourceContainsChangeRecords, 708 excludeEntryScope); 709 710 excludeEntryFilter = new FilterArgument(null, "excludeEntryFilter", false, 711 1, null, 712 INFO_TRANSFORM_LDIF_ARG_DESC_EXCLUDE_ENTRY_FILTER.get( 713 sourceContainsChangeRecords.getIdentifierString())); 714 excludeEntryFilter.addLongIdentifier("suppressEntryFilter", true); 715 excludeEntryFilter.addLongIdentifier("exclude-entry-filter", true); 716 excludeEntryFilter.addLongIdentifier("suppress-entry-filter", true); 717 excludeEntryFilter.setArgumentGroupName( 718 INFO_TRANSFORM_LDIF_ARG_GROUP_EXCLUDE.get()); 719 parser.addArgument(excludeEntryFilter); 720 parser.addExclusiveArgumentSet(sourceContainsChangeRecords, 721 excludeEntryFilter); 722 723 excludeNonMatchingEntries = new BooleanArgument(null, 724 "excludeNonMatchingEntries", 725 INFO_TRANSFORM_LDIF_ARG_DESC_EXCLUDE_NON_MATCHING.get()); 726 excludeNonMatchingEntries.addLongIdentifier("exclude-non-matching-entries", 727 true); 728 excludeNonMatchingEntries.setArgumentGroupName( 729 INFO_TRANSFORM_LDIF_ARG_GROUP_EXCLUDE.get()); 730 parser.addArgument(excludeNonMatchingEntries); 731 parser.addDependentArgumentSet(excludeNonMatchingEntries, 732 excludeEntryBaseDN, excludeEntryScope, excludeEntryFilter); 733 734 735 // Add the remaining arguments. 736 schemaPath = new FileArgument(null, "schemaPath", false, 0, null, 737 INFO_TRANSFORM_LDIF_ARG_DESC_SCHEMA_PATH.get(), 738 true, true, false, false); 739 schemaPath.addLongIdentifier("schemaFile", true); 740 schemaPath.addLongIdentifier("schemaDirectory", true); 741 schemaPath.addLongIdentifier("schema-path", true); 742 schemaPath.addLongIdentifier("schema-file", true); 743 schemaPath.addLongIdentifier("schema-directory", true); 744 parser.addArgument(schemaPath); 745 746 numThreads = new IntegerArgument('t', "numThreads", false, 1, null, 747 INFO_TRANSFORM_LDIF_ARG_DESC_NUM_THREADS.get(), 1, Integer.MAX_VALUE, 748 1); 749 numThreads.addLongIdentifier("num-threads", true); 750 parser.addArgument(numThreads); 751 752 processDNs = new BooleanArgument('d', "processDNs", 753 INFO_TRANSFORM_LDIF_ARG_DESC_PROCESS_DNS.get()); 754 processDNs.addLongIdentifier("process-dns", true); 755 parser.addArgument(processDNs); 756 757 758 // Ensure that at least one kind of transformation was requested. 759 parser.addRequiredArgumentSet(scrambleAttribute, sequentialAttribute, 760 replaceValuesAttribute, addAttributeName, renameAttributeFrom, 761 flattenBaseDN, moveSubtreeFrom, redactAttribute, excludeAttribute, 762 excludeEntryBaseDN, excludeEntryScope, excludeEntryFilter); 763 } 764 765 766 767 /** 768 * {@inheritDoc} 769 */ 770 @Override() 771 public void doExtendedArgumentValidation() 772 throws ArgumentException 773 { 774 // Ideally, exactly one of the targetLDIF and targetToStandardOutput 775 // arguments should always be provided. But in order to preserve backward 776 // compatibility with a legacy scramble-ldif tool, we will allow both to be 777 // omitted if either --scrambleAttribute or --sequentialArgument is 778 // provided. In that case, the path of the output file will be the path of 779 // the first input file with ".scrambled" appended to it. 780 if (! (targetLDIF.isPresent() || targetToStandardOutput.isPresent())) 781 { 782 if (! (scrambleAttribute.isPresent() || sequentialAttribute.isPresent())) 783 { 784 throw new ArgumentException(ERR_TRANSFORM_LDIF_MISSING_TARGET_ARG.get( 785 targetLDIF.getIdentifierString(), 786 targetToStandardOutput.getIdentifierString())); 787 } 788 } 789 790 791 // Make sure that the --renameAttributeFrom and --renameAttributeTo 792 // arguments were provided an equal number of times. 793 final int renameFromOccurrences = renameAttributeFrom.getNumOccurrences(); 794 final int renameToOccurrences = renameAttributeTo.getNumOccurrences(); 795 if (renameFromOccurrences != renameToOccurrences) 796 { 797 throw new ArgumentException( 798 ERR_TRANSFORM_LDIF_ARG_COUNT_MISMATCH.get( 799 renameAttributeFrom.getIdentifierString(), 800 renameAttributeTo.getIdentifierString())); 801 } 802 803 804 // Make sure that the --moveSubtreeFrom and --moveSubtreeTo arguments were 805 // provided an equal number of times. 806 final int moveFromOccurrences = moveSubtreeFrom.getNumOccurrences(); 807 final int moveToOccurrences = moveSubtreeTo.getNumOccurrences(); 808 if (moveFromOccurrences != moveToOccurrences) 809 { 810 throw new ArgumentException( 811 ERR_TRANSFORM_LDIF_ARG_COUNT_MISMATCH.get( 812 moveSubtreeFrom.getIdentifierString(), 813 moveSubtreeTo.getIdentifierString())); 814 } 815 } 816 817 818 819 /** 820 * {@inheritDoc} 821 */ 822 @Override() 823 public ResultCode doToolProcessing() 824 { 825 final Schema schema; 826 try 827 { 828 schema = getSchema(); 829 } 830 catch (final LDAPException le) 831 { 832 wrapErr(0, MAX_OUTPUT_LINE_LENGTH, le.getMessage()); 833 return le.getResultCode(); 834 } 835 836 837 // Create the translators to use to apply the transformations. 838 final ArrayList<LDIFReaderEntryTranslator> entryTranslators = 839 new ArrayList<LDIFReaderEntryTranslator>(10); 840 final ArrayList<LDIFReaderChangeRecordTranslator> changeRecordTranslators = 841 new ArrayList<LDIFReaderChangeRecordTranslator>(10); 842 843 final AtomicLong excludedEntryCount = new AtomicLong(0L); 844 createTranslators(entryTranslators, changeRecordTranslators, 845 schema, excludedEntryCount); 846 847 final AggregateLDIFReaderEntryTranslator entryTranslator = 848 new AggregateLDIFReaderEntryTranslator(entryTranslators); 849 final AggregateLDIFReaderChangeRecordTranslator changeRecordTranslator = 850 new AggregateLDIFReaderChangeRecordTranslator(changeRecordTranslators); 851 852 853 // Determine the path to the target file to be written. 854 final File targetFile; 855 if (targetLDIF.isPresent()) 856 { 857 targetFile = targetLDIF.getValue(); 858 } 859 else if (targetToStandardOutput.isPresent()) 860 { 861 targetFile = null; 862 } 863 else 864 { 865 targetFile = 866 new File(sourceLDIF.getValue().getAbsolutePath() + ".scrambled"); 867 } 868 869 870 // Create the LDIF reader. 871 final LDIFReader ldifReader; 872 try 873 { 874 InputStream inputStream; 875 if (sourceLDIF.isPresent()) 876 { 877 final List<File> sourceFiles = sourceLDIF.getValues(); 878 final ArrayList<InputStream> fileInputStreams = 879 new ArrayList<InputStream>(2*sourceFiles.size()); 880 for (final File f : sourceFiles) 881 { 882 if (! fileInputStreams.isEmpty()) 883 { 884 // Go ahead and ensure that there are at least new end-of-line 885 // markers between each file. Otherwise, it's possible for entries 886 // to run together. 887 final byte[] doubleEOL = new byte[StaticUtils.EOL_BYTES.length * 2]; 888 System.arraycopy(StaticUtils.EOL_BYTES, 0, doubleEOL, 0, 889 StaticUtils.EOL_BYTES.length); 890 System.arraycopy(StaticUtils.EOL_BYTES, 0, doubleEOL, 891 StaticUtils.EOL_BYTES.length, StaticUtils.EOL_BYTES.length); 892 fileInputStreams.add(new ByteArrayInputStream(doubleEOL)); 893 } 894 fileInputStreams.add(new FileInputStream(f)); 895 } 896 897 if (fileInputStreams.size() == 1) 898 { 899 inputStream = fileInputStreams.get(0); 900 } 901 else 902 { 903 inputStream = new AggregateInputStream(fileInputStreams); 904 } 905 } 906 else 907 { 908 inputStream = System.in; 909 } 910 911 if (sourceCompressed.isPresent()) 912 { 913 inputStream = new GZIPInputStream(inputStream); 914 } 915 916 ldifReader = new LDIFReader(inputStream, numThreads.getValue(), 917 entryTranslator, changeRecordTranslator); 918 if (schema != null) 919 { 920 ldifReader.setSchema(schema); 921 } 922 } 923 catch (final Exception e) 924 { 925 Debug.debugException(e); 926 wrapErr(0, MAX_OUTPUT_LINE_LENGTH, 927 ERR_TRANSFORM_LDIF_ERROR_CREATING_LDIF_READER.get( 928 StaticUtils.getExceptionMessage(e))); 929 return ResultCode.LOCAL_ERROR; 930 } 931 932 933 ResultCode resultCode = ResultCode.SUCCESS; 934 OutputStream outputStream = null; 935processingBlock: 936 try 937 { 938 // Create the output stream to use to write the transformed data. 939 try 940 { 941 if (targetFile == null) 942 { 943 outputStream = getOut(); 944 } 945 else 946 { 947 outputStream = 948 new FileOutputStream(targetFile, appendToTargetLDIF.isPresent()); 949 } 950 951 if (compressTarget.isPresent()) 952 { 953 outputStream = new GZIPOutputStream(outputStream); 954 } 955 } 956 catch (final Exception e) 957 { 958 Debug.debugException(e); 959 wrapErr(0, MAX_OUTPUT_LINE_LENGTH, 960 ERR_TRANSFORM_LDIF_ERROR_CREATING_OUTPUT_STREAM.get( 961 targetFile.getAbsolutePath(), 962 StaticUtils.getExceptionMessage(e))); 963 resultCode = ResultCode.LOCAL_ERROR; 964 break processingBlock; 965 } 966 967 968 // Read the source data one record at a time. The transformations will 969 // automatically be applied by the LDIF reader's translators, and even if 970 // there are multiple reader threads, we're guaranteed to get the results 971 // in the right order. 972 long entriesWritten = 0L; 973 while (true) 974 { 975 final LDIFRecord ldifRecord; 976 try 977 { 978 ldifRecord = ldifReader.readLDIFRecord(); 979 } 980 catch (final LDIFException le) 981 { 982 Debug.debugException(le); 983 if (le.mayContinueReading()) 984 { 985 wrapErr(0, MAX_OUTPUT_LINE_LENGTH, 986 ERR_TRANSFORM_LDIF_RECOVERABLE_MALFORMED_RECORD.get( 987 StaticUtils.getExceptionMessage(le))); 988 if (resultCode == ResultCode.SUCCESS) 989 { 990 resultCode = ResultCode.PARAM_ERROR; 991 } 992 continue; 993 } 994 else 995 { 996 wrapErr(0, MAX_OUTPUT_LINE_LENGTH, 997 ERR_TRANSFORM_LDIF_UNRECOVERABLE_MALFORMED_RECORD.get( 998 StaticUtils.getExceptionMessage(le))); 999 if (resultCode == ResultCode.SUCCESS) 1000 { 1001 resultCode = ResultCode.PARAM_ERROR; 1002 } 1003 break processingBlock; 1004 } 1005 } 1006 catch (final Exception e) 1007 { 1008 Debug.debugException(e); 1009 wrapErr(0, MAX_OUTPUT_LINE_LENGTH, 1010 ERR_TRANSFORM_LDIF_UNEXPECTED_READ_ERROR.get( 1011 StaticUtils.getExceptionMessage(e))); 1012 resultCode = ResultCode.LOCAL_ERROR; 1013 break processingBlock; 1014 } 1015 1016 1017 // If the LDIF record is null, then we've run out of records so we're 1018 // done. 1019 if (ldifRecord == null) 1020 { 1021 break; 1022 } 1023 1024 1025 // Write the record to the output stream. 1026 try 1027 { 1028 if (ldifRecord instanceof PreEncodedLDIFEntry) 1029 { 1030 outputStream.write( 1031 ((PreEncodedLDIFEntry) ldifRecord).getLDIFBytes()); 1032 } 1033 else 1034 { 1035 final ByteStringBuffer buffer = getBuffer(); 1036 if (wrapColumn.isPresent()) 1037 { 1038 ldifRecord.toLDIF(buffer, wrapColumn.getValue()); 1039 } 1040 else 1041 { 1042 ldifRecord.toLDIF(buffer, 0); 1043 } 1044 buffer.append(StaticUtils.EOL_BYTES); 1045 buffer.write(outputStream); 1046 } 1047 } 1048 catch (final Exception e) 1049 { 1050 Debug.debugException(e); 1051 wrapErr(0, MAX_OUTPUT_LINE_LENGTH, 1052 ERR_TRANSFORM_LDIF_WRITE_ERROR.get(targetFile.getAbsolutePath(), 1053 StaticUtils.getExceptionMessage(e))); 1054 resultCode = ResultCode.LOCAL_ERROR; 1055 break processingBlock; 1056 } 1057 1058 1059 // If we've written a multiple of 1000 entries, print a progress 1060 // message. 1061 entriesWritten++; 1062 if ((! targetToStandardOutput.isPresent()) && 1063 ((entriesWritten % 1000L) == 0)) 1064 { 1065 final long numExcluded = excludedEntryCount.get(); 1066 if (numExcluded > 0L) 1067 { 1068 wrapOut(0, MAX_OUTPUT_LINE_LENGTH, 1069 INFO_TRANSFORM_LDIF_WROTE_ENTRIES_WITH_EXCLUDED.get( 1070 entriesWritten, numExcluded)); 1071 } 1072 else 1073 { 1074 wrapOut(0, MAX_OUTPUT_LINE_LENGTH, 1075 INFO_TRANSFORM_LDIF_WROTE_ENTRIES_NONE_EXCLUDED.get( 1076 entriesWritten)); 1077 } 1078 } 1079 } 1080 1081 1082 if (! targetToStandardOutput.isPresent()) 1083 { 1084 final long numExcluded = excludedEntryCount.get(); 1085 if (numExcluded > 0L) 1086 { 1087 wrapOut(0, MAX_OUTPUT_LINE_LENGTH, 1088 INFO_TRANSFORM_LDIF_COMPLETE_WITH_EXCLUDED.get(entriesWritten, 1089 numExcluded)); 1090 } 1091 else 1092 { 1093 wrapOut(0, MAX_OUTPUT_LINE_LENGTH, 1094 INFO_TRANSFORM_LDIF_COMPLETE_NONE_EXCLUDED.get(entriesWritten)); 1095 } 1096 } 1097 } 1098 finally 1099 { 1100 if (outputStream != null) 1101 { 1102 try 1103 { 1104 outputStream.close(); 1105 } 1106 catch (final Exception e) 1107 { 1108 Debug.debugException(e); 1109 wrapErr(0, MAX_OUTPUT_LINE_LENGTH, 1110 ERR_TRANSFORM_LDIF_ERROR_CLOSING_OUTPUT_STREAM.get( 1111 targetFile.getAbsolutePath(), 1112 StaticUtils.getExceptionMessage(e))); 1113 if (resultCode == ResultCode.SUCCESS) 1114 { 1115 resultCode = ResultCode.LOCAL_ERROR; 1116 } 1117 } 1118 } 1119 1120 try 1121 { 1122 ldifReader.close(); 1123 } 1124 catch (final Exception e) 1125 { 1126 Debug.debugException(e); 1127 // We can ignore this. 1128 } 1129 } 1130 1131 1132 return resultCode; 1133 } 1134 1135 1136 1137 /** 1138 * Retrieves the schema that should be used for processing. 1139 * 1140 * @return The schema that was created. 1141 * 1142 * @throws LDAPException If a problem is encountered while retrieving the 1143 * schema. 1144 */ 1145 private Schema getSchema() 1146 throws LDAPException 1147 { 1148 // If any schema paths were specified, then load the schema only from those 1149 // paths. 1150 if (schemaPath.isPresent()) 1151 { 1152 final ArrayList<File> schemaFiles = new ArrayList<File>(10); 1153 for (final File path : schemaPath.getValues()) 1154 { 1155 if (path.isFile()) 1156 { 1157 schemaFiles.add(path); 1158 } 1159 else 1160 { 1161 final TreeMap<String,File> fileMap = new TreeMap<String,File>(); 1162 for (final File schemaDirFile : path.listFiles()) 1163 { 1164 final String name = schemaDirFile.getName(); 1165 if (schemaDirFile.isFile() && name.toLowerCase().endsWith(".ldif")) 1166 { 1167 fileMap.put(name, schemaDirFile); 1168 } 1169 } 1170 schemaFiles.addAll(fileMap.values()); 1171 } 1172 } 1173 1174 if (schemaFiles.isEmpty()) 1175 { 1176 throw new LDAPException(ResultCode.PARAM_ERROR, 1177 ERR_TRANSFORM_LDIF_NO_SCHEMA_FILES.get( 1178 schemaPath.getIdentifierString())); 1179 } 1180 else 1181 { 1182 try 1183 { 1184 return Schema.getSchema(schemaFiles); 1185 } 1186 catch (final Exception e) 1187 { 1188 Debug.debugException(e); 1189 throw new LDAPException(ResultCode.LOCAL_ERROR, 1190 ERR_TRANSFORM_LDIF_ERROR_LOADING_SCHEMA.get( 1191 StaticUtils.getExceptionMessage(e))); 1192 } 1193 } 1194 } 1195 else 1196 { 1197 // If the INSTANCE_ROOT environment variable is set and it refers to a 1198 // directory that has a config/schema subdirectory that has one or more 1199 // schema files in it, then read the schema from that directory. 1200 try 1201 { 1202 final String instanceRootStr = System.getenv("INSTANCE_ROOT"); 1203 if (instanceRootStr != null) 1204 { 1205 final File instanceRoot = new File(instanceRootStr); 1206 final File configDir = new File(instanceRoot, "config"); 1207 final File schemaDir = new File(configDir, "schema"); 1208 if (schemaDir.exists()) 1209 { 1210 final TreeMap<String,File> fileMap = new TreeMap<String,File>(); 1211 for (final File schemaDirFile : schemaDir.listFiles()) 1212 { 1213 final String name = schemaDirFile.getName(); 1214 if (schemaDirFile.isFile() && 1215 name.toLowerCase().endsWith(".ldif")) 1216 { 1217 fileMap.put(name, schemaDirFile); 1218 } 1219 } 1220 1221 if (! fileMap.isEmpty()) 1222 { 1223 return Schema.getSchema(new ArrayList<File>(fileMap.values())); 1224 } 1225 } 1226 } 1227 } 1228 catch (final Exception e) 1229 { 1230 Debug.debugException(e); 1231 } 1232 } 1233 1234 1235 // If we've gotten here, then just return null and the tool will try to use 1236 // the default standard schema. 1237 return null; 1238 } 1239 1240 1241 1242 /** 1243 * Creates the entry and change record translators that will be used to 1244 * perform the transformations. 1245 * 1246 * @param entryTranslators A list to which all created entry 1247 * translators should be written. 1248 * @param changeRecordTranslators A list to which all created change record 1249 * translators should be written. 1250 * @param schema The schema to use when processing. 1251 * @param excludedEntryCount A counter used to keep track of the number 1252 * of entries that have been excluded from 1253 * the result set. 1254 */ 1255 private void createTranslators( 1256 final List<LDIFReaderEntryTranslator> entryTranslators, 1257 final List<LDIFReaderChangeRecordTranslator> changeRecordTranslators, 1258 final Schema schema, final AtomicLong excludedEntryCount) 1259 { 1260 if (scrambleAttribute.isPresent()) 1261 { 1262 final Long seed; 1263 if (randomSeed.isPresent()) 1264 { 1265 seed = randomSeed.getValue().longValue(); 1266 } 1267 else 1268 { 1269 seed = null; 1270 } 1271 1272 final ScrambleAttributeTransformation t = 1273 new ScrambleAttributeTransformation(schema, seed, 1274 processDNs.isPresent(), scrambleAttribute.getValues(), 1275 scrambleJSONField.getValues()); 1276 entryTranslators.add(t); 1277 changeRecordTranslators.add(t); 1278 } 1279 1280 if (sequentialAttribute.isPresent()) 1281 { 1282 final long initialValue; 1283 if (initialSequentialValue.isPresent()) 1284 { 1285 initialValue = initialSequentialValue.getValue().longValue(); 1286 } 1287 else 1288 { 1289 initialValue = 0L; 1290 } 1291 1292 final long incrementAmount; 1293 if (sequentialValueIncrement.isPresent()) 1294 { 1295 incrementAmount = sequentialValueIncrement.getValue().longValue(); 1296 } 1297 else 1298 { 1299 incrementAmount = 1L; 1300 } 1301 1302 for (final String attrName : sequentialAttribute.getValues()) 1303 { 1304 1305 1306 final ReplaceWithCounterTransformation t = 1307 new ReplaceWithCounterTransformation(schema, attrName, 1308 initialValue, incrementAmount, 1309 textBeforeSequentialValue.getValue(), 1310 textAfterSequentialValue.getValue(), processDNs.isPresent()); 1311 entryTranslators.add(t); 1312 } 1313 } 1314 1315 if (replaceValuesAttribute.isPresent()) 1316 { 1317 final ReplaceAttributeTransformation t = 1318 new ReplaceAttributeTransformation(schema, 1319 replaceValuesAttribute.getValue(), 1320 replacementValue.getValues()); 1321 entryTranslators.add(t); 1322 } 1323 1324 if (addAttributeName.isPresent()) 1325 { 1326 final AddAttributeTransformation t = new AddAttributeTransformation( 1327 schema, addAttributeBaseDN.getValue(), addAttributeScope.getValue(), 1328 addAttributeFilter.getValue(), 1329 new Attribute(addAttributeName.getValue(), schema, 1330 addAttributeValue.getValues()), 1331 (! addToExistingValues.isPresent())); 1332 entryTranslators.add(t); 1333 } 1334 1335 if (renameAttributeFrom.isPresent()) 1336 { 1337 final Iterator<String> renameFromIterator = 1338 renameAttributeFrom.getValues().iterator(); 1339 final Iterator<String> renameToIterator = 1340 renameAttributeTo.getValues().iterator(); 1341 while (renameFromIterator.hasNext()) 1342 { 1343 final RenameAttributeTransformation t = 1344 new RenameAttributeTransformation(schema, 1345 renameFromIterator.next(), renameToIterator.next(), 1346 processDNs.isPresent()); 1347 entryTranslators.add(t); 1348 changeRecordTranslators.add(t); 1349 } 1350 } 1351 1352 if (flattenBaseDN.isPresent()) 1353 { 1354 final FlattenSubtreeTransformation t = new FlattenSubtreeTransformation( 1355 schema, flattenBaseDN.getValue(), 1356 flattenAddOmittedRDNAttributesToEntry.isPresent(), 1357 flattenAddOmittedRDNAttributesToRDN.isPresent(), 1358 flattenExcludeFilter.getValue()); 1359 entryTranslators.add(t); 1360 } 1361 1362 if (moveSubtreeFrom.isPresent()) 1363 { 1364 final Iterator<DN> moveFromIterator = 1365 moveSubtreeFrom.getValues().iterator(); 1366 final Iterator<DN> moveToIterator = moveSubtreeTo.getValues().iterator(); 1367 while (moveFromIterator.hasNext()) 1368 { 1369 final MoveSubtreeTransformation t = 1370 new MoveSubtreeTransformation(moveFromIterator.next(), 1371 moveToIterator.next()); 1372 entryTranslators.add(t); 1373 changeRecordTranslators.add(t); 1374 } 1375 } 1376 1377 if (redactAttribute.isPresent()) 1378 { 1379 final RedactAttributeTransformation t = new RedactAttributeTransformation( 1380 schema, processDNs.isPresent(), 1381 (! hideRedactedValueCount.isPresent()), redactAttribute.getValues()); 1382 entryTranslators.add(t); 1383 changeRecordTranslators.add(t); 1384 } 1385 1386 if (excludeAttribute.isPresent()) 1387 { 1388 final ExcludeAttributeTransformation t = 1389 new ExcludeAttributeTransformation(schema, 1390 excludeAttribute.getValues()); 1391 entryTranslators.add(t); 1392 changeRecordTranslators.add(t); 1393 } 1394 1395 if (excludeEntryBaseDN.isPresent() || excludeEntryScope.isPresent() || 1396 excludeEntryFilter.isPresent()) 1397 { 1398 final ExcludeEntryTransformation t = new ExcludeEntryTransformation( 1399 schema, excludeEntryBaseDN.getValue(), excludeEntryScope.getValue(), 1400 excludeEntryFilter.getValue(), 1401 (! excludeNonMatchingEntries.isPresent()), excludedEntryCount); 1402 entryTranslators.add(t); 1403 } 1404 1405 entryTranslators.add(this); 1406 } 1407 1408 1409 1410 /** 1411 * {@inheritDoc} 1412 */ 1413 @Override() 1414 public LinkedHashMap<String[],String> getExampleUsages() 1415 { 1416 final LinkedHashMap<String[],String> examples = 1417 new LinkedHashMap<String[],String>(4); 1418 1419 examples.put( 1420 new String[] 1421 { 1422 "--sourceLDIF", "input.ldif", 1423 "--targetLDIF", "scrambled.ldif", 1424 "--scrambleAttribute", "givenName", 1425 "--scrambleAttribute", "sn", 1426 "--scrambleAttribute", "cn", 1427 "--numThreads", "10", 1428 "--schemaPath", "/ds/config/schema", 1429 "--processDNs" 1430 }, 1431 INFO_TRANSFORM_LDIF_EXAMPLE_SCRAMBLE.get()); 1432 1433 examples.put( 1434 new String[] 1435 { 1436 "--sourceLDIF", "input.ldif", 1437 "--targetLDIF", "sequential.ldif", 1438 "--sequentialAttribute", "uid", 1439 "--initialSequentialValue", "1", 1440 "--sequentialValueIncrement", "1", 1441 "--textBeforeSequentialValue", "user.", 1442 "--numThreads", "10", 1443 "--schemaPath", "/ds/config/schema", 1444 "--processDNs" 1445 }, 1446 INFO_TRANSFORM_LDIF_EXAMPLE_SEQUENTIAL.get()); 1447 1448 examples.put( 1449 new String[] 1450 { 1451 "--sourceLDIF", "input.ldif", 1452 "--targetLDIF", "added-organization.ldif", 1453 "--addAttributeName", "o", 1454 "--addAttributeValue", "Example Corp.", 1455 "--addAttributeFilter", "(objectClass=person)", 1456 "--numThreads", "10", 1457 "--schemaPath", "/ds/config/schema" 1458 }, 1459 INFO_TRANSFORM_LDIF_EXAMPLE_ADD.get()); 1460 1461 examples.put( 1462 new String[] 1463 { 1464 "--sourceLDIF", "input.ldif", 1465 "--targetLDIF", "rebased.ldif", 1466 "--moveSubtreeFrom", "o=example.com", 1467 "--moveSubtreeTo", "dc=example,dc=com", 1468 "--numThreads", "10", 1469 "--schemaPath", "/ds/config/schema" 1470 }, 1471 INFO_TRANSFORM_LDIF_EXAMPLE_REBASE.get()); 1472 1473 return examples; 1474 } 1475 1476 1477 1478 /** 1479 * {@inheritDoc} 1480 */ 1481 @Override() 1482 public Entry translate(final Entry original, final long firstLineNumber) 1483 throws LDIFException 1484 { 1485 final ByteStringBuffer buffer = getBuffer(); 1486 if (wrapColumn.isPresent()) 1487 { 1488 original.toLDIF(buffer, wrapColumn.getValue()); 1489 } 1490 else 1491 { 1492 original.toLDIF(buffer, 0); 1493 } 1494 buffer.append(StaticUtils.EOL_BYTES); 1495 1496 return new PreEncodedLDIFEntry(original, buffer.toByteArray()); 1497 } 1498 1499 1500 1501 /** 1502 * Retrieves a byte string buffer that can be used to perform LDIF encoding. 1503 * 1504 * @return A byte string buffer that can be used to perform LDIF encoding. 1505 */ 1506 private ByteStringBuffer getBuffer() 1507 { 1508 ByteStringBuffer buffer = byteStringBuffers.get(); 1509 if (buffer == null) 1510 { 1511 buffer = new ByteStringBuffer(); 1512 byteStringBuffers.set(buffer); 1513 } 1514 else 1515 { 1516 buffer.clear(); 1517 } 1518 1519 return buffer; 1520 } 1521}