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