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