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