001 /*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2008-2017 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 Thread.currentThread().interrupt();
394 }
395 }
396 }
397 finally
398 {
399 writer.close();
400 }
401 }
402
403
404
405 /**
406 * Retrieves the column at which to wrap long lines.
407 *
408 * @return The column at which to wrap long lines, or zero to indicate that
409 * long lines should not be wrapped.
410 */
411 public int getWrapColumn()
412 {
413 return wrapColumn;
414 }
415
416
417
418 /**
419 * Specifies the column at which to wrap long lines. A value of zero
420 * indicates that long lines should not be wrapped.
421 *
422 * @param wrapColumn The column at which to wrap long lines.
423 */
424 public void setWrapColumn(final int wrapColumn)
425 {
426 this.wrapColumn = wrapColumn;
427
428 wrapColumnMinusTwo = wrapColumn - 2;
429 }
430
431
432
433 /**
434 * Indicates whether the LDIF writer should generate comments that attempt to
435 * provide unencoded representations (with special characters escaped) of any
436 * base64-encoded values in entries and change records that are written by
437 * this writer.
438 *
439 * @return {@code true} if the LDIF writer should generate comments that
440 * attempt to provide unencoded representations of any base64-encoded
441 * values, or {@code false} if not.
442 */
443 public static boolean commentAboutBase64EncodedValues()
444 {
445 return commentAboutBase64EncodedValues;
446 }
447
448
449
450 /**
451 * Specifies whether the LDIF writer should generate comments that attempt to
452 * provide unencoded representations (with special characters escaped) of any
453 * base64-encoded values in entries and change records that are written by
454 * this writer.
455 *
456 * @param commentAboutBase64EncodedValues Indicates whether the LDIF writer
457 * should generate comments that
458 * attempt to provide unencoded
459 * representations (with special
460 * characters escaped) of any
461 * base64-encoded values in entries
462 * and change records that are
463 * written by this writer.
464 */
465 public static void setCommentAboutBase64EncodedValues(
466 final boolean commentAboutBase64EncodedValues)
467 {
468 LDIFWriter.commentAboutBase64EncodedValues =
469 commentAboutBase64EncodedValues;
470 }
471
472
473
474 /**
475 * Writes the LDIF version header (i.e.,"version: 1"). If a version header
476 * is to be added to the LDIF content, it should be done before any entries or
477 * change records have been written.
478 *
479 * @throws IOException If a problem occurs while writing the version header.
480 */
481 public void writeVersionHeader()
482 throws IOException
483 {
484 writer.write(VERSION_1_HEADER_BYTES);
485 }
486
487
488
489 /**
490 * Writes the provided entry in LDIF form.
491 *
492 * @param entry The entry to be written. It must not be {@code null}.
493 *
494 * @throws IOException If a problem occurs while writing the LDIF data.
495 */
496 public void writeEntry(final Entry entry)
497 throws IOException
498 {
499 writeEntry(entry, null);
500 }
501
502
503
504 /**
505 * Writes the provided entry in LDIF form, preceded by the provided comment.
506 *
507 * @param entry The entry to be written in LDIF form. It must not be
508 * {@code null}.
509 * @param comment The comment to be written before the entry. It may be
510 * {@code null} if no comment is to be written.
511 *
512 * @throws IOException If a problem occurs while writing the LDIF data.
513 */
514 public void writeEntry(final Entry entry, final String comment)
515 throws IOException
516 {
517 ensureNotNull(entry);
518
519 final Entry e;
520 if (entryTranslator == null)
521 {
522 e = entry;
523 }
524 else
525 {
526 e = entryTranslator.translateEntryToWrite(entry);
527 if (e == null)
528 {
529 return;
530 }
531 }
532
533 if (comment != null)
534 {
535 writeComment(comment, false, false);
536 }
537
538 debugLDIFWrite(e);
539 writeLDIF(e);
540 }
541
542
543
544 /**
545 * Writes the provided change record in LDIF form.
546 *
547 * @param changeRecord The change record to be written. It must not be
548 * {@code null}.
549 *
550 * @throws IOException If a problem occurs while writing the LDIF data.
551 */
552 public void writeChangeRecord(final LDIFChangeRecord changeRecord)
553 throws IOException
554 {
555 writeChangeRecord(changeRecord, null);
556 }
557
558
559
560 /**
561 * Writes the provided change record in LDIF form, preceded by the provided
562 * comment.
563 *
564 * @param changeRecord The change record to be written. It must not be
565 * {@code null}.
566 * @param comment The comment to be written before the entry. It may
567 * be {@code null} if no comment is to be written.
568 *
569 * @throws IOException If a problem occurs while writing the LDIF data.
570 */
571 public void writeChangeRecord(final LDIFChangeRecord changeRecord,
572 final String comment)
573 throws IOException
574 {
575 ensureNotNull(changeRecord);
576
577 final LDIFChangeRecord r;
578 if (changeRecordTranslator == null)
579 {
580 r = changeRecord;
581 }
582 else
583 {
584 r = changeRecordTranslator.translateChangeRecordToWrite(changeRecord);
585 if (r == null)
586 {
587 return;
588 }
589 }
590
591 if (comment != null)
592 {
593 writeComment(comment, false, false);
594 }
595
596 debugLDIFWrite(r);
597 writeLDIF(r);
598 }
599
600
601
602 /**
603 * Writes the provided record in LDIF form.
604 *
605 * @param record The LDIF record to be written. It must not be
606 * {@code null}.
607 *
608 * @throws IOException If a problem occurs while writing the LDIF data.
609 */
610 public void writeLDIFRecord(final LDIFRecord record)
611 throws IOException
612 {
613 writeLDIFRecord(record, null);
614 }
615
616
617
618 /**
619 * Writes the provided record in LDIF form, preceded by the provided comment.
620 *
621 * @param record The LDIF record to be written. It must not be
622 * {@code null}.
623 * @param comment The comment to be written before the LDIF record. It may
624 * be {@code null} if no comment is to be written.
625 *
626 * @throws IOException If a problem occurs while writing the LDIF data.
627 */
628 public void writeLDIFRecord(final LDIFRecord record, final String comment)
629 throws IOException
630 {
631 ensureNotNull(record);
632
633 final LDIFRecord r;
634 if ((entryTranslator != null) && (record instanceof Entry))
635 {
636 r = entryTranslator.translateEntryToWrite((Entry) record);
637 if (r == null)
638 {
639 return;
640 }
641 }
642 else if ((changeRecordTranslator != null) &&
643 (record instanceof LDIFChangeRecord))
644 {
645 r = changeRecordTranslator.translateChangeRecordToWrite(
646 (LDIFChangeRecord) record);
647 if (r == null)
648 {
649 return;
650 }
651 }
652 else
653 {
654 r = record;
655 }
656
657 debugLDIFWrite(r);
658 if (comment != null)
659 {
660 writeComment(comment, false, false);
661 }
662
663 writeLDIF(r);
664 }
665
666
667
668 /**
669 * Writes the provided list of LDIF records (most likely Entries) to the
670 * output. If this LDIFWriter was constructed without any parallel
671 * output threads, then this behaves identically to calling
672 * {@code writeLDIFRecord()} sequentially for each item in the list.
673 * If this LDIFWriter was constructed to write records in parallel, then
674 * the configured number of threads are used to convert the records to raw
675 * bytes, which are sequentially written to the input file. This can speed up
676 * the total time to write a large set of records. Either way, the output
677 * records are guaranteed to be written in the order they appear in the list.
678 *
679 * @param ldifRecords The LDIF records (most likely entries) to write to the
680 * output.
681 *
682 * @throws IOException If a problem occurs while writing the LDIF data.
683 *
684 * @throws InterruptedException If this thread is interrupted while waiting
685 * for the records to be written to the output.
686 */
687 public void writeLDIFRecords(final List<? extends LDIFRecord> ldifRecords)
688 throws IOException, InterruptedException
689 {
690 if (toLdifBytesInvoker == null)
691 {
692 for (final LDIFRecord ldifRecord : ldifRecords)
693 {
694 writeLDIFRecord(ldifRecord);
695 }
696 }
697 else
698 {
699 final List<Result<LDIFRecord,ByteStringBuffer>> results =
700 toLdifBytesInvoker.processAll(ldifRecords);
701 for (final Result<LDIFRecord,ByteStringBuffer> result: results)
702 {
703 rethrow(result.getFailureCause());
704
705 final ByteStringBuffer encodedBytes = result.getOutput();
706 if (encodedBytes != null)
707 {
708 encodedBytes.write(writer);
709 writer.write(EOL_BYTES);
710 }
711 }
712 }
713 }
714
715
716
717
718 /**
719 * Writes the provided comment to the LDIF target, wrapping long lines as
720 * necessary.
721 *
722 * @param comment The comment to be written to the LDIF target. It must
723 * not be {@code null}.
724 * @param spaceBefore Indicates whether to insert a blank line before the
725 * comment.
726 * @param spaceAfter Indicates whether to insert a blank line after the
727 * comment.
728 *
729 * @throws IOException If a problem occurs while writing the LDIF data.
730 */
731 public void writeComment(final String comment, final boolean spaceBefore,
732 final boolean spaceAfter)
733 throws IOException
734 {
735 ensureNotNull(comment);
736 if (spaceBefore)
737 {
738 writer.write(EOL_BYTES);
739 }
740
741 //
742 // Check for a newline explicitly to avoid the overhead of the regex
743 // for the common case of a single-line comment.
744 //
745
746 if (comment.indexOf('\n') < 0)
747 {
748 writeSingleLineComment(comment);
749 }
750 else
751 {
752 //
753 // Split on blank lines and wrap each line individually.
754 //
755
756 final String[] lines = comment.split("\\r?\\n");
757 for (final String line: lines)
758 {
759 writeSingleLineComment(line);
760 }
761 }
762
763 if (spaceAfter)
764 {
765 writer.write(EOL_BYTES);
766 }
767 }
768
769
770
771 /**
772 * Writes the provided comment to the LDIF target, wrapping long lines as
773 * necessary.
774 *
775 * @param comment The comment to be written to the LDIF target. It must
776 * not be {@code null}, and it must not include any line
777 * breaks.
778 *
779 * @throws IOException If a problem occurs while writing the LDIF data.
780 */
781 private void writeSingleLineComment(final String comment)
782 throws IOException
783 {
784 // We will always wrap comments, even if we won't wrap LDIF entries. If
785 // there is a wrap column set, then use it. Otherwise use the terminal
786 // width and back off two characters for the "# " at the beginning.
787 final int commentWrapMinusTwo;
788 if (wrapColumn <= 0)
789 {
790 commentWrapMinusTwo = TERMINAL_WIDTH_COLUMNS - 3;
791 }
792 else
793 {
794 commentWrapMinusTwo = wrapColumnMinusTwo;
795 }
796
797 buffer.clear();
798 final int length = comment.length();
799 if (length <= commentWrapMinusTwo)
800 {
801 buffer.append("# ");
802 buffer.append(comment);
803 buffer.append(EOL_BYTES);
804 }
805 else
806 {
807 int minPos = 0;
808 while (minPos < length)
809 {
810 if ((length - minPos) <= commentWrapMinusTwo)
811 {
812 buffer.append("# ");
813 buffer.append(comment.substring(minPos));
814 buffer.append(EOL_BYTES);
815 break;
816 }
817
818 // First, adjust the position until we find a space. Go backwards if
819 // possible, but if we can't find one there then go forward.
820 boolean spaceFound = false;
821 final int pos = minPos + commentWrapMinusTwo;
822 int spacePos = pos;
823 while (spacePos > minPos)
824 {
825 if (comment.charAt(spacePos) == ' ')
826 {
827 spaceFound = true;
828 break;
829 }
830
831 spacePos--;
832 }
833
834 if (! spaceFound)
835 {
836 spacePos = pos + 1;
837 while (spacePos < length)
838 {
839 if (comment.charAt(spacePos) == ' ')
840 {
841 spaceFound = true;
842 break;
843 }
844
845 spacePos++;
846 }
847
848 if (! spaceFound)
849 {
850 // There are no spaces at all in the remainder of the comment, so
851 // we'll just write the remainder of it all at once.
852 buffer.append("# ");
853 buffer.append(comment.substring(minPos));
854 buffer.append(EOL_BYTES);
855 break;
856 }
857 }
858
859 // We have a space, so we'll write up to the space position and then
860 // start up after the next space.
861 buffer.append("# ");
862 buffer.append(comment.substring(minPos, spacePos));
863 buffer.append(EOL_BYTES);
864
865 minPos = spacePos + 1;
866 while ((minPos < length) && (comment.charAt(minPos) == ' '))
867 {
868 minPos++;
869 }
870 }
871 }
872
873 buffer.write(writer);
874 }
875
876
877
878 /**
879 * Writes the provided record to the LDIF target, wrapping long lines as
880 * necessary.
881 *
882 * @param record The LDIF record to be written.
883 *
884 * @throws IOException If a problem occurs while writing the LDIF data.
885 */
886 private void writeLDIF(final LDIFRecord record)
887 throws IOException
888 {
889 buffer.clear();
890 record.toLDIF(buffer, wrapColumn);
891 buffer.append(EOL_BYTES);
892 buffer.write(writer);
893 }
894
895
896
897 /**
898 * Performs any appropriate wrapping for the provided set of LDIF lines.
899 *
900 * @param wrapColumn The column at which to wrap long lines. A value that
901 * is less than or equal to two indicates that no
902 * wrapping should be performed.
903 * @param ldifLines The set of lines that make up the LDIF data to be
904 * wrapped.
905 *
906 * @return A new list of lines that have been wrapped as appropriate.
907 */
908 public static List<String> wrapLines(final int wrapColumn,
909 final String... ldifLines)
910 {
911 return wrapLines(wrapColumn, Arrays.asList(ldifLines));
912 }
913
914
915
916 /**
917 * Performs any appropriate wrapping for the provided set of LDIF lines.
918 *
919 * @param wrapColumn The column at which to wrap long lines. A value that
920 * is less than or equal to two indicates that no
921 * wrapping should be performed.
922 * @param ldifLines The set of lines that make up the LDIF data to be
923 * wrapped.
924 *
925 * @return A new list of lines that have been wrapped as appropriate.
926 */
927 public static List<String> wrapLines(final int wrapColumn,
928 final List<String> ldifLines)
929 {
930 if (wrapColumn <= 2)
931 {
932 return new ArrayList<String>(ldifLines);
933 }
934
935 final ArrayList<String> newLines = new ArrayList<String>(ldifLines.size());
936 for (final String s : ldifLines)
937 {
938 final int length = s.length();
939 if (length <= wrapColumn)
940 {
941 newLines.add(s);
942 continue;
943 }
944
945 newLines.add(s.substring(0, wrapColumn));
946
947 int pos = wrapColumn;
948 while (pos < length)
949 {
950 if ((length - pos + 1) <= wrapColumn)
951 {
952 newLines.add(' ' + s.substring(pos));
953 break;
954 }
955 else
956 {
957 newLines.add(' ' + s.substring(pos, (pos+wrapColumn-1)));
958 pos += wrapColumn - 1;
959 }
960 }
961 }
962
963 return newLines;
964 }
965
966
967
968 /**
969 * Creates a string consisting of the provided attribute name followed by
970 * either a single colon and the string representation of the provided value,
971 * or two colons and the base64-encoded representation of the provided value.
972 *
973 * @param name The name for the attribute.
974 * @param value The value for the attribute.
975 *
976 * @return A string consisting of the provided attribute name followed by
977 * either a single colon and the string representation of the
978 * provided value, or two colons and the base64-encoded
979 * representation of the provided value.
980 */
981 public static String encodeNameAndValue(final String name,
982 final ASN1OctetString value)
983 {
984 final StringBuilder buffer = new StringBuilder();
985 encodeNameAndValue(name, value, buffer);
986 return buffer.toString();
987 }
988
989
990
991 /**
992 * Appends a string to the provided buffer consisting of the provided
993 * attribute name followed by either a single colon and the string
994 * representation of the provided value, or two colons and the base64-encoded
995 * representation of the provided value.
996 *
997 * @param name The name for the attribute.
998 * @param value The value for the attribute.
999 * @param buffer The buffer to which the name and value are to be written.
1000 */
1001 public static void encodeNameAndValue(final String name,
1002 final ASN1OctetString value,
1003 final StringBuilder buffer)
1004 {
1005 encodeNameAndValue(name, value, buffer, 0);
1006 }
1007
1008
1009
1010 /**
1011 * Appends a string to the provided buffer consisting of the provided
1012 * attribute name followed by either a single colon and the string
1013 * representation of the provided value, or two colons and the base64-encoded
1014 * representation of the provided value.
1015 *
1016 * @param name The name for the attribute.
1017 * @param value The value for the attribute.
1018 * @param buffer The buffer to which the name and value are to be
1019 * written.
1020 * @param wrapColumn The column at which to wrap long lines. A value that
1021 * is less than or equal to two indicates that no
1022 * wrapping should be performed.
1023 */
1024 public static void encodeNameAndValue(final String name,
1025 final ASN1OctetString value,
1026 final StringBuilder buffer,
1027 final int wrapColumn)
1028 {
1029 final int bufferStartPos = buffer.length();
1030 final byte[] valueBytes = value.getValue();
1031 boolean base64Encoded = false;
1032
1033 try
1034 {
1035 buffer.append(name);
1036 buffer.append(':');
1037
1038 final int length = valueBytes.length;
1039 if (length == 0)
1040 {
1041 buffer.append(' ');
1042 return;
1043 }
1044
1045 // If the value starts with a space, colon, or less-than character, then
1046 // it must be base64-encoded.
1047 switch (valueBytes[0])
1048 {
1049 case ' ':
1050 case ':':
1051 case '<':
1052 buffer.append(": ");
1053 Base64.encode(valueBytes, buffer);
1054 base64Encoded = true;
1055 return;
1056 }
1057
1058 // If the value ends with a space, then it should be base64-encoded.
1059 if (valueBytes[length-1] == ' ')
1060 {
1061 buffer.append(": ");
1062 Base64.encode(valueBytes, buffer);
1063 base64Encoded = true;
1064 return;
1065 }
1066
1067 // If any character in the value is outside the ASCII range, or is the
1068 // NUL, LF, or CR character, then the value should be base64-encoded.
1069 for (int i=0; i < length; i++)
1070 {
1071 if ((valueBytes[i] & 0x7F) != (valueBytes[i] & 0xFF))
1072 {
1073 buffer.append(": ");
1074 Base64.encode(valueBytes, buffer);
1075 base64Encoded = true;
1076 return;
1077 }
1078
1079 switch (valueBytes[i])
1080 {
1081 case 0x00: // The NUL character
1082 case 0x0A: // The LF character
1083 case 0x0D: // The CR character
1084 buffer.append(": ");
1085 Base64.encode(valueBytes, buffer);
1086 base64Encoded = true;
1087 return;
1088 }
1089 }
1090
1091 // If we've gotten here, then the string value is acceptable.
1092 buffer.append(' ');
1093 buffer.append(value.stringValue());
1094 }
1095 finally
1096 {
1097 if (wrapColumn > 2)
1098 {
1099 final int length = buffer.length() - bufferStartPos;
1100 if (length > wrapColumn)
1101 {
1102 final String EOL_PLUS_SPACE = EOL + ' ';
1103 buffer.insert((bufferStartPos+wrapColumn), EOL_PLUS_SPACE);
1104
1105 int pos = bufferStartPos + (2*wrapColumn) +
1106 EOL_PLUS_SPACE.length() - 1;
1107 while (pos < buffer.length())
1108 {
1109 buffer.insert(pos, EOL_PLUS_SPACE);
1110 pos += (wrapColumn - 1 + EOL_PLUS_SPACE.length());
1111 }
1112 }
1113 }
1114
1115 if (base64Encoded && commentAboutBase64EncodedValues)
1116 {
1117 writeBase64DecodedValueComment(valueBytes, buffer, wrapColumn);
1118 }
1119 }
1120 }
1121
1122
1123
1124 /**
1125 * Appends a comment to the provided buffer with an unencoded representation
1126 * of the provided value. This will only have any effect if
1127 * {@code commentAboutBase64EncodedValues} is {@code true}.
1128 *
1129 * @param valueBytes The bytes that comprise the value.
1130 * @param buffer The buffer to which the comment should be appended.
1131 * @param wrapColumn The column at which to wrap long lines.
1132 */
1133 private static void writeBase64DecodedValueComment(final byte[] valueBytes,
1134 final StringBuilder buffer,
1135 final int wrapColumn)
1136 {
1137 if (commentAboutBase64EncodedValues)
1138 {
1139 final int wrapColumnMinusTwo;
1140 if (wrapColumn <= 5)
1141 {
1142 wrapColumnMinusTwo = TERMINAL_WIDTH_COLUMNS - 3;
1143 }
1144 else
1145 {
1146 wrapColumnMinusTwo = wrapColumn - 2;
1147 }
1148
1149 final int wrapColumnMinusThree = wrapColumnMinusTwo - 1;
1150
1151 boolean first = true;
1152 final String comment =
1153 "Non-base64-encoded representation of the above value: " +
1154 getEscapedValue(valueBytes);
1155 for (final String s :
1156 wrapLine(comment, wrapColumnMinusTwo, wrapColumnMinusThree))
1157 {
1158 buffer.append(EOL);
1159 buffer.append("# ");
1160 if (first)
1161 {
1162 first = false;
1163 }
1164 else
1165 {
1166 buffer.append(' ');
1167 }
1168 buffer.append(s);
1169 }
1170 }
1171 }
1172
1173
1174
1175 /**
1176 * Appends a string to the provided buffer consisting of the provided
1177 * attribute name followed by either a single colon and the string
1178 * representation of the provided value, or two colons and the base64-encoded
1179 * representation of the provided value. It may optionally be wrapped at the
1180 * specified column.
1181 *
1182 * @param name The name for the attribute.
1183 * @param value The value for the attribute.
1184 * @param buffer The buffer to which the name and value are to be
1185 * written.
1186 * @param wrapColumn The column at which to wrap long lines. A value that
1187 * is less than or equal to two indicates that no
1188 * wrapping should be performed.
1189 */
1190 public static void encodeNameAndValue(final String name,
1191 final ASN1OctetString value,
1192 final ByteStringBuffer buffer,
1193 final int wrapColumn)
1194 {
1195 final int bufferStartPos = buffer.length();
1196 boolean base64Encoded = false;
1197
1198 try
1199 {
1200 buffer.append(name);
1201 base64Encoded = encodeValue(value, buffer);
1202 }
1203 finally
1204 {
1205 if (wrapColumn > 2)
1206 {
1207 final int length = buffer.length() - bufferStartPos;
1208 if (length > wrapColumn)
1209 {
1210 final byte[] EOL_BYTES_PLUS_SPACE = new byte[EOL_BYTES.length + 1];
1211 System.arraycopy(EOL_BYTES, 0, EOL_BYTES_PLUS_SPACE, 0,
1212 EOL_BYTES.length);
1213 EOL_BYTES_PLUS_SPACE[EOL_BYTES.length] = ' ';
1214
1215 buffer.insert((bufferStartPos+wrapColumn), EOL_BYTES_PLUS_SPACE);
1216
1217 int pos = bufferStartPos + (2*wrapColumn) +
1218 EOL_BYTES_PLUS_SPACE.length - 1;
1219 while (pos < buffer.length())
1220 {
1221 buffer.insert(pos, EOL_BYTES_PLUS_SPACE);
1222 pos += (wrapColumn - 1 + EOL_BYTES_PLUS_SPACE.length);
1223 }
1224 }
1225 }
1226
1227 if (base64Encoded && commentAboutBase64EncodedValues)
1228 {
1229 writeBase64DecodedValueComment(value.getValue(), buffer, wrapColumn);
1230 }
1231 }
1232 }
1233
1234
1235
1236 /**
1237 * Appends a string to the provided buffer consisting of the properly-encoded
1238 * representation of the provided value, including the necessary colon(s) and
1239 * space that precede it. Depending on the content of the value, it will
1240 * either be used as-is or base64-encoded.
1241 *
1242 * @param value The value for the attribute.
1243 * @param buffer The buffer to which the value is to be written.
1244 *
1245 * @return {@code true} if the value was base64-encoded, or {@code false} if
1246 * not.
1247 */
1248 static boolean encodeValue(final ASN1OctetString value,
1249 final ByteStringBuffer buffer)
1250 {
1251 buffer.append(':');
1252
1253 final byte[] valueBytes = value.getValue();
1254 final int length = valueBytes.length;
1255 if (length == 0)
1256 {
1257 buffer.append(' ');
1258 return false;
1259 }
1260
1261 // If the value starts with a space, colon, or less-than character, then
1262 // it must be base64-encoded.
1263 switch (valueBytes[0])
1264 {
1265 case ' ':
1266 case ':':
1267 case '<':
1268 buffer.append(':');
1269 buffer.append(' ');
1270 Base64.encode(valueBytes, buffer);
1271 return true;
1272 }
1273
1274 // If the value ends with a space, then it should be base64-encoded.
1275 if (valueBytes[length-1] == ' ')
1276 {
1277 buffer.append(':');
1278 buffer.append(' ');
1279 Base64.encode(valueBytes, buffer);
1280 return true;
1281 }
1282
1283 // If any character in the value is outside the ASCII range, or is the
1284 // NUL, LF, or CR character, then the value should be base64-encoded.
1285 for (int i=0; i < length; i++)
1286 {
1287 if ((valueBytes[i] & 0x7F) != (valueBytes[i] & 0xFF))
1288 {
1289 buffer.append(':');
1290 buffer.append(' ');
1291 Base64.encode(valueBytes, buffer);
1292 return true;
1293 }
1294
1295 switch (valueBytes[i])
1296 {
1297 case 0x00: // The NUL character
1298 case 0x0A: // The LF character
1299 case 0x0D: // The CR character
1300 buffer.append(':');
1301 buffer.append(' ');
1302
1303 Base64.encode(valueBytes, buffer);
1304 return true;
1305 }
1306 }
1307
1308 // If we've gotten here, then the string value is acceptable.
1309 buffer.append(' ');
1310 buffer.append(valueBytes);
1311 return false;
1312 }
1313
1314
1315
1316 /**
1317 * Appends a comment to the provided buffer with an unencoded representation
1318 * of the provided value. This will only have any effect if
1319 * {@code commentAboutBase64EncodedValues} is {@code true}.
1320 *
1321 * @param valueBytes The bytes that comprise the value.
1322 * @param buffer The buffer to which the comment should be appended.
1323 * @param wrapColumn The column at which to wrap long lines.
1324 */
1325 private static void writeBase64DecodedValueComment(final byte[] valueBytes,
1326 final ByteStringBuffer buffer,
1327 final int wrapColumn)
1328 {
1329 if (commentAboutBase64EncodedValues)
1330 {
1331 final int wrapColumnMinusTwo;
1332 if (wrapColumn <= 5)
1333 {
1334 wrapColumnMinusTwo = TERMINAL_WIDTH_COLUMNS - 3;
1335 }
1336 else
1337 {
1338 wrapColumnMinusTwo = wrapColumn - 2;
1339 }
1340
1341 final int wrapColumnMinusThree = wrapColumnMinusTwo - 1;
1342
1343 boolean first = true;
1344 final String comment =
1345 "Non-base64-encoded representation of the above value: " +
1346 getEscapedValue(valueBytes);
1347 for (final String s :
1348 wrapLine(comment, wrapColumnMinusTwo, wrapColumnMinusThree))
1349 {
1350 buffer.append(EOL);
1351 buffer.append("# ");
1352 if (first)
1353 {
1354 first = false;
1355 }
1356 else
1357 {
1358 buffer.append(' ');
1359 }
1360 buffer.append(s);
1361 }
1362 }
1363 }
1364
1365
1366
1367 /**
1368 * Retrieves a string representation of the provided value with all special
1369 * characters escaped with backslashes.
1370 *
1371 * @param valueBytes The byte array containing the value to encode.
1372 *
1373 * @return A string representation of the provided value with any special
1374 * characters
1375 */
1376 private static String getEscapedValue(final byte[] valueBytes)
1377 {
1378 final StringBuilder buffer = new StringBuilder(valueBytes.length * 2);
1379 for (int i=0; i < valueBytes.length; i++)
1380 {
1381 final byte b = valueBytes[i];
1382 switch (b)
1383 {
1384 case '\n':
1385 buffer.append("\\n");
1386 break;
1387 case '\r':
1388 buffer.append("\\r");
1389 break;
1390 case '\t':
1391 buffer.append("\\t");
1392 break;
1393 case ' ':
1394 if (i == 0)
1395 {
1396 buffer.append("\\ ");
1397 }
1398 else if ( i == (valueBytes.length - 1))
1399 {
1400 buffer.append("\\20");
1401 }
1402 else
1403 {
1404 buffer.append(' ');
1405 }
1406 break;
1407 case '<':
1408 if (i == 0)
1409 {
1410 buffer.append('\\');
1411 }
1412 buffer.append('<');
1413 break;
1414 case ':':
1415 if (i == 0)
1416 {
1417 buffer.append('\\');
1418 }
1419 buffer.append(':');
1420 break;
1421 default:
1422 if ((b >= '!') && (b <= '~'))
1423 {
1424 buffer.append((char) b);
1425 }
1426 else
1427 {
1428 buffer.append("\\");
1429 toHex(b, buffer);
1430 }
1431 break;
1432 }
1433 }
1434
1435 return buffer.toString();
1436 }
1437
1438
1439
1440 /**
1441 * If the provided exception is non-null, then it will be rethrown as an
1442 * unchecked exception or an IOException.
1443 *
1444 * @param t The exception to rethrow as an an unchecked exception or an
1445 * IOException or {@code null} if none.
1446 *
1447 * @throws IOException If t is a checked exception.
1448 */
1449 static void rethrow(final Throwable t)
1450 throws IOException
1451 {
1452 if (t == null)
1453 {
1454 return;
1455 }
1456
1457 if (t instanceof IOException)
1458 {
1459 throw (IOException) t;
1460 }
1461 else if (t instanceof RuntimeException)
1462 {
1463 throw (RuntimeException) t;
1464 }
1465 else if (t instanceof Error)
1466 {
1467 throw (Error) t;
1468 }
1469 else
1470 {
1471 throw createIOExceptionWithCause(null, t);
1472 }
1473 }
1474 }