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 */ 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 } 394 } 395 } 396 finally 397 { 398 writer.close(); 399 } 400 } 401 402 403 404 /** 405 * Retrieves the column at which to wrap long lines. 406 * 407 * @return The column at which to wrap long lines, or zero to indicate that 408 * long lines should not be wrapped. 409 */ 410 public int getWrapColumn() 411 { 412 return wrapColumn; 413 } 414 415 416 417 /** 418 * Specifies the column at which to wrap long lines. A value of zero 419 * indicates that long lines should not be wrapped. 420 * 421 * @param wrapColumn The column at which to wrap long lines. 422 */ 423 public void setWrapColumn(final int wrapColumn) 424 { 425 this.wrapColumn = wrapColumn; 426 427 wrapColumnMinusTwo = wrapColumn - 2; 428 } 429 430 431 432 /** 433 * Indicates whether the LDIF writer should generate comments that attempt to 434 * provide unencoded representations (with special characters escaped) of any 435 * base64-encoded values in entries and change records that are written by 436 * this writer. 437 * 438 * @return {@code true} if the LDIF writer should generate comments that 439 * attempt to provide unencoded representations of any base64-encoded 440 * values, or {@code false} if not. 441 */ 442 public static boolean commentAboutBase64EncodedValues() 443 { 444 return commentAboutBase64EncodedValues; 445 } 446 447 448 449 /** 450 * Specifies whether the LDIF writer should generate comments that attempt to 451 * provide unencoded representations (with special characters escaped) of any 452 * base64-encoded values in entries and change records that are written by 453 * this writer. 454 * 455 * @param commentAboutBase64EncodedValues Indicates whether the LDIF writer 456 * should generate comments that 457 * attempt to provide unencoded 458 * representations (with special 459 * characters escaped) of any 460 * base64-encoded values in entries 461 * and change records that are 462 * written by this writer. 463 */ 464 public static void setCommentAboutBase64EncodedValues( 465 final boolean commentAboutBase64EncodedValues) 466 { 467 LDIFWriter.commentAboutBase64EncodedValues = 468 commentAboutBase64EncodedValues; 469 } 470 471 472 473 /** 474 * Writes the LDIF version header (i.e.,"version: 1"). If a version header 475 * is to be added to the LDIF content, it should be done before any entries or 476 * change records have been written. 477 * 478 * @throws IOException If a problem occurs while writing the version header. 479 */ 480 public void writeVersionHeader() 481 throws IOException 482 { 483 writer.write(VERSION_1_HEADER_BYTES); 484 } 485 486 487 488 /** 489 * Writes the provided entry in LDIF form. 490 * 491 * @param entry The entry to be written. It must not be {@code null}. 492 * 493 * @throws IOException If a problem occurs while writing the LDIF data. 494 */ 495 public void writeEntry(final Entry entry) 496 throws IOException 497 { 498 writeEntry(entry, null); 499 } 500 501 502 503 /** 504 * Writes the provided entry in LDIF form, preceded by the provided comment. 505 * 506 * @param entry The entry to be written in LDIF form. It must not be 507 * {@code null}. 508 * @param comment The comment to be written before the entry. It may be 509 * {@code null} if no comment is to be written. 510 * 511 * @throws IOException If a problem occurs while writing the LDIF data. 512 */ 513 public void writeEntry(final Entry entry, final String comment) 514 throws IOException 515 { 516 ensureNotNull(entry); 517 518 final Entry e; 519 if (entryTranslator == null) 520 { 521 e = entry; 522 } 523 else 524 { 525 e = entryTranslator.translateEntryToWrite(entry); 526 if (e == null) 527 { 528 return; 529 } 530 } 531 532 if (comment != null) 533 { 534 writeComment(comment, false, false); 535 } 536 537 debugLDIFWrite(e); 538 writeLDIF(e); 539 } 540 541 542 543 /** 544 * Writes the provided change record in LDIF form. 545 * 546 * @param changeRecord The change record to be written. It must not be 547 * {@code null}. 548 * 549 * @throws IOException If a problem occurs while writing the LDIF data. 550 */ 551 public void writeChangeRecord(final LDIFChangeRecord changeRecord) 552 throws IOException 553 { 554 writeChangeRecord(changeRecord, null); 555 } 556 557 558 559 /** 560 * Writes the provided change record in LDIF form, preceded by the provided 561 * comment. 562 * 563 * @param changeRecord The change record to be written. It must not be 564 * {@code null}. 565 * @param comment The comment to be written before the entry. It may 566 * be {@code null} if no comment is to be written. 567 * 568 * @throws IOException If a problem occurs while writing the LDIF data. 569 */ 570 public void writeChangeRecord(final LDIFChangeRecord changeRecord, 571 final String comment) 572 throws IOException 573 { 574 ensureNotNull(changeRecord); 575 576 final LDIFChangeRecord r; 577 if (changeRecordTranslator == null) 578 { 579 r = changeRecord; 580 } 581 else 582 { 583 r = changeRecordTranslator.translateChangeRecordToWrite(changeRecord); 584 if (r == null) 585 { 586 return; 587 } 588 } 589 590 if (comment != null) 591 { 592 writeComment(comment, false, false); 593 } 594 595 debugLDIFWrite(r); 596 writeLDIF(r); 597 } 598 599 600 601 /** 602 * Writes the provided record in LDIF form. 603 * 604 * @param record The LDIF record to be written. It must not be 605 * {@code null}. 606 * 607 * @throws IOException If a problem occurs while writing the LDIF data. 608 */ 609 public void writeLDIFRecord(final LDIFRecord record) 610 throws IOException 611 { 612 writeLDIFRecord(record, null); 613 } 614 615 616 617 /** 618 * Writes the provided record in LDIF form, preceded by the provided comment. 619 * 620 * @param record The LDIF record to be written. It must not be 621 * {@code null}. 622 * @param comment The comment to be written before the LDIF record. It may 623 * be {@code null} if no comment is to be written. 624 * 625 * @throws IOException If a problem occurs while writing the LDIF data. 626 */ 627 public void writeLDIFRecord(final LDIFRecord record, final String comment) 628 throws IOException 629 { 630 ensureNotNull(record); 631 632 final LDIFRecord r; 633 if ((entryTranslator != null) && (record instanceof Entry)) 634 { 635 r = entryTranslator.translateEntryToWrite((Entry) record); 636 if (r == null) 637 { 638 return; 639 } 640 } 641 else if ((changeRecordTranslator != null) && 642 (record instanceof LDIFChangeRecord)) 643 { 644 r = changeRecordTranslator.translateChangeRecordToWrite( 645 (LDIFChangeRecord) record); 646 if (r == null) 647 { 648 return; 649 } 650 } 651 else 652 { 653 r = record; 654 } 655 656 debugLDIFWrite(r); 657 if (comment != null) 658 { 659 writeComment(comment, false, false); 660 } 661 662 writeLDIF(r); 663 } 664 665 666 667 /** 668 * Writes the provided list of LDIF records (most likely Entries) to the 669 * output. If this LDIFWriter was constructed without any parallel 670 * output threads, then this behaves identically to calling 671 * {@code writeLDIFRecord()} sequentially for each item in the list. 672 * If this LDIFWriter was constructed to write records in parallel, then 673 * the configured number of threads are used to convert the records to raw 674 * bytes, which are sequentially written to the input file. This can speed up 675 * the total time to write a large set of records. Either way, the output 676 * records are guaranteed to be written in the order they appear in the list. 677 * 678 * @param ldifRecords The LDIF records (most likely entries) to write to the 679 * output. 680 * 681 * @throws IOException If a problem occurs while writing the LDIF data. 682 * 683 * @throws InterruptedException If this thread is interrupted while waiting 684 * for the records to be written to the output. 685 */ 686 public void writeLDIFRecords(final List<? extends LDIFRecord> ldifRecords) 687 throws IOException, InterruptedException 688 { 689 if (toLdifBytesInvoker == null) 690 { 691 for (final LDIFRecord ldifRecord : ldifRecords) 692 { 693 writeLDIFRecord(ldifRecord); 694 } 695 } 696 else 697 { 698 final List<Result<LDIFRecord,ByteStringBuffer>> results = 699 toLdifBytesInvoker.processAll(ldifRecords); 700 for (final Result<LDIFRecord,ByteStringBuffer> result: results) 701 { 702 rethrow(result.getFailureCause()); 703 704 final ByteStringBuffer encodedBytes = result.getOutput(); 705 if (encodedBytes != null) 706 { 707 encodedBytes.write(writer); 708 writer.write(EOL_BYTES); 709 } 710 } 711 } 712 } 713 714 715 716 717 /** 718 * Writes the provided comment to the LDIF target, wrapping long lines as 719 * necessary. 720 * 721 * @param comment The comment to be written to the LDIF target. It must 722 * not be {@code null}. 723 * @param spaceBefore Indicates whether to insert a blank line before the 724 * comment. 725 * @param spaceAfter Indicates whether to insert a blank line after the 726 * comment. 727 * 728 * @throws IOException If a problem occurs while writing the LDIF data. 729 */ 730 public void writeComment(final String comment, final boolean spaceBefore, 731 final boolean spaceAfter) 732 throws IOException 733 { 734 ensureNotNull(comment); 735 if (spaceBefore) 736 { 737 writer.write(EOL_BYTES); 738 } 739 740 // 741 // Check for a newline explicitly to avoid the overhead of the regex 742 // for the common case of a single-line comment. 743 // 744 745 if (comment.indexOf('\n') < 0) 746 { 747 writeSingleLineComment(comment); 748 } 749 else 750 { 751 // 752 // Split on blank lines and wrap each line individually. 753 // 754 755 final String[] lines = comment.split("\\r?\\n"); 756 for (final String line: lines) 757 { 758 writeSingleLineComment(line); 759 } 760 } 761 762 if (spaceAfter) 763 { 764 writer.write(EOL_BYTES); 765 } 766 } 767 768 769 770 /** 771 * Writes the provided comment to the LDIF target, wrapping long lines as 772 * necessary. 773 * 774 * @param comment The comment to be written to the LDIF target. It must 775 * not be {@code null}, and it must not include any line 776 * breaks. 777 * 778 * @throws IOException If a problem occurs while writing the LDIF data. 779 */ 780 private void writeSingleLineComment(final String comment) 781 throws IOException 782 { 783 // We will always wrap comments, even if we won't wrap LDIF entries. If 784 // there is a wrap column set, then use it. Otherwise use the terminal 785 // width and back off two characters for the "# " at the beginning. 786 final int commentWrapMinusTwo; 787 if (wrapColumn <= 0) 788 { 789 commentWrapMinusTwo = TERMINAL_WIDTH_COLUMNS - 3; 790 } 791 else 792 { 793 commentWrapMinusTwo = wrapColumnMinusTwo; 794 } 795 796 buffer.clear(); 797 final int length = comment.length(); 798 if (length <= commentWrapMinusTwo) 799 { 800 buffer.append("# "); 801 buffer.append(comment); 802 buffer.append(EOL_BYTES); 803 } 804 else 805 { 806 int minPos = 0; 807 while (minPos < length) 808 { 809 if ((length - minPos) <= commentWrapMinusTwo) 810 { 811 buffer.append("# "); 812 buffer.append(comment.substring(minPos)); 813 buffer.append(EOL_BYTES); 814 break; 815 } 816 817 // First, adjust the position until we find a space. Go backwards if 818 // possible, but if we can't find one there then go forward. 819 boolean spaceFound = false; 820 final int pos = minPos + commentWrapMinusTwo; 821 int spacePos = pos; 822 while (spacePos > minPos) 823 { 824 if (comment.charAt(spacePos) == ' ') 825 { 826 spaceFound = true; 827 break; 828 } 829 830 spacePos--; 831 } 832 833 if (! spaceFound) 834 { 835 spacePos = pos + 1; 836 while (spacePos < length) 837 { 838 if (comment.charAt(spacePos) == ' ') 839 { 840 spaceFound = true; 841 break; 842 } 843 844 spacePos++; 845 } 846 847 if (! spaceFound) 848 { 849 // There are no spaces at all in the remainder of the comment, so 850 // we'll just write the remainder of it all at once. 851 buffer.append("# "); 852 buffer.append(comment.substring(minPos)); 853 buffer.append(EOL_BYTES); 854 break; 855 } 856 } 857 858 // We have a space, so we'll write up to the space position and then 859 // start up after the next space. 860 buffer.append("# "); 861 buffer.append(comment.substring(minPos, spacePos)); 862 buffer.append(EOL_BYTES); 863 864 minPos = spacePos + 1; 865 while ((minPos < length) && (comment.charAt(minPos) == ' ')) 866 { 867 minPos++; 868 } 869 } 870 } 871 872 buffer.write(writer); 873 } 874 875 876 877 /** 878 * Writes the provided record to the LDIF target, wrapping long lines as 879 * necessary. 880 * 881 * @param record The LDIF record to be written. 882 * 883 * @throws IOException If a problem occurs while writing the LDIF data. 884 */ 885 private void writeLDIF(final LDIFRecord record) 886 throws IOException 887 { 888 buffer.clear(); 889 record.toLDIF(buffer, wrapColumn); 890 buffer.append(EOL_BYTES); 891 buffer.write(writer); 892 } 893 894 895 896 /** 897 * Performs any appropriate wrapping for the provided set of LDIF lines. 898 * 899 * @param wrapColumn The column at which to wrap long lines. A value that 900 * is less than or equal to two indicates that no 901 * wrapping should be performed. 902 * @param ldifLines The set of lines that make up the LDIF data to be 903 * wrapped. 904 * 905 * @return A new list of lines that have been wrapped as appropriate. 906 */ 907 public static List<String> wrapLines(final int wrapColumn, 908 final String... ldifLines) 909 { 910 return wrapLines(wrapColumn, Arrays.asList(ldifLines)); 911 } 912 913 914 915 /** 916 * Performs any appropriate wrapping for the provided set of LDIF lines. 917 * 918 * @param wrapColumn The column at which to wrap long lines. A value that 919 * is less than or equal to two indicates that no 920 * wrapping should be performed. 921 * @param ldifLines The set of lines that make up the LDIF data to be 922 * wrapped. 923 * 924 * @return A new list of lines that have been wrapped as appropriate. 925 */ 926 public static List<String> wrapLines(final int wrapColumn, 927 final List<String> ldifLines) 928 { 929 if (wrapColumn <= 2) 930 { 931 return new ArrayList<String>(ldifLines); 932 } 933 934 final ArrayList<String> newLines = new ArrayList<String>(ldifLines.size()); 935 for (final String s : ldifLines) 936 { 937 final int length = s.length(); 938 if (length <= wrapColumn) 939 { 940 newLines.add(s); 941 continue; 942 } 943 944 newLines.add(s.substring(0, wrapColumn)); 945 946 int pos = wrapColumn; 947 while (pos < length) 948 { 949 if ((length - pos + 1) <= wrapColumn) 950 { 951 newLines.add(' ' + s.substring(pos)); 952 break; 953 } 954 else 955 { 956 newLines.add(' ' + s.substring(pos, (pos+wrapColumn-1))); 957 pos += wrapColumn - 1; 958 } 959 } 960 } 961 962 return newLines; 963 } 964 965 966 967 /** 968 * Creates a string consisting of the provided attribute name followed by 969 * either a single colon and the string representation of the provided value, 970 * or two colons and the base64-encoded representation of the provided value. 971 * 972 * @param name The name for the attribute. 973 * @param value The value for the attribute. 974 * 975 * @return A string consisting of the provided attribute name followed by 976 * either a single colon and the string representation of the 977 * provided value, or two colons and the base64-encoded 978 * representation of the provided value. 979 */ 980 public static String encodeNameAndValue(final String name, 981 final ASN1OctetString value) 982 { 983 final StringBuilder buffer = new StringBuilder(); 984 encodeNameAndValue(name, value, buffer); 985 return buffer.toString(); 986 } 987 988 989 990 /** 991 * Appends a string to the provided buffer consisting of the provided 992 * attribute name followed by either a single colon and the string 993 * representation of the provided value, or two colons and the base64-encoded 994 * representation of the provided value. 995 * 996 * @param name The name for the attribute. 997 * @param value The value for the attribute. 998 * @param buffer The buffer to which the name and value are to be written. 999 */ 1000 public static void encodeNameAndValue(final String name, 1001 final ASN1OctetString value, 1002 final StringBuilder buffer) 1003 { 1004 encodeNameAndValue(name, value, buffer, 0); 1005 } 1006 1007 1008 1009 /** 1010 * Appends a string to the provided buffer consisting of the provided 1011 * attribute name followed by either a single colon and the string 1012 * representation of the provided value, or two colons and the base64-encoded 1013 * representation of the provided value. 1014 * 1015 * @param name The name for the attribute. 1016 * @param value The value for the attribute. 1017 * @param buffer The buffer to which the name and value are to be 1018 * written. 1019 * @param wrapColumn The column at which to wrap long lines. A value that 1020 * is less than or equal to two indicates that no 1021 * wrapping should be performed. 1022 */ 1023 public static void encodeNameAndValue(final String name, 1024 final ASN1OctetString value, 1025 final StringBuilder buffer, 1026 final int wrapColumn) 1027 { 1028 final int bufferStartPos = buffer.length(); 1029 final byte[] valueBytes = value.getValue(); 1030 boolean base64Encoded = false; 1031 1032 try 1033 { 1034 buffer.append(name); 1035 buffer.append(':'); 1036 1037 final int length = valueBytes.length; 1038 if (length == 0) 1039 { 1040 buffer.append(' '); 1041 return; 1042 } 1043 1044 // If the value starts with a space, colon, or less-than character, then 1045 // it must be base64-encoded. 1046 switch (valueBytes[0]) 1047 { 1048 case ' ': 1049 case ':': 1050 case '<': 1051 buffer.append(": "); 1052 Base64.encode(valueBytes, buffer); 1053 base64Encoded = true; 1054 return; 1055 } 1056 1057 // If the value ends with a space, then it should be base64-encoded. 1058 if (valueBytes[length-1] == ' ') 1059 { 1060 buffer.append(": "); 1061 Base64.encode(valueBytes, buffer); 1062 base64Encoded = true; 1063 return; 1064 } 1065 1066 // If any character in the value is outside the ASCII range, or is the 1067 // NUL, LF, or CR character, then the value should be base64-encoded. 1068 for (int i=0; i < length; i++) 1069 { 1070 if ((valueBytes[i] & 0x7F) != (valueBytes[i] & 0xFF)) 1071 { 1072 buffer.append(": "); 1073 Base64.encode(valueBytes, buffer); 1074 base64Encoded = true; 1075 return; 1076 } 1077 1078 switch (valueBytes[i]) 1079 { 1080 case 0x00: // The NUL character 1081 case 0x0A: // The LF character 1082 case 0x0D: // The CR character 1083 buffer.append(": "); 1084 Base64.encode(valueBytes, buffer); 1085 base64Encoded = true; 1086 return; 1087 } 1088 } 1089 1090 // If we've gotten here, then the string value is acceptable. 1091 buffer.append(' '); 1092 buffer.append(value.stringValue()); 1093 } 1094 finally 1095 { 1096 if (wrapColumn > 2) 1097 { 1098 final int length = buffer.length() - bufferStartPos; 1099 if (length > wrapColumn) 1100 { 1101 final String EOL_PLUS_SPACE = EOL + ' '; 1102 buffer.insert((bufferStartPos+wrapColumn), EOL_PLUS_SPACE); 1103 1104 int pos = bufferStartPos + (2*wrapColumn) + 1105 EOL_PLUS_SPACE.length() - 1; 1106 while (pos < buffer.length()) 1107 { 1108 buffer.insert(pos, EOL_PLUS_SPACE); 1109 pos += (wrapColumn - 1 + EOL_PLUS_SPACE.length()); 1110 } 1111 } 1112 } 1113 1114 if (base64Encoded && commentAboutBase64EncodedValues) 1115 { 1116 writeBase64DecodedValueComment(valueBytes, buffer, wrapColumn); 1117 } 1118 } 1119 } 1120 1121 1122 1123 /** 1124 * Appends a comment to the provided buffer with an unencoded representation 1125 * of the provided value. This will only have any effect if 1126 * {@code commentAboutBase64EncodedValues} is {@code true}. 1127 * 1128 * @param valueBytes The bytes that comprise the value. 1129 * @param buffer The buffer to which the comment should be appended. 1130 * @param wrapColumn The column at which to wrap long lines. 1131 */ 1132 private static void writeBase64DecodedValueComment(final byte[] valueBytes, 1133 final StringBuilder buffer, 1134 final int wrapColumn) 1135 { 1136 if (commentAboutBase64EncodedValues) 1137 { 1138 final int wrapColumnMinusTwo; 1139 if (wrapColumn <= 5) 1140 { 1141 wrapColumnMinusTwo = TERMINAL_WIDTH_COLUMNS - 3; 1142 } 1143 else 1144 { 1145 wrapColumnMinusTwo = wrapColumn - 2; 1146 } 1147 1148 final int wrapColumnMinusThree = wrapColumnMinusTwo - 1; 1149 1150 boolean first = true; 1151 final String comment = 1152 "Non-base64-encoded representation of the above value: " + 1153 getEscapedValue(valueBytes); 1154 for (final String s : 1155 wrapLine(comment, wrapColumnMinusTwo, wrapColumnMinusThree)) 1156 { 1157 buffer.append(EOL); 1158 buffer.append("# "); 1159 if (first) 1160 { 1161 first = false; 1162 } 1163 else 1164 { 1165 buffer.append(' '); 1166 } 1167 buffer.append(s); 1168 } 1169 } 1170 } 1171 1172 1173 1174 /** 1175 * Appends a string to the provided buffer consisting of the provided 1176 * attribute name followed by either a single colon and the string 1177 * representation of the provided value, or two colons and the base64-encoded 1178 * representation of the provided value. It may optionally be wrapped at the 1179 * specified column. 1180 * 1181 * @param name The name for the attribute. 1182 * @param value The value for the attribute. 1183 * @param buffer The buffer to which the name and value are to be 1184 * written. 1185 * @param wrapColumn The column at which to wrap long lines. A value that 1186 * is less than or equal to two indicates that no 1187 * wrapping should be performed. 1188 */ 1189 public static void encodeNameAndValue(final String name, 1190 final ASN1OctetString value, 1191 final ByteStringBuffer buffer, 1192 final int wrapColumn) 1193 { 1194 final int bufferStartPos = buffer.length(); 1195 boolean base64Encoded = false; 1196 1197 try 1198 { 1199 buffer.append(name); 1200 base64Encoded = encodeValue(value, buffer); 1201 } 1202 finally 1203 { 1204 if (wrapColumn > 2) 1205 { 1206 final int length = buffer.length() - bufferStartPos; 1207 if (length > wrapColumn) 1208 { 1209 final byte[] EOL_BYTES_PLUS_SPACE = new byte[EOL_BYTES.length + 1]; 1210 System.arraycopy(EOL_BYTES, 0, EOL_BYTES_PLUS_SPACE, 0, 1211 EOL_BYTES.length); 1212 EOL_BYTES_PLUS_SPACE[EOL_BYTES.length] = ' '; 1213 1214 buffer.insert((bufferStartPos+wrapColumn), EOL_BYTES_PLUS_SPACE); 1215 1216 int pos = bufferStartPos + (2*wrapColumn) + 1217 EOL_BYTES_PLUS_SPACE.length - 1; 1218 while (pos < buffer.length()) 1219 { 1220 buffer.insert(pos, EOL_BYTES_PLUS_SPACE); 1221 pos += (wrapColumn - 1 + EOL_BYTES_PLUS_SPACE.length); 1222 } 1223 } 1224 } 1225 1226 if (base64Encoded && commentAboutBase64EncodedValues) 1227 { 1228 writeBase64DecodedValueComment(value.getValue(), buffer, wrapColumn); 1229 } 1230 } 1231 } 1232 1233 1234 1235 /** 1236 * Appends a string to the provided buffer consisting of the properly-encoded 1237 * representation of the provided value, including the necessary colon(s) and 1238 * space that precede it. Depending on the content of the value, it will 1239 * either be used as-is or base64-encoded. 1240 * 1241 * @param value The value for the attribute. 1242 * @param buffer The buffer to which the value is to be written. 1243 * 1244 * @return {@code true} if the value was base64-encoded, or {@code false} if 1245 * not. 1246 */ 1247 static boolean encodeValue(final ASN1OctetString value, 1248 final ByteStringBuffer buffer) 1249 { 1250 buffer.append(':'); 1251 1252 final byte[] valueBytes = value.getValue(); 1253 final int length = valueBytes.length; 1254 if (length == 0) 1255 { 1256 buffer.append(' '); 1257 return false; 1258 } 1259 1260 // If the value starts with a space, colon, or less-than character, then 1261 // it must be base64-encoded. 1262 switch (valueBytes[0]) 1263 { 1264 case ' ': 1265 case ':': 1266 case '<': 1267 buffer.append(':'); 1268 buffer.append(' '); 1269 Base64.encode(valueBytes, buffer); 1270 return true; 1271 } 1272 1273 // If the value ends with a space, then it should be base64-encoded. 1274 if (valueBytes[length-1] == ' ') 1275 { 1276 buffer.append(':'); 1277 buffer.append(' '); 1278 Base64.encode(valueBytes, buffer); 1279 return true; 1280 } 1281 1282 // If any character in the value is outside the ASCII range, or is the 1283 // NUL, LF, or CR character, then the value should be base64-encoded. 1284 for (int i=0; i < length; i++) 1285 { 1286 if ((valueBytes[i] & 0x7F) != (valueBytes[i] & 0xFF)) 1287 { 1288 buffer.append(':'); 1289 buffer.append(' '); 1290 Base64.encode(valueBytes, buffer); 1291 return true; 1292 } 1293 1294 switch (valueBytes[i]) 1295 { 1296 case 0x00: // The NUL character 1297 case 0x0A: // The LF character 1298 case 0x0D: // The CR character 1299 buffer.append(':'); 1300 buffer.append(' '); 1301 1302 Base64.encode(valueBytes, buffer); 1303 return true; 1304 } 1305 } 1306 1307 // If we've gotten here, then the string value is acceptable. 1308 buffer.append(' '); 1309 buffer.append(valueBytes); 1310 return false; 1311 } 1312 1313 1314 1315 /** 1316 * Appends a comment to the provided buffer with an unencoded representation 1317 * of the provided value. This will only have any effect if 1318 * {@code commentAboutBase64EncodedValues} is {@code true}. 1319 * 1320 * @param valueBytes The bytes that comprise the value. 1321 * @param buffer The buffer to which the comment should be appended. 1322 * @param wrapColumn The column at which to wrap long lines. 1323 */ 1324 private static void writeBase64DecodedValueComment(final byte[] valueBytes, 1325 final ByteStringBuffer buffer, 1326 final int wrapColumn) 1327 { 1328 if (commentAboutBase64EncodedValues) 1329 { 1330 final int wrapColumnMinusTwo; 1331 if (wrapColumn <= 5) 1332 { 1333 wrapColumnMinusTwo = TERMINAL_WIDTH_COLUMNS - 3; 1334 } 1335 else 1336 { 1337 wrapColumnMinusTwo = wrapColumn - 2; 1338 } 1339 1340 final int wrapColumnMinusThree = wrapColumnMinusTwo - 1; 1341 1342 boolean first = true; 1343 final String comment = 1344 "Non-base64-encoded representation of the above value: " + 1345 getEscapedValue(valueBytes); 1346 for (final String s : 1347 wrapLine(comment, wrapColumnMinusTwo, wrapColumnMinusThree)) 1348 { 1349 buffer.append(EOL); 1350 buffer.append("# "); 1351 if (first) 1352 { 1353 first = false; 1354 } 1355 else 1356 { 1357 buffer.append(' '); 1358 } 1359 buffer.append(s); 1360 } 1361 } 1362 } 1363 1364 1365 1366 /** 1367 * Retrieves a string representation of the provided value with all special 1368 * characters escaped with backslashes. 1369 * 1370 * @param valueBytes The byte array containing the value to encode. 1371 * 1372 * @return A string representation of the provided value with any special 1373 * characters 1374 */ 1375 private static String getEscapedValue(final byte[] valueBytes) 1376 { 1377 final StringBuilder buffer = new StringBuilder(valueBytes.length * 2); 1378 for (int i=0; i < valueBytes.length; i++) 1379 { 1380 final byte b = valueBytes[i]; 1381 switch (b) 1382 { 1383 case '\n': 1384 buffer.append("\\n"); 1385 break; 1386 case '\r': 1387 buffer.append("\\r"); 1388 break; 1389 case '\t': 1390 buffer.append("\\t"); 1391 break; 1392 case ' ': 1393 if (i == 0) 1394 { 1395 buffer.append("\\ "); 1396 } 1397 else if ( i == (valueBytes.length - 1)) 1398 { 1399 buffer.append("\\20"); 1400 } 1401 else 1402 { 1403 buffer.append(' '); 1404 } 1405 break; 1406 case '<': 1407 if (i == 0) 1408 { 1409 buffer.append('\\'); 1410 } 1411 buffer.append('<'); 1412 break; 1413 case ':': 1414 if (i == 0) 1415 { 1416 buffer.append('\\'); 1417 } 1418 buffer.append(':'); 1419 break; 1420 default: 1421 if ((b >= '!') && (b <= '~')) 1422 { 1423 buffer.append((char) b); 1424 } 1425 else 1426 { 1427 buffer.append("\\"); 1428 toHex(b, buffer); 1429 } 1430 break; 1431 } 1432 } 1433 1434 return buffer.toString(); 1435 } 1436 1437 1438 1439 /** 1440 * If the provided exception is non-null, then it will be rethrown as an 1441 * unchecked exception or an IOException. 1442 * 1443 * @param t The exception to rethrow as an an unchecked exception or an 1444 * IOException or {@code null} if none. 1445 * 1446 * @throws IOException If t is a checked exception. 1447 */ 1448 static void rethrow(final Throwable t) 1449 throws IOException 1450 { 1451 if (t == null) 1452 { 1453 return; 1454 } 1455 1456 if (t instanceof IOException) 1457 { 1458 throw (IOException) t; 1459 } 1460 else if (t instanceof RuntimeException) 1461 { 1462 throw (RuntimeException) t; 1463 } 1464 else if (t instanceof Error) 1465 { 1466 throw (Error) t; 1467 } 1468 else 1469 { 1470 throw createIOExceptionWithCause(null, t); 1471 } 1472 } 1473}