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