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