001/* 002 * Copyright 2007-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2017 UnboundID Corp. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021package com.unboundid.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.LDAPSDKThreadFactory; 039import com.unboundid.util.ThreadSafety; 040import com.unboundid.util.ThreadSafetyLevel; 041import com.unboundid.util.ByteStringBuffer; 042import com.unboundid.util.parallel.ParallelProcessor; 043import com.unboundid.util.parallel.Result; 044import com.unboundid.util.parallel.Processor; 045 046import static com.unboundid.util.Debug.*; 047import static com.unboundid.util.StaticUtils.*; 048import static com.unboundid.util.Validator.*; 049 050 051 052/** 053 * This class provides an LDIF writer, which can be used to write entries and 054 * change records in the LDAP Data Interchange Format as per 055 * <A HREF="http://www.ietf.org/rfc/rfc2849.txt">RFC 2849</A>. 056 * <BR><BR> 057 * <H2>Example</H2> 058 * The following example performs a search to find all users in the "Sales" 059 * department and then writes their entries to an LDIF file: 060 * <PRE> 061 * // Perform a search to find all users who are members of the sales 062 * // department. 063 * SearchRequest searchRequest = new SearchRequest("dc=example,dc=com", 064 * SearchScope.SUB, Filter.createEqualityFilter("ou", "Sales")); 065 * SearchResult searchResult; 066 * try 067 * { 068 * searchResult = connection.search(searchRequest); 069 * } 070 * catch (LDAPSearchException lse) 071 * { 072 * searchResult = lse.getSearchResult(); 073 * } 074 * LDAPTestUtils.assertResultCodeEquals(searchResult, ResultCode.SUCCESS); 075 * 076 * // Write all of the matching entries to LDIF. 077 * int entriesWritten = 0; 078 * LDIFWriter ldifWriter = new LDIFWriter(pathToLDIF); 079 * for (SearchResultEntry entry : searchResult.getSearchEntries()) 080 * { 081 * ldifWriter.writeEntry(entry); 082 * entriesWritten++; 083 * } 084 * 085 * ldifWriter.close(); 086 * </PRE> 087 */ 088@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 089public final class LDIFWriter 090 implements Closeable 091{ 092 /** 093 * Indicates whether LDIF records should include a comment above each 094 * base64-encoded value that attempts to provide an unencoded representation 095 * of that value (with special characters escaped). 096 */ 097 private static volatile boolean commentAboutBase64EncodedValues = false; 098 099 100 101 /** 102 * The bytes that comprise the LDIF version header. 103 */ 104 private static final byte[] VERSION_1_HEADER_BYTES = 105 getBytes("version: 1" + EOL); 106 107 108 109 /** 110 * The default buffer size (128KB) that will be used when writing LDIF data 111 * to the appropriate destination. 112 */ 113 private static final int DEFAULT_BUFFER_SIZE = 128 * 1024; 114 115 116 117 // The writer that will be used to actually write the data. 118 private final BufferedOutputStream writer; 119 120 // The byte string buffer that will be used to convert LDIF records to LDIF. 121 // It will only be used when operating synchronously. 122 private final ByteStringBuffer buffer; 123 124 // The translator to use for change records to be written, if any. 125 private final LDIFWriterChangeRecordTranslator changeRecordTranslator; 126 127 // The translator to use for entries to be written, if any. 128 private final LDIFWriterEntryTranslator entryTranslator; 129 130 // The column at which to wrap long lines. 131 private int wrapColumn = 0; 132 133 // A pre-computed value that is two less than the wrap column. 134 private int wrapColumnMinusTwo = -2; 135 136 // non-null if this writer was configured to use multiple threads when 137 // writing batches of entries. 138 private final ParallelProcessor<LDIFRecord,ByteStringBuffer> 139 toLdifBytesInvoker; 140 141 142 143 /** 144 * Creates a new LDIF writer that will write entries to the provided file. 145 * 146 * @param path The path to the LDIF file to be written. It must not be 147 * {@code null}. 148 * 149 * @throws IOException If a problem occurs while opening the provided file 150 * for writing. 151 */ 152 public LDIFWriter(final String path) 153 throws IOException 154 { 155 this(new FileOutputStream(path)); 156 } 157 158 159 160 /** 161 * Creates a new LDIF writer that will write entries to the provided file. 162 * 163 * @param file The LDIF file to be written. It must not be {@code null}. 164 * 165 * @throws IOException If a problem occurs while opening the provided file 166 * for writing. 167 */ 168 public LDIFWriter(final File file) 169 throws IOException 170 { 171 this(new FileOutputStream(file)); 172 } 173 174 175 176 /** 177 * Creates a new LDIF writer that will write entries to the provided output 178 * stream. 179 * 180 * @param outputStream The output stream to which the data is to be written. 181 * It must not be {@code null}. 182 */ 183 public LDIFWriter(final OutputStream outputStream) 184 { 185 this(outputStream, 0); 186 } 187 188 189 190 /** 191 * Creates a new LDIF writer that will write entries to the provided output 192 * stream optionally using parallelThreads when writing batches of LDIF 193 * records. 194 * 195 * @param outputStream The output stream to which the data is to be 196 * written. It must not be {@code null}. 197 * @param parallelThreads If this value is greater than zero, then the 198 * specified number of threads will be used to 199 * encode entries before writing them to the output 200 * for the {@code writeLDIFRecords(List)} method. 201 * Note this is the only output method that will 202 * use multiple threads. 203 * This should only be set to greater than zero when 204 * performance analysis has demonstrated that writing 205 * the LDIF is a bottleneck. The default 206 * synchronous processing is normally fast enough. 207 * There is no benefit in passing in a value 208 * greater than the number of processors in the 209 * system. A value of zero implies the 210 * default behavior of reading and parsing LDIF 211 * records synchronously when one of the read 212 * methods is called. 213 */ 214 public LDIFWriter(final OutputStream outputStream, final int parallelThreads) 215 { 216 this(outputStream, parallelThreads, null); 217 } 218 219 220 221 /** 222 * Creates a new LDIF writer that will write entries to the provided output 223 * stream optionally using parallelThreads when writing batches of LDIF 224 * records. 225 * 226 * @param outputStream The output stream to which the data is to be 227 * written. It must not be {@code null}. 228 * @param parallelThreads If this value is greater than zero, then the 229 * specified number of threads will be used to 230 * encode entries before writing them to the output 231 * for the {@code writeLDIFRecords(List)} method. 232 * Note this is the only output method that will 233 * use multiple threads. 234 * This should only be set to greater than zero when 235 * performance analysis has demonstrated that writing 236 * the LDIF is a bottleneck. The default 237 * synchronous processing is normally fast enough. 238 * There is no benefit in passing in a value 239 * greater than the number of processors in the 240 * system. A value of zero implies the 241 * default behavior of reading and parsing LDIF 242 * records synchronously when one of the read 243 * methods is called. 244 * @param entryTranslator An optional translator that will be used to alter 245 * entries before they are actually written. This 246 * may be {@code null} if no translator is needed. 247 */ 248 public LDIFWriter(final OutputStream outputStream, final int parallelThreads, 249 final LDIFWriterEntryTranslator entryTranslator) 250 { 251 this(outputStream, parallelThreads, entryTranslator, null); 252 } 253 254 255 256 /** 257 * Creates a new LDIF writer that will write entries to the provided output 258 * stream optionally using parallelThreads when writing batches of LDIF 259 * records. 260 * 261 * @param outputStream The output stream to which the data is to 262 * be written. It must not be {@code null}. 263 * @param parallelThreads If this value is greater than zero, then 264 * the specified number of threads will be 265 * used to encode entries before writing them 266 * to the output for the 267 * {@code writeLDIFRecords(List)} method. 268 * Note this is the only output method that 269 * will use multiple threads. This should 270 * only be set to greater than zero when 271 * performance analysis has demonstrated that 272 * writing the LDIF is a bottleneck. The 273 * default synchronous processing is normally 274 * fast enough. There is no benefit in 275 * passing in a value greater than the number 276 * of processors in the system. A value of 277 * zero implies the default behavior of 278 * reading and parsing LDIF records 279 * synchronously when one of the read methods 280 * is called. 281 * @param entryTranslator An optional translator that will be used to 282 * alter entries before they are actually 283 * written. This may be {@code null} if no 284 * translator is needed. 285 * @param changeRecordTranslator An optional translator that will be used to 286 * alter change records before they are 287 * actually written. This may be {@code null} 288 * if no translator is needed. 289 */ 290 public LDIFWriter(final OutputStream outputStream, final int parallelThreads, 291 final LDIFWriterEntryTranslator entryTranslator, 292 final LDIFWriterChangeRecordTranslator changeRecordTranslator) 293 { 294 ensureNotNull(outputStream); 295 ensureTrue(parallelThreads >= 0, 296 "LDIFWriter.parallelThreads must not be negative."); 297 298 this.entryTranslator = entryTranslator; 299 this.changeRecordTranslator = changeRecordTranslator; 300 buffer = new ByteStringBuffer(); 301 302 if (outputStream instanceof BufferedOutputStream) 303 { 304 writer = (BufferedOutputStream) outputStream; 305 } 306 else 307 { 308 writer = new BufferedOutputStream(outputStream, DEFAULT_BUFFER_SIZE); 309 } 310 311 if (parallelThreads == 0) 312 { 313 toLdifBytesInvoker = null; 314 } 315 else 316 { 317 final LDAPSDKThreadFactory threadFactory = 318 new LDAPSDKThreadFactory("LDIFWriter Worker", true, null); 319 toLdifBytesInvoker = new ParallelProcessor<LDIFRecord,ByteStringBuffer>( 320 new Processor<LDIFRecord,ByteStringBuffer>() { 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 public void close() 380 throws IOException 381 { 382 try 383 { 384 if (toLdifBytesInvoker != null) 385 { 386 try 387 { 388 toLdifBytesInvoker.shutdown(); 389 } 390 catch (InterruptedException e) 391 { 392 debugException(e); 393 Thread.currentThread().interrupt(); 394 } 395 } 396 } 397 finally 398 { 399 writer.close(); 400 } 401 } 402 403 404 405 /** 406 * Retrieves the column at which to wrap long lines. 407 * 408 * @return The column at which to wrap long lines, or zero to indicate that 409 * long lines should not be wrapped. 410 */ 411 public int getWrapColumn() 412 { 413 return wrapColumn; 414 } 415 416 417 418 /** 419 * Specifies the column at which to wrap long lines. A value of zero 420 * indicates that long lines should not be wrapped. 421 * 422 * @param wrapColumn The column at which to wrap long lines. 423 */ 424 public void setWrapColumn(final int wrapColumn) 425 { 426 this.wrapColumn = wrapColumn; 427 428 wrapColumnMinusTwo = wrapColumn - 2; 429 } 430 431 432 433 /** 434 * Indicates whether the LDIF writer should generate comments that attempt to 435 * provide unencoded representations (with special characters escaped) of any 436 * base64-encoded values in entries and change records that are written by 437 * this writer. 438 * 439 * @return {@code true} if the LDIF writer should generate comments that 440 * attempt to provide unencoded representations of any base64-encoded 441 * values, or {@code false} if not. 442 */ 443 public static boolean commentAboutBase64EncodedValues() 444 { 445 return commentAboutBase64EncodedValues; 446 } 447 448 449 450 /** 451 * Specifies whether the LDIF writer should generate comments that attempt to 452 * provide unencoded representations (with special characters escaped) of any 453 * base64-encoded values in entries and change records that are written by 454 * this writer. 455 * 456 * @param commentAboutBase64EncodedValues Indicates whether the LDIF writer 457 * should generate comments that 458 * attempt to provide unencoded 459 * representations (with special 460 * characters escaped) of any 461 * base64-encoded values in entries 462 * and change records that are 463 * written by this writer. 464 */ 465 public static void setCommentAboutBase64EncodedValues( 466 final boolean commentAboutBase64EncodedValues) 467 { 468 LDIFWriter.commentAboutBase64EncodedValues = 469 commentAboutBase64EncodedValues; 470 } 471 472 473 474 /** 475 * Writes the LDIF version header (i.e.,"version: 1"). If a version header 476 * is to be added to the LDIF content, it should be done before any entries or 477 * change records have been written. 478 * 479 * @throws IOException If a problem occurs while writing the version header. 480 */ 481 public void writeVersionHeader() 482 throws IOException 483 { 484 writer.write(VERSION_1_HEADER_BYTES); 485 } 486 487 488 489 /** 490 * Writes the provided entry in LDIF form. 491 * 492 * @param entry The entry to be written. It must not be {@code null}. 493 * 494 * @throws IOException If a problem occurs while writing the LDIF data. 495 */ 496 public void writeEntry(final Entry entry) 497 throws IOException 498 { 499 writeEntry(entry, null); 500 } 501 502 503 504 /** 505 * Writes the provided entry in LDIF form, preceded by the provided comment. 506 * 507 * @param entry The entry to be written in LDIF form. It must not be 508 * {@code null}. 509 * @param comment The comment to be written before the entry. It may be 510 * {@code null} if no comment is to be written. 511 * 512 * @throws IOException If a problem occurs while writing the LDIF data. 513 */ 514 public void writeEntry(final Entry entry, final String comment) 515 throws IOException 516 { 517 ensureNotNull(entry); 518 519 final Entry e; 520 if (entryTranslator == null) 521 { 522 e = entry; 523 } 524 else 525 { 526 e = entryTranslator.translateEntryToWrite(entry); 527 if (e == null) 528 { 529 return; 530 } 531 } 532 533 if (comment != null) 534 { 535 writeComment(comment, false, false); 536 } 537 538 debugLDIFWrite(e); 539 writeLDIF(e); 540 } 541 542 543 544 /** 545 * Writes the provided change record in LDIF form. 546 * 547 * @param changeRecord The change record to be written. It must not be 548 * {@code null}. 549 * 550 * @throws IOException If a problem occurs while writing the LDIF data. 551 */ 552 public void writeChangeRecord(final LDIFChangeRecord changeRecord) 553 throws IOException 554 { 555 writeChangeRecord(changeRecord, null); 556 } 557 558 559 560 /** 561 * Writes the provided change record in LDIF form, preceded by the provided 562 * comment. 563 * 564 * @param changeRecord The change record to be written. It must not be 565 * {@code null}. 566 * @param comment The comment to be written before the entry. It may 567 * be {@code null} if no comment is to be written. 568 * 569 * @throws IOException If a problem occurs while writing the LDIF data. 570 */ 571 public void writeChangeRecord(final LDIFChangeRecord changeRecord, 572 final String comment) 573 throws IOException 574 { 575 ensureNotNull(changeRecord); 576 577 final LDIFChangeRecord r; 578 if (changeRecordTranslator == null) 579 { 580 r = changeRecord; 581 } 582 else 583 { 584 r = changeRecordTranslator.translateChangeRecordToWrite(changeRecord); 585 if (r == null) 586 { 587 return; 588 } 589 } 590 591 if (comment != null) 592 { 593 writeComment(comment, false, false); 594 } 595 596 debugLDIFWrite(r); 597 writeLDIF(r); 598 } 599 600 601 602 /** 603 * Writes the provided record in LDIF form. 604 * 605 * @param record The LDIF record to be written. It must not be 606 * {@code null}. 607 * 608 * @throws IOException If a problem occurs while writing the LDIF data. 609 */ 610 public void writeLDIFRecord(final LDIFRecord record) 611 throws IOException 612 { 613 writeLDIFRecord(record, null); 614 } 615 616 617 618 /** 619 * Writes the provided record in LDIF form, preceded by the provided comment. 620 * 621 * @param record The LDIF record to be written. It must not be 622 * {@code null}. 623 * @param comment The comment to be written before the LDIF record. It may 624 * be {@code null} if no comment is to be written. 625 * 626 * @throws IOException If a problem occurs while writing the LDIF data. 627 */ 628 public void writeLDIFRecord(final LDIFRecord record, final String comment) 629 throws IOException 630 { 631 ensureNotNull(record); 632 633 final LDIFRecord r; 634 if ((entryTranslator != null) && (record instanceof Entry)) 635 { 636 r = entryTranslator.translateEntryToWrite((Entry) record); 637 if (r == null) 638 { 639 return; 640 } 641 } 642 else if ((changeRecordTranslator != null) && 643 (record instanceof LDIFChangeRecord)) 644 { 645 r = changeRecordTranslator.translateChangeRecordToWrite( 646 (LDIFChangeRecord) record); 647 if (r == null) 648 { 649 return; 650 } 651 } 652 else 653 { 654 r = record; 655 } 656 657 debugLDIFWrite(r); 658 if (comment != null) 659 { 660 writeComment(comment, false, false); 661 } 662 663 writeLDIF(r); 664 } 665 666 667 668 /** 669 * Writes the provided list of LDIF records (most likely Entries) to the 670 * output. If this LDIFWriter was constructed without any parallel 671 * output threads, then this behaves identically to calling 672 * {@code writeLDIFRecord()} sequentially for each item in the list. 673 * If this LDIFWriter was constructed to write records in parallel, then 674 * the configured number of threads are used to convert the records to raw 675 * bytes, which are sequentially written to the input file. This can speed up 676 * the total time to write a large set of records. Either way, the output 677 * records are guaranteed to be written in the order they appear in the list. 678 * 679 * @param ldifRecords The LDIF records (most likely entries) to write to the 680 * output. 681 * 682 * @throws IOException If a problem occurs while writing the LDIF data. 683 * 684 * @throws InterruptedException If this thread is interrupted while waiting 685 * for the records to be written to the output. 686 */ 687 public void writeLDIFRecords(final List<? extends LDIFRecord> ldifRecords) 688 throws IOException, InterruptedException 689 { 690 if (toLdifBytesInvoker == null) 691 { 692 for (final LDIFRecord ldifRecord : ldifRecords) 693 { 694 writeLDIFRecord(ldifRecord); 695 } 696 } 697 else 698 { 699 final List<Result<LDIFRecord,ByteStringBuffer>> results = 700 toLdifBytesInvoker.processAll(ldifRecords); 701 for (final Result<LDIFRecord,ByteStringBuffer> result: results) 702 { 703 rethrow(result.getFailureCause()); 704 705 final ByteStringBuffer encodedBytes = result.getOutput(); 706 if (encodedBytes != null) 707 { 708 encodedBytes.write(writer); 709 writer.write(EOL_BYTES); 710 } 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 ensureNotNull(comment); 736 if (spaceBefore) 737 { 738 writer.write(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(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 = 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(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(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(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(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(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<String>(ldifLines); 933 } 934 935 final ArrayList<String> newLines = new ArrayList<String>(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 = 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 = 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 wrapLine(comment, wrapColumnMinusTwo, wrapColumnMinusThree)) 1157 { 1158 buffer.append(EOL); 1159 buffer.append("# "); 1160 if (first) 1161 { 1162 first = false; 1163 } 1164 else 1165 { 1166 buffer.append(' '); 1167 } 1168 buffer.append(s); 1169 } 1170 } 1171 } 1172 1173 1174 1175 /** 1176 * Appends a string to the provided buffer consisting of the provided 1177 * attribute name followed by either a single colon and the string 1178 * representation of the provided value, or two colons and the base64-encoded 1179 * representation of the provided value. It may optionally be wrapped at the 1180 * specified column. 1181 * 1182 * @param name The name for the attribute. 1183 * @param value The value for the attribute. 1184 * @param buffer The buffer to which the name and value are to be 1185 * written. 1186 * @param wrapColumn The column at which to wrap long lines. A value that 1187 * is less than or equal to two indicates that no 1188 * wrapping should be performed. 1189 */ 1190 public static void encodeNameAndValue(final String name, 1191 final ASN1OctetString value, 1192 final ByteStringBuffer buffer, 1193 final int wrapColumn) 1194 { 1195 final int bufferStartPos = buffer.length(); 1196 boolean base64Encoded = false; 1197 1198 try 1199 { 1200 buffer.append(name); 1201 base64Encoded = encodeValue(value, buffer); 1202 } 1203 finally 1204 { 1205 if (wrapColumn > 2) 1206 { 1207 final int length = buffer.length() - bufferStartPos; 1208 if (length > wrapColumn) 1209 { 1210 final byte[] EOL_BYTES_PLUS_SPACE = new byte[EOL_BYTES.length + 1]; 1211 System.arraycopy(EOL_BYTES, 0, EOL_BYTES_PLUS_SPACE, 0, 1212 EOL_BYTES.length); 1213 EOL_BYTES_PLUS_SPACE[EOL_BYTES.length] = ' '; 1214 1215 buffer.insert((bufferStartPos+wrapColumn), EOL_BYTES_PLUS_SPACE); 1216 1217 int pos = bufferStartPos + (2*wrapColumn) + 1218 EOL_BYTES_PLUS_SPACE.length - 1; 1219 while (pos < buffer.length()) 1220 { 1221 buffer.insert(pos, EOL_BYTES_PLUS_SPACE); 1222 pos += (wrapColumn - 1 + EOL_BYTES_PLUS_SPACE.length); 1223 } 1224 } 1225 } 1226 1227 if (base64Encoded && commentAboutBase64EncodedValues) 1228 { 1229 writeBase64DecodedValueComment(value.getValue(), buffer, wrapColumn); 1230 } 1231 } 1232 } 1233 1234 1235 1236 /** 1237 * Appends a string to the provided buffer consisting of the properly-encoded 1238 * representation of the provided value, including the necessary colon(s) and 1239 * space that precede it. Depending on the content of the value, it will 1240 * either be used as-is or base64-encoded. 1241 * 1242 * @param value The value for the attribute. 1243 * @param buffer The buffer to which the value is to be written. 1244 * 1245 * @return {@code true} if the value was base64-encoded, or {@code false} if 1246 * not. 1247 */ 1248 static boolean encodeValue(final ASN1OctetString value, 1249 final ByteStringBuffer buffer) 1250 { 1251 buffer.append(':'); 1252 1253 final byte[] valueBytes = value.getValue(); 1254 final int length = valueBytes.length; 1255 if (length == 0) 1256 { 1257 buffer.append(' '); 1258 return false; 1259 } 1260 1261 // If the value starts with a space, colon, or less-than character, then 1262 // it must be base64-encoded. 1263 switch (valueBytes[0]) 1264 { 1265 case ' ': 1266 case ':': 1267 case '<': 1268 buffer.append(':'); 1269 buffer.append(' '); 1270 Base64.encode(valueBytes, buffer); 1271 return true; 1272 } 1273 1274 // If the value ends with a space, then it should be base64-encoded. 1275 if (valueBytes[length-1] == ' ') 1276 { 1277 buffer.append(':'); 1278 buffer.append(' '); 1279 Base64.encode(valueBytes, buffer); 1280 return true; 1281 } 1282 1283 // If any character in the value is outside the ASCII range, or is the 1284 // NUL, LF, or CR character, then the value should be base64-encoded. 1285 for (int i=0; i < length; i++) 1286 { 1287 if ((valueBytes[i] & 0x7F) != (valueBytes[i] & 0xFF)) 1288 { 1289 buffer.append(':'); 1290 buffer.append(' '); 1291 Base64.encode(valueBytes, buffer); 1292 return true; 1293 } 1294 1295 switch (valueBytes[i]) 1296 { 1297 case 0x00: // The NUL character 1298 case 0x0A: // The LF character 1299 case 0x0D: // The CR character 1300 buffer.append(':'); 1301 buffer.append(' '); 1302 1303 Base64.encode(valueBytes, buffer); 1304 return true; 1305 } 1306 } 1307 1308 // If we've gotten here, then the string value is acceptable. 1309 buffer.append(' '); 1310 buffer.append(valueBytes); 1311 return false; 1312 } 1313 1314 1315 1316 /** 1317 * Appends a comment to the provided buffer with an unencoded representation 1318 * of the provided value. This will only have any effect if 1319 * {@code commentAboutBase64EncodedValues} is {@code true}. 1320 * 1321 * @param valueBytes The bytes that comprise the value. 1322 * @param buffer The buffer to which the comment should be appended. 1323 * @param wrapColumn The column at which to wrap long lines. 1324 */ 1325 private static void writeBase64DecodedValueComment(final byte[] valueBytes, 1326 final ByteStringBuffer buffer, 1327 final int wrapColumn) 1328 { 1329 if (commentAboutBase64EncodedValues) 1330 { 1331 final int wrapColumnMinusTwo; 1332 if (wrapColumn <= 5) 1333 { 1334 wrapColumnMinusTwo = TERMINAL_WIDTH_COLUMNS - 3; 1335 } 1336 else 1337 { 1338 wrapColumnMinusTwo = wrapColumn - 2; 1339 } 1340 1341 final int wrapColumnMinusThree = wrapColumnMinusTwo - 1; 1342 1343 boolean first = true; 1344 final String comment = 1345 "Non-base64-encoded representation of the above value: " + 1346 getEscapedValue(valueBytes); 1347 for (final String s : 1348 wrapLine(comment, wrapColumnMinusTwo, wrapColumnMinusThree)) 1349 { 1350 buffer.append(EOL); 1351 buffer.append("# "); 1352 if (first) 1353 { 1354 first = false; 1355 } 1356 else 1357 { 1358 buffer.append(' '); 1359 } 1360 buffer.append(s); 1361 } 1362 } 1363 } 1364 1365 1366 1367 /** 1368 * Retrieves a string representation of the provided value with all special 1369 * characters escaped with backslashes. 1370 * 1371 * @param valueBytes The byte array containing the value to encode. 1372 * 1373 * @return A string representation of the provided value with any special 1374 * characters 1375 */ 1376 private static String getEscapedValue(final byte[] valueBytes) 1377 { 1378 final StringBuilder buffer = new StringBuilder(valueBytes.length * 2); 1379 for (int i=0; i < valueBytes.length; i++) 1380 { 1381 final byte b = valueBytes[i]; 1382 switch (b) 1383 { 1384 case '\n': 1385 buffer.append("\\n"); 1386 break; 1387 case '\r': 1388 buffer.append("\\r"); 1389 break; 1390 case '\t': 1391 buffer.append("\\t"); 1392 break; 1393 case ' ': 1394 if (i == 0) 1395 { 1396 buffer.append("\\ "); 1397 } 1398 else if ( i == (valueBytes.length - 1)) 1399 { 1400 buffer.append("\\20"); 1401 } 1402 else 1403 { 1404 buffer.append(' '); 1405 } 1406 break; 1407 case '<': 1408 if (i == 0) 1409 { 1410 buffer.append('\\'); 1411 } 1412 buffer.append('<'); 1413 break; 1414 case ':': 1415 if (i == 0) 1416 { 1417 buffer.append('\\'); 1418 } 1419 buffer.append(':'); 1420 break; 1421 default: 1422 if ((b >= '!') && (b <= '~')) 1423 { 1424 buffer.append((char) b); 1425 } 1426 else 1427 { 1428 buffer.append("\\"); 1429 toHex(b, buffer); 1430 } 1431 break; 1432 } 1433 } 1434 1435 return buffer.toString(); 1436 } 1437 1438 1439 1440 /** 1441 * If the provided exception is non-null, then it will be rethrown as an 1442 * unchecked exception or an IOException. 1443 * 1444 * @param t The exception to rethrow as an an unchecked exception or an 1445 * IOException or {@code null} if none. 1446 * 1447 * @throws IOException If t is a checked exception. 1448 */ 1449 static void rethrow(final Throwable t) 1450 throws IOException 1451 { 1452 if (t == null) 1453 { 1454 return; 1455 } 1456 1457 if (t instanceof IOException) 1458 { 1459 throw (IOException) t; 1460 } 1461 else if (t instanceof RuntimeException) 1462 { 1463 throw (RuntimeException) t; 1464 } 1465 else if (t instanceof Error) 1466 { 1467 throw (Error) t; 1468 } 1469 else 1470 { 1471 throw createIOExceptionWithCause(null, t); 1472 } 1473 } 1474}