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