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