001/* 002 * Copyright 2007-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-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.ldif; 022 023 024 025import java.io.Closeable; 026import java.io.File; 027import java.io.IOException; 028import java.io.OutputStream; 029import java.io.FileOutputStream; 030import java.io.BufferedOutputStream; 031import java.util.List; 032import java.util.ArrayList; 033import java.util.Arrays; 034 035import com.unboundid.asn1.ASN1OctetString; 036import com.unboundid.ldap.sdk.Entry; 037import com.unboundid.util.Base64; 038import com.unboundid.util.ByteStringBuffer; 039import com.unboundid.util.Debug; 040import com.unboundid.util.LDAPSDKThreadFactory; 041import com.unboundid.util.StaticUtils; 042import com.unboundid.util.ThreadSafety; 043import com.unboundid.util.ThreadSafetyLevel; 044import com.unboundid.util.Validator; 045import com.unboundid.util.parallel.ParallelProcessor; 046import com.unboundid.util.parallel.Result; 047import com.unboundid.util.parallel.Processor; 048 049 050 051/** 052 * This class provides an LDIF writer, which can be used to write entries and 053 * change records in the LDAP Data Interchange Format as per 054 * <A HREF="http://www.ietf.org/rfc/rfc2849.txt">RFC 2849</A>. 055 * <BR><BR> 056 * <H2>Example</H2> 057 * The following example performs a search to find all users in the "Sales" 058 * department and then writes their entries to an LDIF file: 059 * <PRE> 060 * // Perform a search to find all users who are members of the sales 061 * // department. 062 * SearchRequest searchRequest = new SearchRequest("dc=example,dc=com", 063 * SearchScope.SUB, Filter.createEqualityFilter("ou", "Sales")); 064 * SearchResult searchResult; 065 * try 066 * { 067 * searchResult = connection.search(searchRequest); 068 * } 069 * catch (LDAPSearchException lse) 070 * { 071 * searchResult = lse.getSearchResult(); 072 * } 073 * LDAPTestUtils.assertResultCodeEquals(searchResult, ResultCode.SUCCESS); 074 * 075 * // Write all of the matching entries to LDIF. 076 * int entriesWritten = 0; 077 * LDIFWriter ldifWriter = new LDIFWriter(pathToLDIF); 078 * for (SearchResultEntry entry : searchResult.getSearchEntries()) 079 * { 080 * ldifWriter.writeEntry(entry); 081 * entriesWritten++; 082 * } 083 * 084 * ldifWriter.close(); 085 * </PRE> 086 */ 087@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 088public final class LDIFWriter 089 implements Closeable 090{ 091 /** 092 * Indicates whether LDIF records should include a comment above each 093 * base64-encoded value that attempts to provide an unencoded representation 094 * of that value (with special characters escaped). 095 */ 096 private static volatile boolean commentAboutBase64EncodedValues = false; 097 098 099 100 /** 101 * The bytes that comprise the LDIF version header. 102 */ 103 private static final byte[] VERSION_1_HEADER_BYTES = 104 StaticUtils.getBytes("version: 1" + StaticUtils.EOL); 105 106 107 108 /** 109 * The default buffer size (128KB) that will be used when writing LDIF data 110 * to the appropriate destination. 111 */ 112 private static final int DEFAULT_BUFFER_SIZE = 128 * 1024; 113 114 115 116 // The writer that will be used to actually write the data. 117 private final BufferedOutputStream writer; 118 119 // The byte string buffer that will be used to convert LDIF records to LDIF. 120 // It will only be used when operating synchronously. 121 private final ByteStringBuffer buffer; 122 123 // The translator to use for change records to be written, if any. 124 private final LDIFWriterChangeRecordTranslator changeRecordTranslator; 125 126 // The translator to use for entries to be written, if any. 127 private final LDIFWriterEntryTranslator entryTranslator; 128 129 // The column at which to wrap long lines. 130 private int wrapColumn = 0; 131 132 // A pre-computed value that is two less than the wrap column. 133 private int wrapColumnMinusTwo = -2; 134 135 // non-null if this writer was configured to use multiple threads when 136 // writing batches of entries. 137 private final ParallelProcessor<LDIFRecord,ByteStringBuffer> 138 toLdifBytesInvoker; 139 140 141 142 /** 143 * Creates a new LDIF writer that will write entries to the provided file. 144 * 145 * @param path The path to the LDIF file to be written. It must not be 146 * {@code null}. 147 * 148 * @throws IOException If a problem occurs while opening the provided file 149 * for writing. 150 */ 151 public LDIFWriter(final String path) 152 throws IOException 153 { 154 this(new FileOutputStream(path)); 155 } 156 157 158 159 /** 160 * Creates a new LDIF writer that will write entries to the provided file. 161 * 162 * @param file The LDIF file to be written. It must not be {@code null}. 163 * 164 * @throws IOException If a problem occurs while opening the provided file 165 * for writing. 166 */ 167 public LDIFWriter(final File file) 168 throws IOException 169 { 170 this(new FileOutputStream(file)); 171 } 172 173 174 175 /** 176 * Creates a new LDIF writer that will write entries to the provided output 177 * stream. 178 * 179 * @param outputStream The output stream to which the data is to be written. 180 * It must not be {@code null}. 181 */ 182 public LDIFWriter(final OutputStream outputStream) 183 { 184 this(outputStream, 0); 185 } 186 187 188 189 /** 190 * Creates a new LDIF writer that will write entries to the provided output 191 * stream optionally using parallelThreads when writing batches of LDIF 192 * records. 193 * 194 * @param outputStream The output stream to which the data is to be 195 * written. It must not be {@code null}. 196 * @param parallelThreads If this value is greater than zero, then the 197 * specified number of threads will be used to 198 * encode entries before writing them to the output 199 * for the {@code writeLDIFRecords(List)} method. 200 * Note this is the only output method that will 201 * use multiple threads. 202 * This should only be set to greater than zero when 203 * performance analysis has demonstrated that writing 204 * the LDIF is a bottleneck. The default 205 * synchronous processing is normally fast enough. 206 * There is no benefit in passing in a value 207 * greater than the number of processors in the 208 * system. A value of zero implies the 209 * default behavior of reading and parsing LDIF 210 * records synchronously when one of the read 211 * methods is called. 212 */ 213 public LDIFWriter(final OutputStream outputStream, final int parallelThreads) 214 { 215 this(outputStream, parallelThreads, null); 216 } 217 218 219 220 /** 221 * Creates a new LDIF writer that will write entries to the provided output 222 * stream optionally using parallelThreads when writing batches of LDIF 223 * records. 224 * 225 * @param outputStream The output stream to which the data is to be 226 * written. It must not be {@code null}. 227 * @param parallelThreads If this value is greater than zero, then the 228 * specified number of threads will be used to 229 * encode entries before writing them to the output 230 * for the {@code writeLDIFRecords(List)} method. 231 * Note this is the only output method that will 232 * use multiple threads. 233 * This should only be set to greater than zero when 234 * performance analysis has demonstrated that writing 235 * the LDIF is a bottleneck. The default 236 * synchronous processing is normally fast enough. 237 * There is no benefit in passing in a value 238 * greater than the number of processors in the 239 * system. A value of zero implies the 240 * default behavior of reading and parsing LDIF 241 * records synchronously when one of the read 242 * methods is called. 243 * @param entryTranslator An optional translator that will be used to alter 244 * entries before they are actually written. This 245 * may be {@code null} if no translator is needed. 246 */ 247 public LDIFWriter(final OutputStream outputStream, final int parallelThreads, 248 final LDIFWriterEntryTranslator entryTranslator) 249 { 250 this(outputStream, parallelThreads, entryTranslator, null); 251 } 252 253 254 255 /** 256 * Creates a new LDIF writer that will write entries to the provided output 257 * stream optionally using parallelThreads when writing batches of LDIF 258 * records. 259 * 260 * @param outputStream The output stream to which the data is to 261 * be written. It must not be {@code null}. 262 * @param parallelThreads If this value is greater than zero, then 263 * the specified number of threads will be 264 * used to encode entries before writing them 265 * to the output for the 266 * {@code writeLDIFRecords(List)} method. 267 * Note this is the only output method that 268 * will use multiple threads. This should 269 * only be set to greater than zero when 270 * performance analysis has demonstrated that 271 * writing the LDIF is a bottleneck. The 272 * default synchronous processing is normally 273 * fast enough. There is no benefit in 274 * passing in a value greater than the number 275 * of processors in the system. A value of 276 * zero implies the default behavior of 277 * reading and parsing LDIF records 278 * synchronously when one of the read methods 279 * is called. 280 * @param entryTranslator An optional translator that will be used to 281 * alter entries before they are actually 282 * written. This may be {@code null} if no 283 * translator is needed. 284 * @param changeRecordTranslator An optional translator that will be used to 285 * alter change records before they are 286 * actually written. This may be {@code null} 287 * if no translator is needed. 288 */ 289 public LDIFWriter(final OutputStream outputStream, final int parallelThreads, 290 final LDIFWriterEntryTranslator entryTranslator, 291 final LDIFWriterChangeRecordTranslator changeRecordTranslator) 292 { 293 Validator.ensureNotNull(outputStream); 294 Validator.ensureTrue(parallelThreads >= 0, 295 "LDIFWriter.parallelThreads must not be negative."); 296 297 this.entryTranslator = entryTranslator; 298 this.changeRecordTranslator = changeRecordTranslator; 299 buffer = new ByteStringBuffer(); 300 301 if (outputStream instanceof BufferedOutputStream) 302 { 303 writer = (BufferedOutputStream) outputStream; 304 } 305 else 306 { 307 writer = new BufferedOutputStream(outputStream, DEFAULT_BUFFER_SIZE); 308 } 309 310 if (parallelThreads == 0) 311 { 312 toLdifBytesInvoker = null; 313 } 314 else 315 { 316 final LDAPSDKThreadFactory threadFactory = 317 new LDAPSDKThreadFactory("LDIFWriter Worker", true, null); 318 toLdifBytesInvoker = new ParallelProcessor<>( 319 new Processor<LDIFRecord,ByteStringBuffer>() { 320 @Override() 321 public ByteStringBuffer process(final LDIFRecord input) 322 throws IOException 323 { 324 final LDIFRecord r; 325 if ((entryTranslator != null) && (input instanceof Entry)) 326 { 327 r = entryTranslator.translateEntryToWrite((Entry) input); 328 if (r == null) 329 { 330 return null; 331 } 332 } 333 else if ((changeRecordTranslator != null) && 334 (input instanceof LDIFChangeRecord)) 335 { 336 r = changeRecordTranslator.translateChangeRecordToWrite( 337 (LDIFChangeRecord) input); 338 if (r == null) 339 { 340 return null; 341 } 342 } 343 else 344 { 345 r = input; 346 } 347 348 final ByteStringBuffer b = new ByteStringBuffer(200); 349 r.toLDIF(b, wrapColumn); 350 return b; 351 } 352 }, threadFactory, parallelThreads, 5); 353 } 354 } 355 356 357 358 /** 359 * Flushes the output stream used by this LDIF writer to ensure any buffered 360 * data is written out. 361 * 362 * @throws IOException If a problem occurs while attempting to flush the 363 * output stream. 364 */ 365 public void flush() 366 throws IOException 367 { 368 writer.flush(); 369 } 370 371 372 373 /** 374 * Closes this LDIF writer and the underlying LDIF target. 375 * 376 * @throws IOException If a problem occurs while closing the underlying LDIF 377 * target. 378 */ 379 @Override() 380 public void close() 381 throws IOException 382 { 383 try 384 { 385 if (toLdifBytesInvoker != null) 386 { 387 try 388 { 389 toLdifBytesInvoker.shutdown(); 390 } 391 catch (final InterruptedException e) 392 { 393 Debug.debugException(e); 394 Thread.currentThread().interrupt(); 395 } 396 } 397 } 398 finally 399 { 400 writer.close(); 401 } 402 } 403 404 405 406 /** 407 * Retrieves the column at which to wrap long lines. 408 * 409 * @return The column at which to wrap long lines, or zero to indicate that 410 * long lines should not be wrapped. 411 */ 412 public int getWrapColumn() 413 { 414 return wrapColumn; 415 } 416 417 418 419 /** 420 * Specifies the column at which to wrap long lines. A value of zero 421 * indicates that long lines should not be wrapped. 422 * 423 * @param wrapColumn The column at which to wrap long lines. 424 */ 425 public void setWrapColumn(final int wrapColumn) 426 { 427 this.wrapColumn = wrapColumn; 428 429 wrapColumnMinusTwo = wrapColumn - 2; 430 } 431 432 433 434 /** 435 * Indicates whether the LDIF writer should generate comments that attempt to 436 * provide unencoded representations (with special characters escaped) of any 437 * base64-encoded values in entries and change records that are written by 438 * this writer. 439 * 440 * @return {@code true} if the LDIF writer should generate comments that 441 * attempt to provide unencoded representations of any base64-encoded 442 * values, or {@code false} if not. 443 */ 444 public static boolean commentAboutBase64EncodedValues() 445 { 446 return commentAboutBase64EncodedValues; 447 } 448 449 450 451 /** 452 * Specifies whether the LDIF writer should generate comments that attempt to 453 * provide unencoded representations (with special characters escaped) of any 454 * base64-encoded values in entries and change records that are written by 455 * this writer. 456 * 457 * @param commentAboutBase64EncodedValues Indicates whether the LDIF writer 458 * should generate comments that 459 * attempt to provide unencoded 460 * representations (with special 461 * characters escaped) of any 462 * base64-encoded values in entries 463 * and change records that are 464 * written by this writer. 465 */ 466 public static void setCommentAboutBase64EncodedValues( 467 final boolean commentAboutBase64EncodedValues) 468 { 469 LDIFWriter.commentAboutBase64EncodedValues = 470 commentAboutBase64EncodedValues; 471 } 472 473 474 475 /** 476 * Writes the LDIF version header (i.e.,"version: 1"). If a version header 477 * is to be added to the LDIF content, it should be done before any entries or 478 * change records have been written. 479 * 480 * @throws IOException If a problem occurs while writing the version header. 481 */ 482 public void writeVersionHeader() 483 throws IOException 484 { 485 writer.write(VERSION_1_HEADER_BYTES); 486 } 487 488 489 490 /** 491 * Writes the provided entry in LDIF form. 492 * 493 * @param entry The entry to be written. It must not be {@code null}. 494 * 495 * @throws IOException If a problem occurs while writing the LDIF data. 496 */ 497 public void writeEntry(final Entry entry) 498 throws IOException 499 { 500 writeEntry(entry, null); 501 } 502 503 504 505 /** 506 * Writes the provided entry in LDIF form, preceded by the provided comment. 507 * 508 * @param entry The entry to be written in LDIF form. It must not be 509 * {@code null}. 510 * @param comment The comment to be written before the entry. It may be 511 * {@code null} if no comment is to be written. 512 * 513 * @throws IOException If a problem occurs while writing the LDIF data. 514 */ 515 public void writeEntry(final Entry entry, final String comment) 516 throws IOException 517 { 518 Validator.ensureNotNull(entry); 519 520 final Entry e; 521 if (entryTranslator == null) 522 { 523 e = entry; 524 } 525 else 526 { 527 e = entryTranslator.translateEntryToWrite(entry); 528 if (e == null) 529 { 530 return; 531 } 532 } 533 534 if (comment != null) 535 { 536 writeComment(comment, false, false); 537 } 538 539 Debug.debugLDIFWrite(e); 540 writeLDIF(e); 541 } 542 543 544 545 /** 546 * Writes the provided change record in LDIF form. 547 * 548 * @param changeRecord The change record to be written. It must not be 549 * {@code null}. 550 * 551 * @throws IOException If a problem occurs while writing the LDIF data. 552 */ 553 public void writeChangeRecord(final LDIFChangeRecord changeRecord) 554 throws IOException 555 { 556 writeChangeRecord(changeRecord, null); 557 } 558 559 560 561 /** 562 * Writes the provided change record in LDIF form, preceded by the provided 563 * comment. 564 * 565 * @param changeRecord The change record to be written. It must not be 566 * {@code null}. 567 * @param comment The comment to be written before the entry. It may 568 * be {@code null} if no comment is to be written. 569 * 570 * @throws IOException If a problem occurs while writing the LDIF data. 571 */ 572 public void writeChangeRecord(final LDIFChangeRecord changeRecord, 573 final String comment) 574 throws IOException 575 { 576 Validator.ensureNotNull(changeRecord); 577 578 final LDIFChangeRecord r; 579 if (changeRecordTranslator == null) 580 { 581 r = changeRecord; 582 } 583 else 584 { 585 r = changeRecordTranslator.translateChangeRecordToWrite(changeRecord); 586 if (r == null) 587 { 588 return; 589 } 590 } 591 592 if (comment != null) 593 { 594 writeComment(comment, false, false); 595 } 596 597 Debug.debugLDIFWrite(r); 598 writeLDIF(r); 599 } 600 601 602 603 /** 604 * Writes the provided record in LDIF form. 605 * 606 * @param record The LDIF record to be written. It must not be 607 * {@code null}. 608 * 609 * @throws IOException If a problem occurs while writing the LDIF data. 610 */ 611 public void writeLDIFRecord(final LDIFRecord record) 612 throws IOException 613 { 614 writeLDIFRecord(record, null); 615 } 616 617 618 619 /** 620 * Writes the provided record in LDIF form, preceded by the provided comment. 621 * 622 * @param record The LDIF record to be written. It must not be 623 * {@code null}. 624 * @param comment The comment to be written before the LDIF record. It may 625 * be {@code null} if no comment is to be written. 626 * 627 * @throws IOException If a problem occurs while writing the LDIF data. 628 */ 629 public void writeLDIFRecord(final LDIFRecord record, final String comment) 630 throws IOException 631 { 632 633 Validator.ensureNotNull(record); 634 final LDIFRecord r; 635 if ((entryTranslator != null) && (record instanceof Entry)) 636 { 637 r = entryTranslator.translateEntryToWrite((Entry) record); 638 if (r == null) 639 { 640 return; 641 } 642 } 643 else if ((changeRecordTranslator != null) && 644 (record instanceof LDIFChangeRecord)) 645 { 646 r = changeRecordTranslator.translateChangeRecordToWrite( 647 (LDIFChangeRecord) record); 648 if (r == null) 649 { 650 return; 651 } 652 } 653 else 654 { 655 r = record; 656 } 657 658 Debug.debugLDIFWrite(r); 659 if (comment != null) 660 { 661 writeComment(comment, false, false); 662 } 663 664 writeLDIF(r); 665 } 666 667 668 669 /** 670 * Writes the provided list of LDIF records (most likely Entries) to the 671 * output. If this LDIFWriter was constructed without any parallel 672 * output threads, then this behaves identically to calling 673 * {@code writeLDIFRecord()} sequentially for each item in the list. 674 * If this LDIFWriter was constructed to write records in parallel, then 675 * the configured number of threads are used to convert the records to raw 676 * bytes, which are sequentially written to the input file. This can speed up 677 * the total time to write a large set of records. Either way, the output 678 * records are guaranteed to be written in the order they appear in the list. 679 * 680 * @param ldifRecords The LDIF records (most likely entries) to write to the 681 * output. 682 * 683 * @throws IOException If a problem occurs while writing the LDIF data. 684 * 685 * @throws InterruptedException If this thread is interrupted while waiting 686 * for the records to be written to the output. 687 */ 688 public void writeLDIFRecords(final List<? extends LDIFRecord> ldifRecords) 689 throws IOException, InterruptedException 690 { 691 if (toLdifBytesInvoker == null) 692 { 693 for (final LDIFRecord ldifRecord : ldifRecords) 694 { 695 writeLDIFRecord(ldifRecord); 696 } 697 } 698 else 699 { 700 final List<Result<LDIFRecord,ByteStringBuffer>> results = 701 toLdifBytesInvoker.processAll(ldifRecords); 702 for (final Result<LDIFRecord,ByteStringBuffer> result: results) 703 { 704 rethrow(result.getFailureCause()); 705 706 final ByteStringBuffer encodedBytes = result.getOutput(); 707 if (encodedBytes != null) 708 { 709 encodedBytes.write(writer); 710 writer.write(StaticUtils.EOL_BYTES); 711 } 712 } 713 } 714 } 715 716 717 718 /** 719 * Writes the provided comment to the LDIF target, wrapping long lines as 720 * necessary. 721 * 722 * @param comment The comment to be written to the LDIF target. It must 723 * not be {@code null}. 724 * @param spaceBefore Indicates whether to insert a blank line before the 725 * comment. 726 * @param spaceAfter Indicates whether to insert a blank line after the 727 * comment. 728 * 729 * @throws IOException If a problem occurs while writing the LDIF data. 730 */ 731 public void writeComment(final String comment, final boolean spaceBefore, 732 final boolean spaceAfter) 733 throws IOException 734 { 735 Validator.ensureNotNull(comment); 736 if (spaceBefore) 737 { 738 writer.write(StaticUtils.EOL_BYTES); 739 } 740 741 // 742 // Check for a newline explicitly to avoid the overhead of the regex 743 // for the common case of a single-line comment. 744 // 745 746 if (comment.indexOf('\n') < 0) 747 { 748 writeSingleLineComment(comment); 749 } 750 else 751 { 752 // 753 // Split on blank lines and wrap each line individually. 754 // 755 756 final String[] lines = comment.split("\\r?\\n"); 757 for (final String line: lines) 758 { 759 writeSingleLineComment(line); 760 } 761 } 762 763 if (spaceAfter) 764 { 765 writer.write(StaticUtils.EOL_BYTES); 766 } 767 } 768 769 770 771 /** 772 * Writes the provided comment to the LDIF target, wrapping long lines as 773 * necessary. 774 * 775 * @param comment The comment to be written to the LDIF target. It must 776 * not be {@code null}, and it must not include any line 777 * breaks. 778 * 779 * @throws IOException If a problem occurs while writing the LDIF data. 780 */ 781 private void writeSingleLineComment(final String comment) 782 throws IOException 783 { 784 // We will always wrap comments, even if we won't wrap LDIF entries. If 785 // there is a wrap column set, then use it. Otherwise use the terminal 786 // width and back off two characters for the "# " at the beginning. 787 final int commentWrapMinusTwo; 788 if (wrapColumn <= 0) 789 { 790 commentWrapMinusTwo = StaticUtils.TERMINAL_WIDTH_COLUMNS - 3; 791 } 792 else 793 { 794 commentWrapMinusTwo = wrapColumnMinusTwo; 795 } 796 797 buffer.clear(); 798 final int length = comment.length(); 799 if (length <= commentWrapMinusTwo) 800 { 801 buffer.append("# "); 802 buffer.append(comment); 803 buffer.append(StaticUtils.EOL_BYTES); 804 } 805 else 806 { 807 int minPos = 0; 808 while (minPos < length) 809 { 810 if ((length - minPos) <= commentWrapMinusTwo) 811 { 812 buffer.append("# "); 813 buffer.append(comment.substring(minPos)); 814 buffer.append(StaticUtils.EOL_BYTES); 815 break; 816 } 817 818 // First, adjust the position until we find a space. Go backwards if 819 // possible, but if we can't find one there then go forward. 820 boolean spaceFound = false; 821 final int pos = minPos + commentWrapMinusTwo; 822 int spacePos = pos; 823 while (spacePos > minPos) 824 { 825 if (comment.charAt(spacePos) == ' ') 826 { 827 spaceFound = true; 828 break; 829 } 830 831 spacePos--; 832 } 833 834 if (! spaceFound) 835 { 836 spacePos = pos + 1; 837 while (spacePos < length) 838 { 839 if (comment.charAt(spacePos) == ' ') 840 { 841 spaceFound = true; 842 break; 843 } 844 845 spacePos++; 846 } 847 848 if (! spaceFound) 849 { 850 // There are no spaces at all in the remainder of the comment, so 851 // we'll just write the remainder of it all at once. 852 buffer.append("# "); 853 buffer.append(comment.substring(minPos)); 854 buffer.append(StaticUtils.EOL_BYTES); 855 break; 856 } 857 } 858 859 // We have a space, so we'll write up to the space position and then 860 // start up after the next space. 861 buffer.append("# "); 862 buffer.append(comment.substring(minPos, spacePos)); 863 buffer.append(StaticUtils.EOL_BYTES); 864 865 minPos = spacePos + 1; 866 while ((minPos < length) && (comment.charAt(minPos) == ' ')) 867 { 868 minPos++; 869 } 870 } 871 } 872 873 buffer.write(writer); 874 } 875 876 877 878 /** 879 * Writes the provided record to the LDIF target, wrapping long lines as 880 * necessary. 881 * 882 * @param record The LDIF record to be written. 883 * 884 * @throws IOException If a problem occurs while writing the LDIF data. 885 */ 886 private void writeLDIF(final LDIFRecord record) 887 throws IOException 888 { 889 buffer.clear(); 890 record.toLDIF(buffer, wrapColumn); 891 buffer.append(StaticUtils.EOL_BYTES); 892 buffer.write(writer); 893 } 894 895 896 897 /** 898 * Performs any appropriate wrapping for the provided set of LDIF lines. 899 * 900 * @param wrapColumn The column at which to wrap long lines. A value that 901 * is less than or equal to two indicates that no 902 * wrapping should be performed. 903 * @param ldifLines The set of lines that make up the LDIF data to be 904 * wrapped. 905 * 906 * @return A new list of lines that have been wrapped as appropriate. 907 */ 908 public static List<String> wrapLines(final int wrapColumn, 909 final String... ldifLines) 910 { 911 return wrapLines(wrapColumn, Arrays.asList(ldifLines)); 912 } 913 914 915 916 /** 917 * Performs any appropriate wrapping for the provided set of LDIF lines. 918 * 919 * @param wrapColumn The column at which to wrap long lines. A value that 920 * is less than or equal to two indicates that no 921 * wrapping should be performed. 922 * @param ldifLines The set of lines that make up the LDIF data to be 923 * wrapped. 924 * 925 * @return A new list of lines that have been wrapped as appropriate. 926 */ 927 public static List<String> wrapLines(final int wrapColumn, 928 final List<String> ldifLines) 929 { 930 if (wrapColumn <= 2) 931 { 932 return new ArrayList<>(ldifLines); 933 } 934 935 final ArrayList<String> newLines = new ArrayList<>(ldifLines.size()); 936 for (final String s : ldifLines) 937 { 938 final int length = s.length(); 939 if (length <= wrapColumn) 940 { 941 newLines.add(s); 942 continue; 943 } 944 945 newLines.add(s.substring(0, wrapColumn)); 946 947 int pos = wrapColumn; 948 while (pos < length) 949 { 950 if ((length - pos + 1) <= wrapColumn) 951 { 952 newLines.add(' ' + s.substring(pos)); 953 break; 954 } 955 else 956 { 957 newLines.add(' ' + s.substring(pos, (pos+wrapColumn-1))); 958 pos += wrapColumn - 1; 959 } 960 } 961 } 962 963 return newLines; 964 } 965 966 967 968 /** 969 * Creates a string consisting of the provided attribute name followed by 970 * either a single colon and the string representation of the provided value, 971 * or two colons and the base64-encoded representation of the provided value. 972 * 973 * @param name The name for the attribute. 974 * @param value The value for the attribute. 975 * 976 * @return A string consisting of the provided attribute name followed by 977 * either a single colon and the string representation of the 978 * provided value, or two colons and the base64-encoded 979 * representation of the provided value. 980 */ 981 public static String encodeNameAndValue(final String name, 982 final ASN1OctetString value) 983 { 984 final StringBuilder buffer = new StringBuilder(); 985 encodeNameAndValue(name, value, buffer); 986 return buffer.toString(); 987 } 988 989 990 991 /** 992 * Appends a string to the provided buffer consisting of the provided 993 * attribute name followed by either a single colon and the string 994 * representation of the provided value, or two colons and the base64-encoded 995 * representation of the provided value. 996 * 997 * @param name The name for the attribute. 998 * @param value The value for the attribute. 999 * @param buffer The buffer to which the name and value are to be written. 1000 */ 1001 public static void encodeNameAndValue(final String name, 1002 final ASN1OctetString value, 1003 final StringBuilder buffer) 1004 { 1005 encodeNameAndValue(name, value, buffer, 0); 1006 } 1007 1008 1009 1010 /** 1011 * Appends a string to the provided buffer consisting of the provided 1012 * attribute name followed by either a single colon and the string 1013 * representation of the provided value, or two colons and the base64-encoded 1014 * representation of the provided value. 1015 * 1016 * @param name The name for the attribute. 1017 * @param value The value for the attribute. 1018 * @param buffer The buffer to which the name and value are to be 1019 * written. 1020 * @param wrapColumn The column at which to wrap long lines. A value that 1021 * is less than or equal to two indicates that no 1022 * wrapping should be performed. 1023 */ 1024 public static void encodeNameAndValue(final String name, 1025 final ASN1OctetString value, 1026 final StringBuilder buffer, 1027 final int wrapColumn) 1028 { 1029 final int bufferStartPos = buffer.length(); 1030 final byte[] valueBytes = value.getValue(); 1031 boolean base64Encoded = false; 1032 1033 try 1034 { 1035 buffer.append(name); 1036 buffer.append(':'); 1037 1038 final int length = valueBytes.length; 1039 if (length == 0) 1040 { 1041 buffer.append(' '); 1042 return; 1043 } 1044 1045 // If the value starts with a space, colon, or less-than character, then 1046 // it must be base64-encoded. 1047 switch (valueBytes[0]) 1048 { 1049 case ' ': 1050 case ':': 1051 case '<': 1052 buffer.append(": "); 1053 Base64.encode(valueBytes, buffer); 1054 base64Encoded = true; 1055 return; 1056 } 1057 1058 // If the value ends with a space, then it should be base64-encoded. 1059 if (valueBytes[length-1] == ' ') 1060 { 1061 buffer.append(": "); 1062 Base64.encode(valueBytes, buffer); 1063 base64Encoded = true; 1064 return; 1065 } 1066 1067 // If any character in the value is outside the ASCII range, or is the 1068 // NUL, LF, or CR character, then the value should be base64-encoded. 1069 for (int i=0; i < length; i++) 1070 { 1071 if ((valueBytes[i] & 0x7F) != (valueBytes[i] & 0xFF)) 1072 { 1073 buffer.append(": "); 1074 Base64.encode(valueBytes, buffer); 1075 base64Encoded = true; 1076 return; 1077 } 1078 1079 switch (valueBytes[i]) 1080 { 1081 case 0x00: // The NUL character 1082 case 0x0A: // The LF character 1083 case 0x0D: // The CR character 1084 buffer.append(": "); 1085 Base64.encode(valueBytes, buffer); 1086 base64Encoded = true; 1087 return; 1088 } 1089 } 1090 1091 // If we've gotten here, then the string value is acceptable. 1092 buffer.append(' '); 1093 buffer.append(value.stringValue()); 1094 } 1095 finally 1096 { 1097 if (wrapColumn > 2) 1098 { 1099 final int length = buffer.length() - bufferStartPos; 1100 if (length > wrapColumn) 1101 { 1102 final String EOL_PLUS_SPACE = StaticUtils.EOL + ' '; 1103 buffer.insert((bufferStartPos+wrapColumn), EOL_PLUS_SPACE); 1104 1105 int pos = bufferStartPos + (2*wrapColumn) + 1106 EOL_PLUS_SPACE.length() - 1; 1107 while (pos < buffer.length()) 1108 { 1109 buffer.insert(pos, EOL_PLUS_SPACE); 1110 pos += (wrapColumn - 1 + EOL_PLUS_SPACE.length()); 1111 } 1112 } 1113 } 1114 1115 if (base64Encoded && commentAboutBase64EncodedValues) 1116 { 1117 writeBase64DecodedValueComment(valueBytes, buffer, wrapColumn); 1118 } 1119 } 1120 } 1121 1122 1123 1124 /** 1125 * Appends a comment to the provided buffer with an unencoded representation 1126 * of the provided value. This will only have any effect if 1127 * {@code commentAboutBase64EncodedValues} is {@code true}. 1128 * 1129 * @param valueBytes The bytes that comprise the value. 1130 * @param buffer The buffer to which the comment should be appended. 1131 * @param wrapColumn The column at which to wrap long lines. 1132 */ 1133 private static void writeBase64DecodedValueComment(final byte[] valueBytes, 1134 final StringBuilder buffer, 1135 final int wrapColumn) 1136 { 1137 if (commentAboutBase64EncodedValues) 1138 { 1139 final int wrapColumnMinusTwo; 1140 if (wrapColumn <= 5) 1141 { 1142 wrapColumnMinusTwo = StaticUtils.TERMINAL_WIDTH_COLUMNS - 3; 1143 } 1144 else 1145 { 1146 wrapColumnMinusTwo = wrapColumn - 2; 1147 } 1148 1149 final int wrapColumnMinusThree = wrapColumnMinusTwo - 1; 1150 1151 boolean first = true; 1152 final String comment = 1153 "Non-base64-encoded representation of the above value: " + 1154 getEscapedValue(valueBytes); 1155 for (final String s : 1156 StaticUtils.wrapLine(comment, wrapColumnMinusTwo, 1157 wrapColumnMinusThree)) 1158 { 1159 buffer.append(StaticUtils.EOL); 1160 buffer.append("# "); 1161 if (first) 1162 { 1163 first = false; 1164 } 1165 else 1166 { 1167 buffer.append(' '); 1168 } 1169 buffer.append(s); 1170 } 1171 } 1172 } 1173 1174 1175 1176 /** 1177 * Appends a string to the provided buffer consisting of the provided 1178 * attribute name followed by either a single colon and the string 1179 * representation of the provided value, or two colons and the base64-encoded 1180 * representation of the provided value. It may optionally be wrapped at the 1181 * specified column. 1182 * 1183 * @param name The name for the attribute. 1184 * @param value The value for the attribute. 1185 * @param buffer The buffer to which the name and value are to be 1186 * written. 1187 * @param wrapColumn The column at which to wrap long lines. A value that 1188 * is less than or equal to two indicates that no 1189 * wrapping should be performed. 1190 */ 1191 public static void encodeNameAndValue(final String name, 1192 final ASN1OctetString value, 1193 final ByteStringBuffer buffer, 1194 final int wrapColumn) 1195 { 1196 final int bufferStartPos = buffer.length(); 1197 boolean base64Encoded = false; 1198 1199 try 1200 { 1201 buffer.append(name); 1202 base64Encoded = encodeValue(value, buffer); 1203 } 1204 finally 1205 { 1206 if (wrapColumn > 2) 1207 { 1208 final int length = buffer.length() - bufferStartPos; 1209 if (length > wrapColumn) 1210 { 1211 final byte[] EOL_BYTES_PLUS_SPACE = 1212 new byte[StaticUtils.EOL_BYTES.length + 1]; 1213 System.arraycopy(StaticUtils.EOL_BYTES, 0, EOL_BYTES_PLUS_SPACE, 0, 1214 StaticUtils.EOL_BYTES.length); 1215 EOL_BYTES_PLUS_SPACE[StaticUtils.EOL_BYTES.length] = ' '; 1216 1217 buffer.insert((bufferStartPos+wrapColumn), EOL_BYTES_PLUS_SPACE); 1218 1219 int pos = bufferStartPos + (2*wrapColumn) + 1220 EOL_BYTES_PLUS_SPACE.length - 1; 1221 while (pos < buffer.length()) 1222 { 1223 buffer.insert(pos, EOL_BYTES_PLUS_SPACE); 1224 pos += (wrapColumn - 1 + EOL_BYTES_PLUS_SPACE.length); 1225 } 1226 } 1227 } 1228 1229 if (base64Encoded && commentAboutBase64EncodedValues) 1230 { 1231 writeBase64DecodedValueComment(value.getValue(), buffer, wrapColumn); 1232 } 1233 } 1234 } 1235 1236 1237 1238 /** 1239 * Appends a string to the provided buffer consisting of the properly-encoded 1240 * representation of the provided value, including the necessary colon(s) and 1241 * space that precede it. Depending on the content of the value, it will 1242 * either be used as-is or base64-encoded. 1243 * 1244 * @param value The value for the attribute. 1245 * @param buffer The buffer to which the value is to be written. 1246 * 1247 * @return {@code true} if the value was base64-encoded, or {@code false} if 1248 * not. 1249 */ 1250 static boolean encodeValue(final ASN1OctetString value, 1251 final ByteStringBuffer buffer) 1252 { 1253 buffer.append(':'); 1254 1255 final byte[] valueBytes = value.getValue(); 1256 final int length = valueBytes.length; 1257 if (length == 0) 1258 { 1259 buffer.append(' '); 1260 return false; 1261 } 1262 1263 // If the value starts with a space, colon, or less-than character, then 1264 // it must be base64-encoded. 1265 switch (valueBytes[0]) 1266 { 1267 case ' ': 1268 case ':': 1269 case '<': 1270 buffer.append(':'); 1271 buffer.append(' '); 1272 Base64.encode(valueBytes, buffer); 1273 return true; 1274 } 1275 1276 // If the value ends with a space, then it should be base64-encoded. 1277 if (valueBytes[length-1] == ' ') 1278 { 1279 buffer.append(':'); 1280 buffer.append(' '); 1281 Base64.encode(valueBytes, buffer); 1282 return true; 1283 } 1284 1285 // If any character in the value is outside the ASCII range, or is the 1286 // NUL, LF, or CR character, then the value should be base64-encoded. 1287 for (int i=0; i < length; i++) 1288 { 1289 if ((valueBytes[i] & 0x7F) != (valueBytes[i] & 0xFF)) 1290 { 1291 buffer.append(':'); 1292 buffer.append(' '); 1293 Base64.encode(valueBytes, buffer); 1294 return true; 1295 } 1296 1297 switch (valueBytes[i]) 1298 { 1299 case 0x00: // The NUL character 1300 case 0x0A: // The LF character 1301 case 0x0D: // The CR character 1302 buffer.append(':'); 1303 buffer.append(' '); 1304 1305 Base64.encode(valueBytes, buffer); 1306 return true; 1307 } 1308 } 1309 1310 // If we've gotten here, then the string value is acceptable. 1311 buffer.append(' '); 1312 buffer.append(valueBytes); 1313 return false; 1314 } 1315 1316 1317 1318 /** 1319 * Appends a comment to the provided buffer with an unencoded representation 1320 * of the provided value. This will only have any effect if 1321 * {@code commentAboutBase64EncodedValues} is {@code true}. 1322 * 1323 * @param valueBytes The bytes that comprise the value. 1324 * @param buffer The buffer to which the comment should be appended. 1325 * @param wrapColumn The column at which to wrap long lines. 1326 */ 1327 private static void writeBase64DecodedValueComment(final byte[] valueBytes, 1328 final ByteStringBuffer buffer, 1329 final int wrapColumn) 1330 { 1331 if (commentAboutBase64EncodedValues) 1332 { 1333 final int wrapColumnMinusTwo; 1334 if (wrapColumn <= 5) 1335 { 1336 wrapColumnMinusTwo = StaticUtils.TERMINAL_WIDTH_COLUMNS - 3; 1337 } 1338 else 1339 { 1340 wrapColumnMinusTwo = wrapColumn - 2; 1341 } 1342 1343 final int wrapColumnMinusThree = wrapColumnMinusTwo - 1; 1344 1345 boolean first = true; 1346 final String comment = 1347 "Non-base64-encoded representation of the above value: " + 1348 getEscapedValue(valueBytes); 1349 for (final String s : 1350 StaticUtils.wrapLine(comment, wrapColumnMinusTwo, 1351 wrapColumnMinusThree)) 1352 { 1353 buffer.append(StaticUtils.EOL); 1354 buffer.append("# "); 1355 if (first) 1356 { 1357 first = false; 1358 } 1359 else 1360 { 1361 buffer.append(' '); 1362 } 1363 buffer.append(s); 1364 } 1365 } 1366 } 1367 1368 1369 1370 /** 1371 * Retrieves a string representation of the provided value with all special 1372 * characters escaped with backslashes. 1373 * 1374 * @param valueBytes The byte array containing the value to encode. 1375 * 1376 * @return A string representation of the provided value with any special 1377 * characters 1378 */ 1379 private static String getEscapedValue(final byte[] valueBytes) 1380 { 1381 final StringBuilder buffer = new StringBuilder(valueBytes.length * 2); 1382 for (int i=0; i < valueBytes.length; i++) 1383 { 1384 final byte b = valueBytes[i]; 1385 switch (b) 1386 { 1387 case '\n': 1388 buffer.append("\\n"); 1389 break; 1390 case '\r': 1391 buffer.append("\\r"); 1392 break; 1393 case '\t': 1394 buffer.append("\\t"); 1395 break; 1396 case ' ': 1397 if (i == 0) 1398 { 1399 buffer.append("\\ "); 1400 } 1401 else if ( i == (valueBytes.length - 1)) 1402 { 1403 buffer.append("\\20"); 1404 } 1405 else 1406 { 1407 buffer.append(' '); 1408 } 1409 break; 1410 case '<': 1411 if (i == 0) 1412 { 1413 buffer.append('\\'); 1414 } 1415 buffer.append('<'); 1416 break; 1417 case ':': 1418 if (i == 0) 1419 { 1420 buffer.append('\\'); 1421 } 1422 buffer.append(':'); 1423 break; 1424 default: 1425 if ((b >= '!') && (b <= '~')) 1426 { 1427 buffer.append((char) b); 1428 } 1429 else 1430 { 1431 buffer.append("\\"); 1432 StaticUtils.toHex(b, buffer); 1433 } 1434 break; 1435 } 1436 } 1437 1438 return buffer.toString(); 1439 } 1440 1441 1442 1443 /** 1444 * If the provided exception is non-null, then it will be rethrown as an 1445 * unchecked exception or an IOException. 1446 * 1447 * @param t The exception to rethrow as an an unchecked exception or an 1448 * IOException or {@code null} if none. 1449 * 1450 * @throws IOException If t is a checked exception. 1451 */ 1452 static void rethrow(final Throwable t) 1453 throws IOException 1454 { 1455 if (t == null) 1456 { 1457 return; 1458 } 1459 1460 if (t instanceof IOException) 1461 { 1462 throw (IOException) t; 1463 } 1464 else if (t instanceof RuntimeException) 1465 { 1466 throw (RuntimeException) t; 1467 } 1468 else if (t instanceof Error) 1469 { 1470 throw (Error) t; 1471 } 1472 else 1473 { 1474 throw new IOException(t); 1475 } 1476 } 1477}