001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2006-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.util; 028 029 030 031 import java.io.BufferedWriter; 032 import java.io.IOException; 033 import java.util.Iterator; 034 import java.util.List; 035 import java.util.regex.Pattern; 036 import java.util.Collection; 037 038 import org.opends.messages.Message; 039 import org.opends.server.protocols.asn1.ASN1OctetString; 040 import org.opends.server.loggers.debug.DebugTracer; 041 import org.opends.server.types.Attribute; 042 import org.opends.server.types.AttributeType; 043 import org.opends.server.types.AttributeValue; 044 import org.opends.server.types.DebugLogLevel; 045 import org.opends.server.types.DN; 046 import org.opends.server.types.Entry; 047 import org.opends.server.types.LDIFExportConfig; 048 import org.opends.server.types.Modification; 049 import org.opends.server.types.RawAttribute; 050 import org.opends.server.types.RawModification; 051 import org.opends.server.types.RDN; 052 053 import static org.opends.server.loggers.debug.DebugLogger.*; 054 import static org.opends.server.util.StaticUtils.*; 055 import static org.opends.server.util.Validator.*; 056 057 058 /** 059 * This class provides a mechanism for writing entries in LDIF form to a file or 060 * an output stream. 061 */ 062 @org.opends.server.types.PublicAPI( 063 stability=org.opends.server.types.StabilityLevel.UNCOMMITTED, 064 mayInstantiate=true, 065 mayExtend=false, 066 mayInvoke=true) 067 public final class LDIFWriter 068 { 069 /** 070 * The tracer object for the debug logger. 071 */ 072 private static final DebugTracer TRACER = getTracer(); 073 074 // FIXME -- Add support for generating a hash when writing the data. 075 // FIXME -- Add support for signing the hash that is generated. 076 077 078 079 // The writer to which the LDIF information will be written. 080 private BufferedWriter writer; 081 082 // The configuration to use for the export. 083 private LDIFExportConfig exportConfig; 084 085 // Regular expression used for splitting comments on line-breaks. 086 private static final Pattern SPLIT_NEWLINE = Pattern.compile("\\r?\\n"); 087 088 089 090 /** 091 * Creates a new LDIF writer with the provided configuration. 092 * 093 * @param exportConfig The configuration to use for the export. It must not 094 * be <CODE>null</CODE>. 095 * 096 * @throws IOException If a problem occurs while opening the writer. 097 */ 098 public LDIFWriter(LDIFExportConfig exportConfig) 099 throws IOException 100 { 101 ensureNotNull(exportConfig); 102 this.exportConfig = exportConfig; 103 104 writer = exportConfig.getWriter(); 105 } 106 107 108 109 /** 110 * Writes the provided comment to the LDIF file, optionally wrapping near the 111 * specified column. Each line will be prefixed by the octothorpe (#) 112 * character followed by a space. If the comment should be wrapped at a 113 * specified column, then it will attempt to do so at the first whitespace 114 * character at or before that column (so it will try not wrap in the middle 115 * of a word). 116 * <BR><BR> 117 * This comment will be ignored by the 118 * Directory Server's LDIF reader, as well as any other compliant LDIF parsing 119 * software. 120 * 121 * @param comment The comment to be written. Any line breaks that it 122 * contains will be honored, and potentially new line 123 * breaks may be introduced by the wrapping process. It 124 * must not be <CODE>null</CODE>. 125 * @param wrapColumn The column at which long lines should be wrapped, or 126 * -1 to indicate that no additional wrapping should be 127 * added. This will override the wrap column setting 128 * specified in the LDIF export configuration. 129 * 130 * @throws IOException If a problem occurs while attempting to write the 131 * comment to the LDIF file. 132 */ 133 public void writeComment(Message comment, int wrapColumn) 134 throws IOException 135 { 136 ensureNotNull(comment); 137 138 139 // First, break up the comment into multiple lines to preserve the original 140 // spacing that it contained. 141 String[] lines = SPLIT_NEWLINE.split(comment); 142 143 // Now iterate through the lines and write them out, prefixing and wrapping 144 // them as necessary. 145 for (String l : lines) 146 { 147 if (wrapColumn <= 0) 148 { 149 writer.write("# "); 150 writer.write(l); 151 writer.newLine(); 152 } 153 else 154 { 155 int breakColumn = wrapColumn - 2; 156 157 if (l.length() <= breakColumn) 158 { 159 writer.write("# "); 160 writer.write(l); 161 writer.newLine(); 162 } 163 else 164 { 165 int startPos = 0; 166 outerLoop: 167 while (startPos < l.length()) 168 { 169 if ((startPos+breakColumn) >= l.length()) 170 { 171 172 writer.write("# "); 173 writer.write(l.substring(startPos)); 174 writer.newLine(); 175 startPos = l.length(); 176 } 177 else 178 { 179 int endPos = startPos + breakColumn; 180 181 int i=endPos - 1; 182 while (i > startPos) 183 { 184 if (l.charAt(i) == ' ') 185 { 186 writer.write("# "); 187 writer.write(l.substring(startPos, i)); 188 writer.newLine(); 189 190 startPos = i+1; 191 continue outerLoop; 192 } 193 194 i--; 195 } 196 197 // If we've gotten here, then there are no spaces on the entire 198 // line. If that happens, then we'll have to break in the middle 199 // of a word. 200 writer.write("# "); 201 writer.write(l.substring(startPos, endPos)); 202 writer.newLine(); 203 204 startPos = endPos; 205 } 206 } 207 } 208 } 209 } 210 } 211 212 /** 213 * Iterates over each entry contained in the map and writes out the entry in 214 * LDIF format. The main benefit of this method is that the entries can be 215 * sorted by DN and output in sorted order. 216 * 217 * @param entries The Map containing the entries keyed by DN. 218 * 219 * @return <CODE>true</CODE>of all of the entries were 220 * written out, <CODE>false</CODE>if it was not 221 * because of the export configuration. 222 * 223 * @throws IOException If a problem occurs while writing the entry to LDIF. 224 * 225 * @throws LDIFException If a problem occurs while trying to determine 226 * whether to include the entry in the export. 227 */ 228 public boolean writeEntries(Collection <Entry> entries) 229 throws IOException, LDIFException { 230 231 boolean ret=true; 232 Iterator<Entry> i = entries.iterator(); 233 while(ret && i.hasNext()) { 234 ret=writeEntry(i.next()); 235 } 236 return ret; 237 } 238 239 240 /** 241 * Writes the provided entry to LDIF. 242 * 243 * @param entry The entry to be written. It must not be <CODE>null</CODE>. 244 * 245 * @return <CODE>true</CODE> if the entry was actually written, or 246 * <CODE>false</CODE> if it was not because of the export 247 * configuration. 248 * 249 * @throws IOException If a problem occurs while writing the entry to LDIF. 250 * 251 * @throws LDIFException If a problem occurs while trying to determine 252 * whether to include the entry in the export. 253 */ 254 public boolean writeEntry(Entry entry) 255 throws IOException, LDIFException 256 { 257 ensureNotNull(entry); 258 return entry.toLDIF(exportConfig); 259 } 260 261 262 263 /** 264 * Writes a change record entry for the provided change record. 265 * 266 * @param changeRecord The change record entry to be written. 267 * 268 * @throws IOException If a problem occurs while writing the change record. 269 */ 270 public void writeChangeRecord(ChangeRecordEntry changeRecord) 271 throws IOException 272 { 273 ensureNotNull(changeRecord); 274 275 276 // Get the information necessary to write the LDIF. 277 BufferedWriter writer = exportConfig.getWriter(); 278 int wrapColumn = exportConfig.getWrapColumn(); 279 boolean wrapLines = (wrapColumn > 1); 280 281 282 // First, write the DN. 283 StringBuilder dnLine = new StringBuilder(); 284 dnLine.append("dn"); 285 appendLDIFSeparatorAndValue(dnLine, 286 getBytes(changeRecord.getDN().toString())); 287 writeLDIFLine(dnLine, writer, wrapLines, wrapColumn); 288 289 290 // Figure out what type of change it is and act accordingly. 291 if (changeRecord instanceof AddChangeRecordEntry) 292 { 293 StringBuilder changeTypeLine = new StringBuilder("changetype: add"); 294 writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn); 295 296 AddChangeRecordEntry addRecord = (AddChangeRecordEntry) changeRecord; 297 for (Attribute a : addRecord.getAttributes()) 298 { 299 for (AttributeValue v : a.getValues()) 300 { 301 StringBuilder line = new StringBuilder(); 302 line.append(a.getNameWithOptions()); 303 String stringValue = v.getStringValue(); 304 if (needsBase64Encoding(stringValue)) 305 { 306 line.append(":: "); 307 line.append(Base64.encode(v.getValueBytes())); 308 } 309 else 310 { 311 line.append(": "); 312 line.append(stringValue); 313 } 314 writeLDIFLine(line, writer, wrapLines, wrapColumn); 315 } 316 } 317 } 318 else if (changeRecord instanceof DeleteChangeRecordEntry) 319 { 320 StringBuilder changeTypeLine = new StringBuilder("changetype: delete"); 321 writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn); 322 } 323 else if (changeRecord instanceof ModifyChangeRecordEntry) 324 { 325 StringBuilder changeTypeLine = new StringBuilder("changetype: modify"); 326 writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn); 327 328 ModifyChangeRecordEntry modifyRecord = 329 (ModifyChangeRecordEntry) changeRecord; 330 List<RawModification> mods = modifyRecord.getModifications(); 331 Iterator<RawModification> iterator = mods.iterator(); 332 while (iterator.hasNext()) 333 { 334 RawModification m = iterator.next(); 335 RawAttribute a = m.getAttribute(); 336 String attrName = a.getAttributeType(); 337 StringBuilder modTypeLine = new StringBuilder(); 338 modTypeLine.append(m.getModificationType().getLDIFName()); 339 modTypeLine.append(": "); 340 modTypeLine.append(attrName); 341 writeLDIFLine(modTypeLine, writer, wrapLines, wrapColumn); 342 343 for (ASN1OctetString s : a.getValues()) 344 { 345 StringBuilder valueLine = new StringBuilder(); 346 String stringValue = s.stringValue(); 347 348 valueLine.append(attrName); 349 if (needsBase64Encoding(stringValue)) 350 { 351 valueLine.append(":: "); 352 valueLine.append(Base64.encode(s.value())); 353 } 354 else 355 { 356 valueLine.append(": "); 357 valueLine.append(stringValue); 358 } 359 360 writeLDIFLine(valueLine, writer, wrapLines, wrapColumn); 361 } 362 363 if (iterator.hasNext()) 364 { 365 StringBuilder dashLine = new StringBuilder("-"); 366 writeLDIFLine(dashLine, writer, wrapLines, wrapColumn); 367 } 368 } 369 } 370 else if (changeRecord instanceof ModifyDNChangeRecordEntry) 371 { 372 StringBuilder changeTypeLine = new StringBuilder("changetype: moddn"); 373 writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn); 374 375 ModifyDNChangeRecordEntry modifyDNRecord = 376 (ModifyDNChangeRecordEntry) changeRecord; 377 378 StringBuilder newRDNLine = new StringBuilder(); 379 newRDNLine.append("newrdn: "); 380 modifyDNRecord.getNewRDN().toString(newRDNLine); 381 writeLDIFLine(newRDNLine, writer, wrapLines, wrapColumn); 382 383 StringBuilder deleteOldRDNLine = new StringBuilder(); 384 deleteOldRDNLine.append("deleteoldrdn: "); 385 if (modifyDNRecord.deleteOldRDN()) 386 { 387 deleteOldRDNLine.append("1"); 388 } 389 else 390 { 391 deleteOldRDNLine.append("0"); 392 } 393 writeLDIFLine(deleteOldRDNLine, writer, wrapLines, wrapColumn); 394 395 DN newSuperiorDN = modifyDNRecord.getNewSuperiorDN(); 396 if (newSuperiorDN != null) 397 { 398 StringBuilder newSuperiorLine = new StringBuilder(); 399 newSuperiorLine.append("newsuperior: "); 400 newSuperiorDN.toString(newSuperiorLine); 401 writeLDIFLine(newSuperiorLine, writer, wrapLines, wrapColumn); 402 } 403 } 404 405 406 // Make sure there is a blank line after the entry. 407 writer.newLine(); 408 } 409 410 411 412 /** 413 * Writes an add change record for the provided entry. No filtering will be 414 * performed for this entry, nor will any export plugins be invoked. Further, 415 * only the user attributes will be included. 416 * 417 * @param entry The entry to include in the add change record. It must not 418 * be <CODE>null</CODE>. 419 * 420 * @throws IOException If a problem occurs while writing the add record. 421 */ 422 public void writeAddChangeRecord(Entry entry) 423 throws IOException 424 { 425 ensureNotNull(entry); 426 427 428 // Get the information necessary to write the LDIF. 429 BufferedWriter writer = exportConfig.getWriter(); 430 int wrapColumn = exportConfig.getWrapColumn(); 431 boolean wrapLines = (wrapColumn > 1); 432 433 434 // First, write the DN. 435 StringBuilder dnLine = new StringBuilder(); 436 dnLine.append("dn"); 437 appendLDIFSeparatorAndValue(dnLine, getBytes(entry.getDN().toString())); 438 writeLDIFLine(dnLine, writer, wrapLines, wrapColumn); 439 440 441 // Next, the changetype. 442 StringBuilder changeTypeLine = new StringBuilder("changetype: add"); 443 writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn); 444 445 446 // Now the objectclasses. 447 for (String s : entry.getObjectClasses().values()) 448 { 449 StringBuilder ocLine = new StringBuilder(); 450 ocLine.append("objectClass: "); 451 ocLine.append(s); 452 writeLDIFLine(ocLine, writer, wrapLines, wrapColumn); 453 } 454 455 456 // Finally, the set of user attributes. 457 for (AttributeType attrType : entry.getUserAttributes().keySet()) 458 { 459 List<Attribute> attrList = entry.getUserAttribute(attrType); 460 for (Attribute a : attrList) 461 { 462 StringBuilder attrName = new StringBuilder(a.getName()); 463 for (String o : a.getOptions()) 464 { 465 attrName.append(";"); 466 attrName.append(o); 467 } 468 469 for (AttributeValue v : a.getValues()) 470 { 471 StringBuilder attrLine = new StringBuilder(); 472 attrLine.append(attrName); 473 appendLDIFSeparatorAndValue(attrLine, v.getValueBytes()); 474 writeLDIFLine(attrLine, writer, wrapLines, wrapColumn); 475 } 476 } 477 } 478 479 480 // Make sure there is a blank line after the entry. 481 writer.newLine(); 482 } 483 484 485 486 /** 487 * Writes a delete change record for the provided entry, optionally including 488 * a comment with the full entry contents. No filtering will be performed for 489 * this entry, nor will any export plugins be invoked. Further, only the user 490 * attributes will be included. 491 * 492 * @param entry The entry to include in the delete change record. It 493 * must not be <CODE>null</CODE>. 494 * @param commentEntry Indicates whether to include a comment with the 495 * contents of the entry. 496 * 497 * @throws IOException If a problem occurs while writing the delete record. 498 */ 499 public void writeDeleteChangeRecord(Entry entry, boolean commentEntry) 500 throws IOException 501 { 502 ensureNotNull(entry); 503 504 // Get the information necessary to write the LDIF. 505 BufferedWriter writer = exportConfig.getWriter(); 506 int wrapColumn = exportConfig.getWrapColumn(); 507 boolean wrapLines = (wrapColumn > 1); 508 509 510 // Add the DN and changetype lines. 511 StringBuilder dnLine = new StringBuilder(); 512 dnLine.append("dn"); 513 appendLDIFSeparatorAndValue(dnLine, getBytes(entry.getDN().toString())); 514 writeLDIFLine(dnLine, writer, wrapLines, wrapColumn); 515 516 StringBuilder changeTypeLine = new StringBuilder("changetype: delete"); 517 writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn); 518 519 520 // If we should include a comment with the rest of the entry contents, then 521 // do so now. 522 if (commentEntry) 523 { 524 // Write the objectclasses. 525 for (String s : entry.getObjectClasses().values()) 526 { 527 StringBuilder ocLine = new StringBuilder(); 528 ocLine.append("# objectClass: "); 529 ocLine.append(s); 530 writeLDIFLine(ocLine, writer, wrapLines, wrapColumn); 531 } 532 533 // Write the set of user attributes. 534 for (AttributeType attrType : entry.getUserAttributes().keySet()) 535 { 536 List<Attribute> attrList = entry.getUserAttribute(attrType); 537 for (Attribute a : attrList) 538 { 539 StringBuilder attrName = new StringBuilder(); 540 attrName.append("# "); 541 attrName.append(a.getName()); 542 for (String o : a.getOptions()) 543 { 544 attrName.append(";"); 545 attrName.append(o); 546 } 547 548 for (AttributeValue v : a.getValues()) 549 { 550 StringBuilder attrLine = new StringBuilder(); 551 attrLine.append(attrName); 552 appendLDIFSeparatorAndValue(attrLine, v.getValueBytes()); 553 writeLDIFLine(attrLine, writer, wrapLines, wrapColumn); 554 } 555 } 556 } 557 } 558 559 560 // Make sure there is a blank line after the entry. 561 writer.newLine(); 562 } 563 564 565 566 /** 567 * Writes a modify change record with the provided information. No filtering 568 * will be performed, nor will any export plugins be invoked. 569 * 570 * @param dn The DN of the entry being modified. It must not be 571 * <CODE>null</CODE>. 572 * @param modifications The set of modifications to include in the change 573 * record. It must not be <CODE>null</CODE>. 574 * 575 * @throws IOException If a problem occurs while writing the modify record. 576 */ 577 public void writeModifyChangeRecord(DN dn, List<Modification> modifications) 578 throws IOException 579 { 580 ensureNotNull(dn, modifications); 581 582 // If there aren't any modifications, then there's nothing to do. 583 if (modifications.isEmpty()) 584 { 585 return; 586 } 587 588 589 // Get the information necessary to write the LDIF. 590 BufferedWriter writer = exportConfig.getWriter(); 591 int wrapColumn = exportConfig.getWrapColumn(); 592 boolean wrapLines = (wrapColumn > 1); 593 594 595 // Write the DN and changetype. 596 StringBuilder dnLine = new StringBuilder(); 597 dnLine.append("dn"); 598 appendLDIFSeparatorAndValue(dnLine, getBytes(dn.toString())); 599 writeLDIFLine(dnLine, writer, wrapLines, wrapColumn); 600 601 StringBuilder changeTypeLine = new StringBuilder("changetype: modify"); 602 writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn); 603 604 605 // Iterate through the modifications and write them to the LDIF. 606 Iterator<Modification> iterator = modifications.iterator(); 607 while (iterator.hasNext()) 608 { 609 Modification m = iterator.next(); 610 Attribute a = m.getAttribute(); 611 612 StringBuilder nameBuffer = new StringBuilder(a.getName()); 613 for (String o : a.getOptions()) 614 { 615 nameBuffer.append(";"); 616 nameBuffer.append(o); 617 } 618 String name = nameBuffer.toString(); 619 620 StringBuilder modTypeLine = new StringBuilder(); 621 switch (m.getModificationType()) 622 { 623 case ADD: 624 modTypeLine.append("add: "); 625 modTypeLine.append(name); 626 break; 627 case DELETE: 628 modTypeLine.append("delete: "); 629 modTypeLine.append(name); 630 break; 631 case REPLACE: 632 modTypeLine.append("replace: "); 633 modTypeLine.append(name); 634 break; 635 case INCREMENT: 636 modTypeLine.append("increment: "); 637 modTypeLine.append(name); 638 break; 639 default: 640 // We have no idea what the changetype is, so we can't write anything. 641 continue; 642 } 643 writeLDIFLine(modTypeLine, writer, wrapLines, wrapColumn); 644 645 for (AttributeValue v : a.getValues()) 646 { 647 StringBuilder valueLine = new StringBuilder(); 648 valueLine.append(name); 649 appendLDIFSeparatorAndValue(valueLine, v.getValueBytes()); 650 writeLDIFLine(valueLine, writer, wrapLines, wrapColumn); 651 } 652 653 654 // If this is the last modification, then append blank line. Otherwise 655 // write a line with just a dash. 656 if (iterator.hasNext()) 657 { 658 writer.write("-"); 659 writer.newLine(); 660 } 661 else 662 { 663 writer.newLine(); 664 } 665 } 666 } 667 668 669 670 /** 671 * Writes a modify DN change record with the provided information. No 672 * filtering will be performed, nor will any export plugins be invoked. 673 * 674 * @param dn The DN of the entry before the rename. It must not 675 * be <CODE>null</CODE>. 676 * @param newRDN The new RDN for the entry. It must not be 677 * <CODE>null</CODE>. 678 * @param deleteOldRDN Indicates whether the old RDN value should be removed 679 * from the entry. 680 * @param newSuperior The new superior DN for the entry, or 681 * <CODE>null</CODE> if the entry will stay below the 682 * same parent. 683 * 684 * @throws IOException If a problem occurs while writing the modify record. 685 */ 686 public void writeModifyDNChangeRecord(DN dn, RDN newRDN, boolean deleteOldRDN, 687 DN newSuperior) 688 throws IOException 689 { 690 ensureNotNull(dn, newRDN); 691 692 693 // Get the information necessary to write the LDIF. 694 BufferedWriter writer = exportConfig.getWriter(); 695 int wrapColumn = exportConfig.getWrapColumn(); 696 boolean wrapLines = (wrapColumn > 1); 697 698 699 // Write the current DN. 700 StringBuilder dnLine = new StringBuilder(); 701 dnLine.append("dn"); 702 appendLDIFSeparatorAndValue(dnLine, getBytes(dn.toString())); 703 writeLDIFLine(dnLine, writer, wrapLines, wrapColumn); 704 705 706 // Write the changetype. Some older tools may not support the "moddn" 707 // changetype, so only use it if a newSuperior element has been provided, 708 // but use modrdn elsewhere. 709 if (newSuperior == null) 710 { 711 StringBuilder changeTypeLine = new StringBuilder("changetype: modrdn"); 712 writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn); 713 } 714 else 715 { 716 StringBuilder changeTypeLine = new StringBuilder("changetype: moddn"); 717 writeLDIFLine(changeTypeLine, writer, wrapLines, wrapColumn); 718 } 719 720 721 // Write the newRDN element. 722 StringBuilder rdnLine = new StringBuilder(); 723 rdnLine.append("newrdn"); 724 appendLDIFSeparatorAndValue(rdnLine, getBytes(newRDN.toString())); 725 writeLDIFLine(rdnLine, writer, wrapLines, wrapColumn); 726 727 728 // Write the deleteOldRDN element. 729 StringBuilder deleteOldRDNLine = new StringBuilder(); 730 deleteOldRDNLine.append("deleteoldrdn: "); 731 deleteOldRDNLine.append(deleteOldRDN ? "1" : "0"); 732 writeLDIFLine(deleteOldRDNLine, writer, wrapLines, wrapColumn); 733 734 if (newSuperior != null) 735 { 736 StringBuilder newSuperiorLine = new StringBuilder(); 737 newSuperiorLine.append("newsuperior"); 738 appendLDIFSeparatorAndValue(newSuperiorLine, 739 getBytes(newSuperior.toString())); 740 writeLDIFLine(newSuperiorLine, writer, wrapLines, wrapColumn); 741 } 742 743 744 // Make sure there is a blank line after the entry. 745 writer.newLine(); 746 } 747 748 749 750 /** 751 * Flushes the data written to the output stream or underlying file. 752 * 753 * @throws IOException If a problem occurs while flushing the output. 754 */ 755 public void flush() 756 throws IOException 757 { 758 writer.flush(); 759 } 760 761 762 763 /** 764 * Closes the LDIF writer and the underlying output stream or file. 765 * 766 * @throws IOException If a problem occurs while closing the writer. 767 */ 768 public void close() 769 throws IOException 770 { 771 writer.flush(); 772 writer.close(); 773 } 774 775 776 777 /** 778 * Appends an LDIF separator and properly-encoded form of the given 779 * value to the provided buffer. If the value is safe to include 780 * as-is, then a single colon, a single space, space, and the 781 * provided value will be appended. Otherwise, two colons, a single 782 * space, and a base64-encoded form of the value will be appended. 783 * 784 * @param buffer The buffer to which the information should be 785 * appended. It must not be <CODE>null</CODE>. 786 * @param valueBytes The value to append to the buffer. It must not be 787 * <CODE>null</CODE>. 788 */ 789 public static void appendLDIFSeparatorAndValue(StringBuilder buffer, 790 byte[] valueBytes) 791 { 792 ensureNotNull(buffer, valueBytes); 793 794 795 // If the value is empty, then just append a single colon and a single 796 // space. 797 if ((valueBytes == null) || (valueBytes.length == 0)) 798 { 799 buffer.append(": "); 800 return; 801 } 802 803 804 if (needsBase64Encoding(valueBytes)) 805 { 806 buffer.append(":: "); 807 buffer.append(Base64.encode(valueBytes)); 808 } 809 else 810 { 811 buffer.append(": "); 812 813 try 814 { 815 buffer.append(new String(valueBytes, "UTF-8")); 816 } 817 catch (Exception e) 818 { 819 // This should never happen. 820 if (debugEnabled()) 821 { 822 TRACER.debugCaught(DebugLogLevel.ERROR, e); 823 } 824 buffer.append(new String(valueBytes)); 825 } 826 } 827 } 828 829 830 831 /** 832 * Writes the provided line to LDIF using the provided information. 833 * 834 * @param line The line of information to write. It must not be 835 * <CODE>null</CODE>. 836 * @param writer The writer to which the data should be written. It 837 * must not be <CODE>null</CODE>. 838 * @param wrapLines Indicates whether to wrap long lines. 839 * @param wrapColumn The column at which long lines should be wrapped. 840 * 841 * @throws IOException If a problem occurs while writing the information. 842 */ 843 public static void writeLDIFLine(StringBuilder line, BufferedWriter writer, 844 boolean wrapLines, int wrapColumn) 845 throws IOException 846 { 847 ensureNotNull(line, writer); 848 849 int length = line.length(); 850 if (wrapLines && (length > wrapColumn)) 851 { 852 writer.write(line.substring(0, wrapColumn)); 853 writer.newLine(); 854 855 int pos = wrapColumn; 856 while (pos < length) 857 { 858 int writeLength = Math.min(wrapColumn-1, length-pos); 859 writer.write(' '); 860 writer.write(line.substring(pos, pos+writeLength)); 861 writer.newLine(); 862 863 pos += wrapColumn-1; 864 } 865 } 866 else 867 { 868 writer.write(line.toString()); 869 writer.newLine(); 870 } 871 } 872 } 873