001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 *
019 */
020 package org.apache.directory.shared.ldap.ldif;
021
022
023 import java.io.BufferedReader;
024 import java.io.Closeable;
025 import java.io.DataInputStream;
026 import java.io.File;
027 import java.io.FileInputStream;
028 import java.io.FileNotFoundException;
029 import java.io.FileReader;
030 import java.io.IOException;
031 import java.io.InputStream;
032 import java.io.InputStreamReader;
033 import java.io.Reader;
034 import java.io.StringReader;
035 import java.io.UnsupportedEncodingException;
036 import java.net.MalformedURLException;
037 import java.net.URL;
038 import java.nio.charset.Charset;
039 import java.util.ArrayList;
040 import java.util.Iterator;
041 import java.util.List;
042 import java.util.NoSuchElementException;
043
044 import org.apache.directory.shared.asn1.primitives.OID;
045 import org.apache.directory.shared.i18n.I18n;
046 import org.apache.directory.shared.ldap.entry.EntryAttribute;
047 import org.apache.directory.shared.ldap.entry.ModificationOperation;
048 import org.apache.directory.shared.ldap.entry.client.DefaultClientAttribute;
049 import org.apache.directory.shared.ldap.exception.LdapException;
050 import org.apache.directory.shared.ldap.exception.LdapInvalidDnException;
051 import org.apache.directory.shared.ldap.message.control.Control;
052 import org.apache.directory.shared.ldap.name.DN;
053 import org.apache.directory.shared.ldap.name.DnParser;
054 import org.apache.directory.shared.ldap.name.RDN;
055 import org.apache.directory.shared.ldap.util.Base64;
056 import org.apache.directory.shared.ldap.util.StringTools;
057 import org.slf4j.Logger;
058 import org.slf4j.LoggerFactory;
059
060
061 /**
062 * <pre>
063 * <ldif-file> ::= "version:" <fill> <number> <seps> <dn-spec> <sep>
064 * <ldif-content-change>
065 *
066 * <ldif-content-change> ::=
067 * <number> <oid> <options-e> <value-spec> <sep>
068 * <attrval-specs-e> <ldif-attrval-record-e> |
069 * <alpha> <chars-e> <options-e> <value-spec> <sep>
070 * <attrval-specs-e> <ldif-attrval-record-e> |
071 * "control:" <fill> <number> <oid> <spaces-e>
072 * <criticality> <value-spec-e> <sep> <controls-e>
073 * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> |
074 * "changetype:" <fill> <changerecord-type> <ldif-change-record-e>
075 *
076 * <ldif-attrval-record-e> ::= <seps> <dn-spec> <sep> <attributeType>
077 * <options-e> <value-spec> <sep> <attrval-specs-e>
078 * <ldif-attrval-record-e> | e
079 *
080 * <ldif-change-record-e> ::= <seps> <dn-spec> <sep> <controls-e>
081 * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> | e
082 *
083 * <dn-spec> ::= "dn:" <fill> <safe-string> | "dn::" <fill> <base64-string>
084 *
085 * <controls-e> ::= "control:" <fill> <number> <oid> <spaces-e> <criticality>
086 * <value-spec-e> <sep> <controls-e> | e
087 *
088 * <criticality> ::= "true" | "false" | e
089 *
090 * <oid> ::= '.' <number> <oid> | e
091 *
092 * <attrval-specs-e> ::= <number> <oid> <options-e> <value-spec>
093 * <sep> <attrval-specs-e> |
094 * <alpha> <chars-e> <options-e> <value-spec> <sep> <attrval-specs-e> | e
095 *
096 * <value-spec-e> ::= <value-spec> | e
097 *
098 * <value-spec> ::= ':' <fill> <safe-string-e> |
099 * "::" <fill> <base64-chars> |
100 * ":<" <fill> <url>
101 *
102 * <attributeType> ::= <number> <oid> | <alpha> <chars-e>
103 *
104 * <options-e> ::= ';' <char> <chars-e> <options-e> |e
105 *
106 * <chars-e> ::= <char> <chars-e> | e
107 *
108 * <changerecord-type> ::= "add" <sep> <attributeType>
109 * <options-e> <value-spec> <sep> <attrval-specs-e> |
110 * "delete" <sep> |
111 * "modify" <sep> <mod-type> <fill> <attributeType>
112 * <options-e> <sep> <attrval-specs-e> <sep> '-' <sep> <mod-specs-e> |
113 * "moddn" <sep> <newrdn> <sep> "deleteoldrdn:"
114 * <fill> <0-1> <sep> <newsuperior-e> <sep> |
115 * "modrdn" <sep> <newrdn> <sep> "deleteoldrdn:"
116 * <fill> <0-1> <sep> <newsuperior-e> <sep>
117 *
118 * <newrdn> ::= ':' <fill> <safe-string> | "::" <fill> <base64-chars>
119 *
120 * <newsuperior-e> ::= "newsuperior" <newrdn> | e
121 *
122 * <mod-specs-e> ::= <mod-type> <fill> <attributeType> <options-e>
123 * <sep> <attrval-specs-e> <sep> '-' <sep> <mod-specs-e> | e
124 *
125 * <mod-type> ::= "add:" | "delete:" | "replace:"
126 *
127 * <url> ::= <a Uniform Resource Locator, as defined in [6]>
128 *
129 *
130 *
131 * LEXICAL
132 * -------
133 *
134 * <fill> ::= ' ' <fill> | e
135 * <char> ::= <alpha> | <digit> | '-'
136 * <number> ::= <digit> <digits>
137 * <0-1> ::= '0' | '1'
138 * <digits> ::= <digit> <digits> | e
139 * <digit> ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
140 * <seps> ::= <sep> <seps-e>
141 * <seps-e> ::= <sep> <seps-e> | e
142 * <sep> ::= 0x0D 0x0A | 0x0A
143 * <spaces> ::= ' ' <spaces-e>
144 * <spaces-e> ::= ' ' <spaces-e> | e
145 * <safe-string-e> ::= <safe-string> | e
146 * <safe-string> ::= <safe-init-char> <safe-chars>
147 * <safe-init-char> ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x1F] | [0x21-0x39] | 0x3B | [0x3D-0x7F]
148 * <safe-chars> ::= <safe-char> <safe-chars> | e
149 * <safe-char> ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x7F]
150 * <base64-string> ::= <base64-char> <base64-chars>
151 * <base64-chars> ::= <base64-char> <base64-chars> | e
152 * <base64-char> ::= 0x2B | 0x2F | [0x30-0x39] | 0x3D | [0x41-9x5A] | [0x61-0x7A]
153 * <alpha> ::= [0x41-0x5A] | [0x61-0x7A]
154 *
155 * COMMENTS
156 * --------
157 * - The ldap-oid VN is not correct in the RFC-2849. It has been changed from 1*DIGIT 0*1("." 1*DIGIT) to
158 * DIGIT+ ("." DIGIT+)*
159 * - The mod-spec lacks a sep between *attrval-spec and "-".
160 * - The BASE64-UTF8-STRING should be BASE64-CHAR BASE64-STRING
161 * - The ValueSpec rule must accept multilines values. In this case, we have a LF followed by a
162 * single space before the continued value.
163 * </pre>
164 *
165 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
166 * @version $Rev$, $Date$
167 */
168 public class LdifReader implements Iterable<LdifEntry>, Closeable
169 {
170 /** A logger */
171 private static final Logger LOG = LoggerFactory.getLogger( LdifReader.class );
172
173 /**
174 * A private class to track the current position in a line
175 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
176 * @version $Rev$, $Date$
177 */
178 public class Position
179 {
180 /** The current position */
181 private int pos;
182
183
184 /**
185 * Creates a new instance of Position.
186 */
187 public Position()
188 {
189 pos = 0;
190 }
191
192
193 /**
194 * Increment the current position by one
195 *
196 */
197 public void inc()
198 {
199 pos++;
200 }
201
202
203 /**
204 * Increment the current position by the given value
205 *
206 * @param val The value to add to the current position
207 */
208 public void inc( int val )
209 {
210 pos += val;
211 }
212 }
213
214 /** A list of read lines */
215 protected List<String> lines;
216
217 /** The current position */
218 protected Position position;
219
220 /** The ldif file version default value */
221 protected static final int DEFAULT_VERSION = 1;
222
223 /** The ldif version */
224 protected int version;
225
226 /** Type of element read */
227 protected static final int LDIF_ENTRY = 0;
228
229 protected static final int CHANGE = 1;
230
231 protected static final int UNKNOWN = 2;
232
233 /** Size limit for file contained values */
234 protected long sizeLimit = SIZE_LIMIT_DEFAULT;
235
236 /** The default size limit : 1Mo */
237 protected static final long SIZE_LIMIT_DEFAULT = 1024000;
238
239 /** State values for the modify operation */
240 protected static final int MOD_SPEC = 0;
241
242 protected static final int ATTRVAL_SPEC = 1;
243
244 protected static final int ATTRVAL_SPEC_OR_SEP = 2;
245
246 /** Iterator prefetched entry */
247 protected LdifEntry prefetched;
248
249 /** The ldif Reader */
250 protected Reader reader;
251
252 /** A flag set if the ldif contains entries */
253 protected boolean containsEntries;
254
255 /** A flag set if the ldif contains changes */
256 protected boolean containsChanges;
257
258 /**
259 * An Exception to handle error message, has Iterator.next() can't throw
260 * exceptions
261 */
262 protected Exception error;
263
264
265 /**
266 * Constructors
267 */
268 public LdifReader()
269 {
270 lines = new ArrayList<String>();
271 position = new Position();
272 version = DEFAULT_VERSION;
273 }
274
275
276 private void init( BufferedReader reader ) throws LdapLdifException, LdapException
277 {
278 this.reader = reader;
279 lines = new ArrayList<String>();
280 position = new Position();
281 version = DEFAULT_VERSION;
282 containsChanges = false;
283 containsEntries = false;
284
285 // First get the version - if any -
286 version = parseVersion();
287 prefetched = parseEntry();
288 }
289
290
291 /**
292 * A constructor which takes a file name
293 *
294 * @param ldifFileName A file name containing ldif formated input
295 * @throws LdapLdifException
296 * If the file cannot be processed or if the format is incorrect
297 */
298 public LdifReader( String ldifFileName ) throws LdapLdifException
299 {
300 File file = new File( ldifFileName );
301
302 if ( !file.exists() )
303 {
304 LOG.error( I18n.err( I18n.ERR_12010, file.getAbsoluteFile() ) );
305 throw new LdapLdifException( I18n.err( I18n.ERR_12010, file.getAbsoluteFile() ) );
306 }
307
308 if ( !file.canRead() )
309 {
310 LOG.error( I18n.err( I18n.ERR_12011, file.getName() ) );
311 throw new LdapLdifException( I18n.err( I18n.ERR_12011, file.getName() ) );
312 }
313
314 try
315 {
316 init( new BufferedReader( new FileReader( file ) ) );
317 }
318 catch ( FileNotFoundException fnfe )
319 {
320 LOG.error( I18n.err( I18n.ERR_12010, file.getAbsoluteFile() ) );
321 throw new LdapLdifException( I18n.err( I18n.ERR_12010, file.getAbsoluteFile() ) );
322 }
323 catch ( LdapInvalidDnException lide )
324 {
325 throw new LdapLdifException( lide.getMessage() );
326 }
327 catch ( LdapException le )
328 {
329 throw new LdapLdifException( le.getMessage() );
330 }
331 }
332
333
334 /**
335 * A constructor which takes a Reader
336 *
337 * @param in
338 * A Reader containing ldif formated input
339 * @throws LdapLdifException
340 * If the file cannot be processed or if the format is incorrect
341 * @throws LdapException
342 */
343 public LdifReader( Reader in ) throws LdapLdifException, LdapException
344 {
345 init( new BufferedReader( in ) );
346 }
347
348
349 /**
350 * A constructor which takes an InputStream
351 *
352 * @param in
353 * An InputStream containing ldif formated input
354 * @throws LdapLdifException
355 * If the file cannot be processed or if the format is incorrect
356 * @throws LdapException
357 */
358 public LdifReader( InputStream in ) throws LdapLdifException, LdapException
359 {
360 init( new BufferedReader( new InputStreamReader( in ) ) );
361 }
362
363
364 /**
365 * A constructor which takes a File
366 *
367 * @param in
368 * A File containing ldif formated input
369 * @throws LdapLdifException
370 * If the file cannot be processed or if the format is incorrect
371 */
372 public LdifReader( File file ) throws LdapLdifException
373 {
374 if ( !file.exists() )
375 {
376 LOG.error( I18n.err( I18n.ERR_12010, file.getAbsoluteFile() ) );
377 throw new LdapLdifException( I18n.err( I18n.ERR_12010, file.getAbsoluteFile() ) );
378 }
379
380 if ( !file.canRead() )
381 {
382 LOG.error( I18n.err( I18n.ERR_12011, file.getName() ) );
383 throw new LdapLdifException( I18n.err( I18n.ERR_12011, file.getName() ) );
384 }
385
386 try
387 {
388 init( new BufferedReader( new FileReader( file ) ) );
389 }
390 catch ( FileNotFoundException fnfe )
391 {
392 LOG.error( I18n.err( I18n.ERR_12010, file.getAbsoluteFile() ) );
393 throw new LdapLdifException( I18n.err( I18n.ERR_12010, file.getAbsoluteFile() ) );
394 }
395 catch ( LdapInvalidDnException lide )
396 {
397 throw new LdapLdifException( lide.getMessage() );
398 }
399 catch ( LdapException le )
400 {
401 throw new LdapLdifException( le.getMessage() );
402 }
403 }
404
405
406 /**
407 * @return The ldif file version
408 */
409 public int getVersion()
410 {
411 return version;
412 }
413
414
415 /**
416 * @return The maximum size of a file which is used into an attribute value.
417 */
418 public long getSizeLimit()
419 {
420 return sizeLimit;
421 }
422
423
424 /**
425 * Set the maximum file size that can be accepted for an attribute value
426 *
427 * @param sizeLimit
428 * The size in bytes
429 */
430 public void setSizeLimit( long sizeLimit )
431 {
432 this.sizeLimit = sizeLimit;
433 }
434
435
436 // <fill> ::= ' ' <fill> | ���
437 private static void parseFill( char[] document, Position position )
438 {
439
440 while ( StringTools.isCharASCII( document, position.pos, ' ' ) )
441 {
442 position.inc();
443 }
444 }
445
446
447 /**
448 * Parse a number following the rules :
449 *
450 * <number> ::= <digit> <digits> <digits> ::= <digit> <digits> | e <digit>
451 * ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
452 *
453 * Check that the number is in the interval
454 *
455 * @param document The document containing the number to parse
456 * @param position The current position in the document
457 * @return a String representing the parsed number
458 */
459 private static String parseNumber( char[] document, Position position )
460 {
461 int initPos = position.pos;
462
463 while ( true )
464 {
465 if ( StringTools.isDigit( document, position.pos ) )
466 {
467 position.inc();
468 }
469 else
470 {
471 break;
472 }
473 }
474
475 if ( position.pos == initPos )
476 {
477 return null;
478 }
479 else
480 {
481 return new String( document, initPos, position.pos - initPos );
482 }
483 }
484
485
486 /**
487 * Parse the changeType
488 *
489 * @param line
490 * The line which contains the changeType
491 * @return The operation.
492 */
493 private ChangeType parseChangeType( String line )
494 {
495 ChangeType operation = ChangeType.Add;
496
497 String modOp = StringTools.trim( line.substring( "changetype:".length() + 1 ) );
498
499 if ( "add".equalsIgnoreCase( modOp ) )
500 {
501 operation = ChangeType.Add;
502 }
503 else if ( "delete".equalsIgnoreCase( modOp ) )
504 {
505 operation = ChangeType.Delete;
506 }
507 else if ( "modify".equalsIgnoreCase( modOp ) )
508 {
509 operation = ChangeType.Modify;
510 }
511 else if ( "moddn".equalsIgnoreCase( modOp ) )
512 {
513 operation = ChangeType.ModDn;
514 }
515 else if ( "modrdn".equalsIgnoreCase( modOp ) )
516 {
517 operation = ChangeType.ModRdn;
518 }
519
520 return operation;
521 }
522
523
524 /**
525 * Parse the DN of an entry
526 *
527 * @param line
528 * The line to parse
529 * @return A DN
530 * @throws LdapLdifException
531 * If the DN is invalid
532 */
533 private String parseDn( String line ) throws LdapLdifException
534 {
535 String dn = null;
536
537 String lowerLine = line.toLowerCase();
538
539 if ( lowerLine.startsWith( "dn:" ) || lowerLine.startsWith( "DN:" ) )
540 {
541 // Ok, we have a DN. Is it base 64 encoded ?
542 int length = line.length();
543
544 if ( length == 3 )
545 {
546 // The DN is empty : error
547 LOG.error( I18n.err( I18n.ERR_12012 ) );
548 throw new LdapLdifException( I18n.err( I18n.ERR_12013 ) );
549 }
550 else if ( line.charAt( 3 ) == ':' )
551 {
552 if ( length > 4 )
553 {
554 // This is a base 64 encoded DN.
555 String trimmedLine = line.substring( 4 ).trim();
556
557 try
558 {
559 dn = new String( Base64.decode( trimmedLine.toCharArray() ), "UTF-8" );
560 }
561 catch ( UnsupportedEncodingException uee )
562 {
563 // The DN is not base 64 encoded
564 LOG.error( I18n.err( I18n.ERR_12014 ) );
565 throw new LdapLdifException( I18n.err( I18n.ERR_12015 ) );
566 }
567 }
568 else
569 {
570 // The DN is empty : error
571 LOG.error( I18n.err( I18n.ERR_12012 ) );
572 throw new LdapLdifException( I18n.err( I18n.ERR_12013 ) );
573 }
574 }
575 else
576 {
577 dn = line.substring( 3 ).trim();
578 }
579 }
580 else
581 {
582 LOG.error( I18n.err( I18n.ERR_12016 ) );
583 throw new LdapLdifException( I18n.err( I18n.ERR_12013 ) );
584 }
585
586 // Check that the DN is valid. If not, an exception will be thrown
587 try
588 {
589 DnParser.parseInternal( dn, new ArrayList<RDN>() );
590 }
591 catch ( LdapInvalidDnException ine )
592 {
593 LOG.error( I18n.err( I18n.ERR_12017, dn ) );
594 throw new LdapLdifException( ine.getMessage() );
595 }
596
597 return dn;
598 }
599
600
601 /**
602 * Parse the value part.
603 *
604 * @param line
605 * The line which contains the value
606 * @param pos
607 * The starting position in the line
608 * @return A String or a byte[], depending of the kind of value we get
609 */
610 protected static Object parseSimpleValue( String line, int pos )
611 {
612 if ( line.length() > pos + 1 )
613 {
614 char c = line.charAt( pos + 1 );
615
616 if ( c == ':' )
617 {
618 String value = StringTools.trim( line.substring( pos + 2 ) );
619
620 return Base64.decode( value.toCharArray() );
621 }
622 else
623 {
624 return StringTools.trim( line.substring( pos + 1 ) );
625 }
626 }
627 else
628 {
629 return null;
630 }
631 }
632
633
634 /**
635 * Parse the value part.
636 *
637 * @param line The line which contains the value
638 * @param pos The starting position in the line
639 * @return A String or a byte[], depending of the kind of value we get
640 * @throws LdapLdifException
641 * If something went wrong
642 */
643 protected Object parseValue( String line, int pos ) throws LdapLdifException
644 {
645 if ( line.length() > pos + 1 )
646 {
647 char c = line.charAt( pos + 1 );
648
649 if ( c == ':' )
650 {
651 String value = StringTools.trim( line.substring( pos + 2 ) );
652
653 return Base64.decode( value.toCharArray() );
654 }
655 else if ( c == '<' )
656 {
657 String urlName = StringTools.trim( line.substring( pos + 2 ) );
658
659 try
660 {
661 URL url = new URL( urlName );
662
663 if ( "file".equals( url.getProtocol() ) )
664 {
665 String fileName = url.getFile();
666
667 File file = new File( fileName );
668
669 if ( !file.exists() )
670 {
671 LOG.error( I18n.err( I18n.ERR_12018, fileName ) );
672 throw new LdapLdifException( I18n.err( I18n.ERR_12019 ) );
673 }
674 else
675 {
676 long length = file.length();
677
678 if ( length > sizeLimit )
679 {
680 LOG.error( I18n.err( I18n.ERR_12020, fileName ) );
681 throw new LdapLdifException( I18n.err( I18n.ERR_12021 ) );
682 }
683 else
684 {
685 byte[] data = new byte[( int ) length];
686 DataInputStream inf = null;
687
688 try
689 {
690 inf = new DataInputStream( new FileInputStream( file ) );
691 inf.read( data );
692
693 return data;
694 }
695 catch ( FileNotFoundException fnfe )
696 {
697 // We can't reach this point, the file
698 // existence has already been
699 // checked
700 LOG.error( I18n.err( I18n.ERR_12018, fileName ) );
701 throw new LdapLdifException( I18n.err( I18n.ERR_12019 ) );
702 }
703 catch ( IOException ioe )
704 {
705 LOG.error( I18n.err( I18n.ERR_12022, fileName ) );
706 throw new LdapLdifException( I18n.err( I18n.ERR_12023 ) );
707 }
708 finally
709 {
710 try
711 {
712 inf.close();
713 }
714 catch ( IOException ioe )
715 {
716 LOG.error( I18n.err( I18n.ERR_12024, ioe.getMessage() ) );
717 // Just do nothing ...
718 }
719 }
720 }
721 }
722 }
723 else
724 {
725 LOG.error( I18n.err( I18n.ERR_12025 ) );
726 throw new LdapLdifException( I18n.err( I18n.ERR_12026 ) );
727 }
728 }
729 catch ( MalformedURLException mue )
730 {
731 LOG.error( I18n.err( I18n.ERR_12027, urlName ) );
732 throw new LdapLdifException( I18n.err( I18n.ERR_12028 ) );
733 }
734 }
735 else
736 {
737 return StringTools.trim( line.substring( pos + 1 ) );
738 }
739 }
740 else
741 {
742 return null;
743 }
744 }
745
746
747 /**
748 * Parse a control. The grammar is : <control> ::= "control:" <fill>
749 * <ldap-oid> <critical-e> <value-spec-e> <sep> <critical-e> ::= <spaces>
750 * <boolean> | e <boolean> ::= "true" | "false" <value-spec-e> ::=
751 * <value-spec> | e <value-spec> ::= ":" <fill> <SAFE-STRING-e> | "::"
752 * <fill> <BASE64-STRING> | ":<" <fill> <url>
753 *
754 * It can be read as : "control:" <fill> <ldap-oid> [ " "+ ( "true" |
755 * "false") ] [ ":" <fill> <SAFE-STRING-e> | "::" <fill> <BASE64-STRING> | ":<"
756 * <fill> <url> ]
757 *
758 * @param line The line containing the control
759 * @return A control
760 * @exception LdapLdifException If the control has no OID or if the OID is incorrect,
761 * of if the criticality is not set when it's mandatory.
762 */
763 private Control parseControl( String line ) throws LdapLdifException
764 {
765 String lowerLine = line.toLowerCase().trim();
766 char[] controlValue = line.trim().toCharArray();
767 int pos = 0;
768 int length = controlValue.length;
769
770 // Get the <ldap-oid>
771 if ( pos > length )
772 {
773 // No OID : error !
774 LOG.error( I18n.err( I18n.ERR_12029 ) );
775 throw new LdapLdifException( I18n.err( I18n.ERR_12030 ) );
776 }
777
778 int initPos = pos;
779
780 while ( StringTools.isCharASCII( controlValue, pos, '.' ) || StringTools.isDigit( controlValue, pos ) )
781 {
782 pos++;
783 }
784
785 if ( pos == initPos )
786 {
787 // Not a valid OID !
788 LOG.error( I18n.err( I18n.ERR_12029 ) );
789 throw new LdapLdifException( I18n.err( I18n.ERR_12030 ) );
790 }
791
792 // Create and check the OID
793 String oidString = lowerLine.substring( 0, pos );
794
795 if ( !OID.isOID( oidString ) )
796 {
797 LOG.error( I18n.err( I18n.ERR_12031, oidString ) );
798 throw new LdapLdifException( I18n.err( I18n.ERR_12032 ) );
799 }
800
801 LdifControl control = new LdifControl( oidString );
802
803 // Get the criticality, if any
804 // Skip the <fill>
805 while ( StringTools.isCharASCII( controlValue, pos, ' ' ) )
806 {
807 pos++;
808 }
809
810 // Check if we have a "true" or a "false"
811 int criticalPos = lowerLine.indexOf( ':' );
812
813 int criticalLength = 0;
814
815 if ( criticalPos == -1 )
816 {
817 criticalLength = length - pos;
818 }
819 else
820 {
821 criticalLength = criticalPos - pos;
822 }
823
824 if ( ( criticalLength == 4 ) && ( "true".equalsIgnoreCase( lowerLine.substring( pos, pos + 4 ) ) ) )
825 {
826 control.setCritical( true );
827 }
828 else if ( ( criticalLength == 5 ) && ( "false".equalsIgnoreCase( lowerLine.substring( pos, pos + 5 ) ) ) )
829 {
830 control.setCritical( false );
831 }
832 else if ( criticalLength != 0 )
833 {
834 // If we have a criticality, it should be either "true" or "false",
835 // nothing else
836 LOG.error( I18n.err( I18n.ERR_12033 ) );
837 throw new LdapLdifException( I18n.err( I18n.ERR_12034 ) );
838 }
839
840 if ( criticalPos > 0 )
841 {
842 // We have a value. It can be a normal value, a base64 encoded value
843 // or a file contained value
844 if ( StringTools.isCharASCII( controlValue, criticalPos + 1, ':' ) )
845 {
846 // Base 64 encoded value
847 byte[] value = Base64.decode( line.substring( criticalPos + 2 ).toCharArray() );
848 control.setValue( value );
849 }
850 else if ( StringTools.isCharASCII( controlValue, criticalPos + 1, '<' ) )
851 {
852 // File contained value
853 }
854 else
855 {
856 // Standard value
857 byte[] value = new byte[length - criticalPos - 1];
858
859 for ( int i = 0; i < length - criticalPos - 1; i++ )
860 {
861 value[i] = ( byte ) controlValue[i + criticalPos + 1];
862 }
863
864 control.setValue( value );
865 }
866 }
867
868 return control;
869 }
870
871
872 /**
873 * Parse an AttributeType/AttributeValue
874 *
875 * @param line The line to parse
876 * @return the parsed Attribute
877 */
878 public static EntryAttribute parseAttributeValue( String line )
879 {
880 int colonIndex = line.indexOf( ':' );
881
882 if ( colonIndex != -1 )
883 {
884 String attributeType = line.toLowerCase().substring( 0, colonIndex );
885 Object attributeValue = parseSimpleValue( line, colonIndex );
886
887 // Create an attribute
888 if ( attributeValue instanceof String )
889 {
890 return new DefaultClientAttribute( attributeType, (String)attributeValue );
891 }
892 else
893 {
894 return new DefaultClientAttribute( attributeType, (byte[])attributeValue );
895 }
896 }
897 else
898 {
899 return null;
900 }
901 }
902
903
904 /**
905 * Parse an AttributeType/AttributeValue
906 *
907 * @param entry The entry where to store the value
908 * @param line The line to parse
909 * @param lowerLine The same line, lowercased
910 * @throws LdapLdifException If anything goes wrong
911 */
912 public void parseAttributeValue( LdifEntry entry, String line, String lowerLine ) throws LdapLdifException, LdapException
913 {
914 int colonIndex = line.indexOf( ':' );
915
916 String attributeType = lowerLine.substring( 0, colonIndex );
917
918 // We should *not* have a DN twice
919 if ( attributeType.equals( "dn" ) )
920 {
921 LOG.error( I18n.err( I18n.ERR_12002 ) );
922 throw new LdapLdifException( I18n.err( I18n.ERR_12003 ) );
923 }
924
925 Object attributeValue = parseValue( line, colonIndex );
926
927 // Update the entry
928 entry.addAttribute( attributeType, attributeValue );
929 }
930
931
932 /**
933 * Parse a ModRDN operation
934 *
935 * @param entry
936 * The entry to update
937 * @param iter
938 * The lines iterator
939 * @throws LdapLdifException
940 * If anything goes wrong
941 */
942 private void parseModRdn( LdifEntry entry, Iterator<String> iter ) throws LdapLdifException
943 {
944 // We must have two lines : one starting with "newrdn:" or "newrdn::",
945 // and the second starting with "deleteoldrdn:"
946 if ( iter.hasNext() )
947 {
948 String line = iter.next();
949 String lowerLine = line.toLowerCase();
950
951 if ( lowerLine.startsWith( "newrdn::" ) || lowerLine.startsWith( "newrdn:" ) )
952 {
953 int colonIndex = line.indexOf( ':' );
954 Object attributeValue = parseValue( line, colonIndex );
955 entry.setNewRdn( attributeValue instanceof String ? ( String ) attributeValue : StringTools
956 .utf8ToString( ( byte[] ) attributeValue ) );
957 }
958 else
959 {
960 LOG.error( I18n.err( I18n.ERR_12035 ) );
961 throw new LdapLdifException( I18n.err( I18n.ERR_12036 ) );
962 }
963
964 }
965 else
966 {
967 LOG.error( I18n.err( I18n.ERR_12035 ) );
968 throw new LdapLdifException( I18n.err( I18n.ERR_12037 ) );
969 }
970
971 if ( iter.hasNext() )
972 {
973 String line = iter.next();
974 String lowerLine = line.toLowerCase();
975
976 if ( lowerLine.startsWith( "deleteoldrdn:" ) )
977 {
978 int colonIndex = line.indexOf( ':' );
979 Object attributeValue = parseValue( line, colonIndex );
980 entry.setDeleteOldRdn( "1".equals( attributeValue ) );
981 }
982 else
983 {
984 LOG.error( I18n.err( I18n.ERR_12038 ) );
985 throw new LdapLdifException( I18n.err( I18n.ERR_12039 ) );
986 }
987 }
988 else
989 {
990 LOG.error( I18n.err( I18n.ERR_12038 ) );
991 throw new LdapLdifException( I18n.err( I18n.ERR_12039 ) );
992 }
993
994 return;
995 }
996
997
998 /**
999 * Parse a modify change type.
1000 *
1001 * The grammar is : <changerecord> ::= "changetype:" FILL "modify" SEP
1002 * <mod-spec> <mod-specs-e> <mod-spec> ::= "add:" <mod-val> | "delete:"
1003 * <mod-val-del> | "replace:" <mod-val> <mod-specs-e> ::= <mod-spec>
1004 * <mod-specs-e> | e <mod-val> ::= FILL ATTRIBUTE-DESCRIPTION SEP
1005 * ATTRVAL-SPEC <attrval-specs-e> "-" SEP <mod-val-del> ::= FILL
1006 * ATTRIBUTE-DESCRIPTION SEP <attrval-specs-e> "-" SEP <attrval-specs-e> ::=
1007 * ATTRVAL-SPEC <attrval-specs> | e *
1008 *
1009 * @param entry The entry to feed
1010 * @param iter The lines
1011 * @exception LdapLdifException If the modify operation is invalid
1012 */
1013 private void parseModify( LdifEntry entry, Iterator<String> iter ) throws LdapLdifException
1014 {
1015 int state = MOD_SPEC;
1016 String modified = null;
1017 ModificationOperation modificationType = ModificationOperation.ADD_ATTRIBUTE;
1018 EntryAttribute attribute = null;
1019
1020 // The following flag is used to deal with empty modifications
1021 boolean isEmptyValue = true;
1022
1023 while ( iter.hasNext() )
1024 {
1025 String line = iter.next();
1026 String lowerLine = line.toLowerCase();
1027
1028 if ( lowerLine.startsWith( "-" ) )
1029 {
1030 if ( state != ATTRVAL_SPEC_OR_SEP )
1031 {
1032 LOG.error( I18n.err( I18n.ERR_12040 ) );
1033 throw new LdapLdifException( I18n.err( I18n.ERR_12041 ) );
1034 }
1035 else
1036 {
1037 if ( isEmptyValue )
1038 {
1039 // Update the entry
1040 entry.addModificationItem( modificationType, modified, null );
1041 }
1042 else
1043 {
1044 // Update the entry with the attribute
1045 entry.addModificationItem( modificationType, attribute );
1046 }
1047
1048 state = MOD_SPEC;
1049 isEmptyValue = true;
1050 continue;
1051 }
1052 }
1053 else if ( lowerLine.startsWith( "add:" ) )
1054 {
1055 if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) )
1056 {
1057 LOG.error( I18n.err( I18n.ERR_12042 ) );
1058 throw new LdapLdifException( I18n.err( I18n.ERR_12043 ) );
1059 }
1060
1061 modified = StringTools.trim( line.substring( "add:".length() ) );
1062 modificationType = ModificationOperation.ADD_ATTRIBUTE;
1063 attribute = new DefaultClientAttribute( modified );
1064
1065 state = ATTRVAL_SPEC;
1066 }
1067 else if ( lowerLine.startsWith( "delete:" ) )
1068 {
1069 if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) )
1070 {
1071 LOG.error( I18n.err( I18n.ERR_12042 ) );
1072 throw new LdapLdifException( I18n.err( I18n.ERR_12043 ) );
1073 }
1074
1075 modified = StringTools.trim( line.substring( "delete:".length() ) );
1076 modificationType = ModificationOperation.REMOVE_ATTRIBUTE;
1077 attribute = new DefaultClientAttribute( modified );
1078
1079 state = ATTRVAL_SPEC_OR_SEP;
1080 }
1081 else if ( lowerLine.startsWith( "replace:" ) )
1082 {
1083 if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) )
1084 {
1085 LOG.error( I18n.err( I18n.ERR_12042 ) );
1086 throw new LdapLdifException( I18n.err( I18n.ERR_12043 ) );
1087 }
1088
1089 modified = StringTools.trim( line.substring( "replace:".length() ) );
1090 modificationType = ModificationOperation.REPLACE_ATTRIBUTE;
1091 attribute = new DefaultClientAttribute( modified );
1092
1093 state = ATTRVAL_SPEC_OR_SEP;
1094 }
1095 else
1096 {
1097 if ( ( state != ATTRVAL_SPEC ) && ( state != ATTRVAL_SPEC_OR_SEP ) )
1098 {
1099 LOG.error( I18n.err( I18n.ERR_12040 ) );
1100 throw new LdapLdifException( I18n.err( I18n.ERR_12043 ) );
1101 }
1102
1103 // A standard AttributeType/AttributeValue pair
1104 int colonIndex = line.indexOf( ':' );
1105
1106 String attributeType = line.substring( 0, colonIndex );
1107
1108 if ( !attributeType.equalsIgnoreCase( modified ) )
1109 {
1110 LOG.error( I18n.err( I18n.ERR_12044 ) );
1111 throw new LdapLdifException( I18n.err( I18n.ERR_12045 ) );
1112 }
1113
1114 // We should *not* have a DN twice
1115 if ( attributeType.equalsIgnoreCase( "dn" ) )
1116 {
1117 LOG.error( I18n.err( I18n.ERR_12002 ) );
1118 throw new LdapLdifException( I18n.err( I18n.ERR_12003 ) );
1119 }
1120
1121 Object attributeValue = parseValue( line, colonIndex );
1122
1123 if ( attributeValue instanceof String )
1124 {
1125 attribute.add( ( String ) attributeValue );
1126 }
1127 else
1128 {
1129 attribute.add( ( byte[] ) attributeValue );
1130 }
1131
1132 isEmptyValue = false;
1133
1134 state = ATTRVAL_SPEC_OR_SEP;
1135 }
1136 }
1137 }
1138
1139
1140 /**
1141 * Parse a change operation. We have to handle different cases depending on
1142 * the operation. 1) Delete : there should *not* be any line after the
1143 * "changetype: delete" 2) Add : we must have a list of AttributeType :
1144 * AttributeValue elements 3) ModDN : we must have two following lines: a
1145 * "newrdn:" and a "deleteoldrdn:" 4) ModRDN : the very same, but a
1146 * "newsuperior:" line is expected 5) Modify :
1147 *
1148 * The grammar is : <changerecord> ::= "changetype:" FILL "add" SEP
1149 * <attrval-spec> <attrval-specs-e> | "changetype:" FILL "delete" |
1150 * "changetype:" FILL "modrdn" SEP <newrdn> SEP <deleteoldrdn> SEP | // To
1151 * be checked "changetype:" FILL "moddn" SEP <newrdn> SEP <deleteoldrdn> SEP
1152 * <newsuperior> SEP | "changetype:" FILL "modify" SEP <mod-spec>
1153 * <mod-specs-e> <newrdn> ::= "newrdn:" FILL RDN | "newrdn::" FILL
1154 * BASE64-RDN <deleteoldrdn> ::= "deleteoldrdn:" FILL "0" | "deleteoldrdn:"
1155 * FILL "1" <newsuperior> ::= "newsuperior:" FILL DN | "newsuperior::" FILL
1156 * BASE64-DN <mod-specs-e> ::= <mod-spec> <mod-specs-e> | e <mod-spec> ::=
1157 * "add:" <mod-val> | "delete:" <mod-val> | "replace:" <mod-val> <mod-val>
1158 * ::= FILL ATTRIBUTE-DESCRIPTION SEP ATTRVAL-SPEC <attrval-specs-e> "-" SEP
1159 * <attrval-specs-e> ::= ATTRVAL-SPEC <attrval-specs> | e
1160 *
1161 * @param entry The entry to feed
1162 * @param iter The lines iterator
1163 * @param operation The change operation (add, modify, delete, moddn or modrdn)
1164 * @exception LdapLdifException If the change operation is invalid
1165 */
1166 private void parseChange( LdifEntry entry, Iterator<String> iter, ChangeType operation ) throws LdapLdifException, LdapException
1167 {
1168 // The changetype and operation has already been parsed.
1169 entry.setChangeType( operation );
1170
1171 switch ( operation.getChangeType() )
1172 {
1173 case ChangeType.DELETE_ORDINAL:
1174 // The change type will tell that it's a delete operation,
1175 // the dn is used as a key.
1176 return;
1177
1178 case ChangeType.ADD_ORDINAL:
1179 // We will iterate through all attribute/value pairs
1180 while ( iter.hasNext() )
1181 {
1182 String line = iter.next();
1183 String lowerLine = line.toLowerCase();
1184 parseAttributeValue( entry, line, lowerLine );
1185 }
1186
1187 return;
1188
1189 case ChangeType.MODIFY_ORDINAL:
1190 parseModify( entry, iter );
1191 return;
1192
1193 case ChangeType.MODRDN_ORDINAL:// They are supposed to have the same syntax ???
1194 case ChangeType.MODDN_ORDINAL:
1195 // First, parse the modrdn part
1196 parseModRdn( entry, iter );
1197
1198 // The next line should be the new superior
1199 if ( iter.hasNext() )
1200 {
1201 String line = iter.next();
1202 String lowerLine = line.toLowerCase();
1203
1204 if ( lowerLine.startsWith( "newsuperior:" ) )
1205 {
1206 int colonIndex = line.indexOf( ':' );
1207 Object attributeValue = parseValue( line, colonIndex );
1208 entry.setNewSuperior( attributeValue instanceof String ? ( String ) attributeValue
1209 : StringTools.utf8ToString( ( byte[] ) attributeValue ) );
1210 }
1211 else
1212 {
1213 if ( operation == ChangeType.ModDn )
1214 {
1215 LOG.error( I18n.err( I18n.ERR_12046 ) );
1216 throw new LdapLdifException( I18n.err( I18n.ERR_12047 ) );
1217 }
1218 }
1219 }
1220 else
1221 {
1222 if ( operation == ChangeType.ModDn )
1223 {
1224 LOG.error( I18n.err( I18n.ERR_12046 ) );
1225 throw new LdapLdifException( I18n.err( I18n.ERR_12047 ) );
1226 }
1227 }
1228
1229 return;
1230
1231 default:
1232 // This is an error
1233 LOG.error( I18n.err( I18n.ERR_12048 ) );
1234 throw new LdapLdifException( I18n.err( I18n.ERR_12049 ) );
1235 }
1236 }
1237
1238
1239 /**
1240 * Parse a ldif file. The following rules are processed :
1241 *
1242 * <ldif-file> ::= <ldif-attrval-record> <ldif-attrval-records> |
1243 * <ldif-change-record> <ldif-change-records> <ldif-attrval-record> ::=
1244 * <dn-spec> <sep> <attrval-spec> <attrval-specs> <ldif-change-record> ::=
1245 * <dn-spec> <sep> <controls-e> <changerecord> <dn-spec> ::= "dn:" <fill>
1246 * <distinguishedName> | "dn::" <fill> <base64-distinguishedName>
1247 * <changerecord> ::= "changetype:" <fill> <change-op>
1248 *
1249 * @return the parsed ldifEntry
1250 * @exception LdapLdifException If the ldif file does not contain a valid entry
1251 * @throws LdapException
1252 */
1253 private LdifEntry parseEntry() throws LdapLdifException, LdapException
1254 {
1255 if ( ( lines == null ) || ( lines.size() == 0 ) )
1256 {
1257 LOG.debug( "The entry is empty : end of ldif file" );
1258 return null;
1259 }
1260
1261 // The entry must start with a dn: or a dn::
1262 String line = lines.get( 0 );
1263
1264 String name = parseDn( line );
1265
1266 DN dn = new DN( name );
1267
1268 // Ok, we have found a DN
1269 LdifEntry entry = new LdifEntry();
1270 entry.setDn( dn );
1271
1272 // We remove this dn from the lines
1273 lines.remove( 0 );
1274
1275 // Now, let's iterate through the other lines
1276 Iterator<String> iter = lines.iterator();
1277
1278 // This flag is used to distinguish between an entry and a change
1279 int type = UNKNOWN;
1280
1281 // The following boolean is used to check that a control is *not*
1282 // found elswhere than just after the dn
1283 boolean controlSeen = false;
1284
1285 // We use this boolean to check that we do not have AttributeValues
1286 // after a change operation
1287 boolean changeTypeSeen = false;
1288
1289 ChangeType operation = ChangeType.Add;
1290 String lowerLine = null;
1291 Control control = null;
1292
1293 while ( iter.hasNext() )
1294 {
1295 // Each line could start either with an OID, an attribute type, with
1296 // "control:" or with "changetype:"
1297 line = iter.next();
1298 lowerLine = line.toLowerCase();
1299
1300 // We have three cases :
1301 // 1) The first line after the DN is a "control:"
1302 // 2) The first line after the DN is a "changeType:"
1303 // 3) The first line after the DN is anything else
1304 if ( lowerLine.startsWith( "control:" ) )
1305 {
1306 if ( containsEntries )
1307 {
1308 LOG.error( I18n.err( I18n.ERR_12004 ) );
1309 throw new LdapLdifException( I18n.err( I18n.ERR_12005 ) );
1310 }
1311
1312 containsChanges = true;
1313
1314 if ( controlSeen )
1315 {
1316 LOG.error( I18n.err( I18n.ERR_12050 ) );
1317 throw new LdapLdifException( I18n.err( I18n.ERR_12051 ) );
1318 }
1319
1320 // Parse the control
1321 control = parseControl( line.substring( "control:".length() ) );
1322 entry.setControl( control );
1323 }
1324 else if ( lowerLine.startsWith( "changetype:" ) )
1325 {
1326 if ( containsEntries )
1327 {
1328 LOG.error( I18n.err( I18n.ERR_12004 ) );
1329 throw new LdapLdifException( I18n.err( I18n.ERR_12005 ) );
1330 }
1331
1332 containsChanges = true;
1333
1334 if ( changeTypeSeen )
1335 {
1336 LOG.error( I18n.err( I18n.ERR_12052 ) );
1337 throw new LdapLdifException( I18n.err( I18n.ERR_12053 ) );
1338 }
1339
1340 // A change request
1341 type = CHANGE;
1342 controlSeen = true;
1343
1344 operation = parseChangeType( line );
1345
1346 // Parse the change operation in a separate function
1347 parseChange( entry, iter, operation );
1348 changeTypeSeen = true;
1349 }
1350 else if ( line.indexOf( ':' ) > 0 )
1351 {
1352 if ( containsChanges )
1353 {
1354 LOG.error( I18n.err( I18n.ERR_12004 ) );
1355 throw new LdapLdifException( I18n.err( I18n.ERR_12005 ) );
1356 }
1357
1358 containsEntries = true;
1359
1360 if ( controlSeen || changeTypeSeen )
1361 {
1362 LOG.error( I18n.err( I18n.ERR_12054 ) );
1363 throw new LdapLdifException( I18n.err( I18n.ERR_12055 ) );
1364 }
1365
1366 parseAttributeValue( entry, line, lowerLine );
1367 type = LDIF_ENTRY;
1368 }
1369 else
1370 {
1371 // Invalid attribute Value
1372 LOG.error( I18n.err( I18n.ERR_12056 ) );
1373 throw new LdapLdifException( I18n.err( I18n.ERR_12057 ) );
1374 }
1375 }
1376
1377 if ( type == LDIF_ENTRY )
1378 {
1379 LOG.debug( "Read an entry : {}", entry );
1380 }
1381 else if ( type == CHANGE )
1382 {
1383 entry.setChangeType( operation );
1384 LOG.debug( "Read a modification : {}", entry );
1385 }
1386 else
1387 {
1388 LOG.error( I18n.err( I18n.ERR_12058 ) );
1389 throw new LdapLdifException( I18n.err( I18n.ERR_12059 ) );
1390 }
1391
1392 return entry;
1393 }
1394
1395
1396 /**
1397 * Parse the version from the ldif input.
1398 *
1399 * @return A number representing the version (default to 1)
1400 * @throws LdapLdifException
1401 * If the version is incorrect
1402 * @throws LdapLdifException
1403 * If the input is incorrect
1404 */
1405 private int parseVersion() throws LdapLdifException
1406 {
1407 int ver = DEFAULT_VERSION;
1408
1409 // First, read a list of lines
1410 readLines();
1411
1412 if ( lines.size() == 0 )
1413 {
1414 LOG.warn( "The ldif file is empty" );
1415 return ver;
1416 }
1417
1418 // get the first line
1419 String line = lines.get( 0 );
1420
1421 // <ldif-file> ::= "version:" <fill> <number>
1422 char[] document = line.toCharArray();
1423 String versionNumber = null;
1424
1425 if ( line.startsWith( "version:" ) )
1426 {
1427 position.inc( "version:".length() );
1428 parseFill( document, position );
1429
1430 // Version number. Must be '1' in this version
1431 versionNumber = parseNumber( document, position );
1432
1433 // We should not have any other chars after the number
1434 if ( position.pos != document.length )
1435 {
1436 LOG.error( I18n.err( I18n.ERR_12060 ) );
1437 throw new LdapLdifException( I18n.err( I18n.ERR_12061 ) );
1438 }
1439
1440 try
1441 {
1442 ver = Integer.parseInt( versionNumber );
1443 }
1444 catch ( NumberFormatException nfe )
1445 {
1446 LOG.error( I18n.err( I18n.ERR_12060 ) );
1447 throw new LdapLdifException( I18n.err( I18n.ERR_12061 ) );
1448 }
1449
1450 LOG.debug( "Ldif version : {}", versionNumber );
1451
1452 // We have found the version, just discard the line from the list
1453 lines.remove( 0 );
1454
1455 // and read the next lines if the current buffer is empty
1456 if ( lines.size() == 0 )
1457 {
1458 readLines();
1459 }
1460 }
1461 else
1462 {
1463 LOG.warn( "No version information : assuming version: 1" );
1464 }
1465
1466 return ver;
1467 }
1468
1469
1470 /**
1471 * Reads an entry in a ldif buffer, and returns the resulting lines, without
1472 * comments, and unfolded.
1473 *
1474 * The lines represent *one* entry.
1475 *
1476 * @throws LdapLdifException If something went wrong
1477 */
1478 protected void readLines() throws LdapLdifException
1479 {
1480 String line = null;
1481 boolean insideComment = true;
1482 boolean isFirstLine = true;
1483
1484 lines.clear();
1485 StringBuffer sb = new StringBuffer();
1486
1487 try
1488 {
1489 while ( ( line = ( ( BufferedReader ) reader ).readLine() ) != null )
1490 {
1491 if ( line.length() == 0 )
1492 {
1493 if ( isFirstLine )
1494 {
1495 continue;
1496 }
1497 else
1498 {
1499 // The line is empty, we have read an entry
1500 insideComment = false;
1501 break;
1502 }
1503 }
1504
1505 // We will read the first line which is not a comment
1506 switch ( line.charAt( 0 ) )
1507 {
1508 case '#':
1509 insideComment = true;
1510 break;
1511
1512 case ' ':
1513 isFirstLine = false;
1514
1515 if ( insideComment )
1516 {
1517 continue;
1518 }
1519 else if ( sb.length() == 0 )
1520 {
1521 LOG.error( I18n.err( I18n.ERR_12062 ) );
1522 throw new LdapLdifException( I18n.err( I18n.ERR_12061 ) );
1523 }
1524 else
1525 {
1526 sb.append( line.substring( 1 ) );
1527 }
1528
1529 insideComment = false;
1530 break;
1531
1532 default:
1533 isFirstLine = false;
1534
1535 // We have found a new entry
1536 // First, stores the previous one if any.
1537 if ( sb.length() != 0 )
1538 {
1539 lines.add( sb.toString() );
1540 }
1541
1542 sb = new StringBuffer( line );
1543 insideComment = false;
1544 break;
1545 }
1546 }
1547 }
1548 catch ( IOException ioe )
1549 {
1550 throw new LdapLdifException( I18n.err( I18n.ERR_12063 ) );
1551 }
1552
1553 // Stores the current line if necessary.
1554 if ( sb.length() != 0 )
1555 {
1556 lines.add( sb.toString() );
1557 }
1558
1559 return;
1560 }
1561
1562
1563 /**
1564 * Parse a ldif file (using the default encoding).
1565 *
1566 * @param fileName
1567 * The ldif file
1568 * @return A list of entries
1569 * @throws LdapLdifException
1570 * If the parsing fails
1571 */
1572 public List<LdifEntry> parseLdifFile( String fileName ) throws LdapLdifException
1573 {
1574 return parseLdifFile( fileName, Charset.forName( StringTools.getDefaultCharsetName() ).toString() );
1575 }
1576
1577
1578 /**
1579 * Parse a ldif file, decoding it using the given charset encoding
1580 *
1581 * @param fileName
1582 * The ldif file
1583 * @param encoding
1584 * The charset encoding to use
1585 * @return A list of entries
1586 * @throws LdapLdifException
1587 * If the parsing fails
1588 */
1589 public List<LdifEntry> parseLdifFile( String fileName, String encoding ) throws LdapLdifException
1590 {
1591 if ( StringTools.isEmpty( fileName ) )
1592 {
1593 LOG.error( I18n.err( I18n.ERR_12064 ) );
1594 throw new LdapLdifException( I18n.err( I18n.ERR_12065 ) );
1595 }
1596
1597 File file = new File( fileName );
1598
1599 if ( !file.exists() )
1600 {
1601 LOG.error( I18n.err( I18n.ERR_12066, fileName ) );
1602 throw new LdapLdifException( I18n.err( I18n.ERR_12067, fileName ) );
1603 }
1604
1605 BufferedReader reader = null;
1606
1607 // Open the file and then get a channel from the stream
1608 try
1609 {
1610 reader = new BufferedReader(
1611 new InputStreamReader(
1612 new FileInputStream( file ), Charset.forName( encoding ) ) );
1613
1614 return parseLdif( reader );
1615 }
1616 catch ( FileNotFoundException fnfe )
1617 {
1618 LOG.error( I18n.err( I18n.ERR_12068, fileName ) );
1619 throw new LdapLdifException( I18n.err( I18n.ERR_12067, fileName ) );
1620 }
1621 catch ( LdapException le )
1622 {
1623 le.printStackTrace();
1624 throw new LdapLdifException( le.getMessage() );
1625 }
1626 finally
1627 {
1628 // close the reader
1629 try
1630 {
1631 if ( reader != null )
1632 {
1633 reader.close();
1634 }
1635 }
1636 catch ( IOException ioe )
1637 {
1638 // Nothing to do
1639 }
1640 }
1641 }
1642
1643
1644 /**
1645 * A method which parses a ldif string and returns a list of entries.
1646 *
1647 * @param ldif
1648 * The ldif string
1649 * @return A list of entries, or an empty List
1650 * @throws LdapLdifException
1651 * If something went wrong
1652 */
1653 public List<LdifEntry> parseLdif( String ldif ) throws LdapLdifException
1654 {
1655 LOG.debug( "Starts parsing ldif buffer" );
1656
1657 if ( StringTools.isEmpty( ldif ) )
1658 {
1659 return new ArrayList<LdifEntry>();
1660 }
1661
1662 BufferedReader reader = new BufferedReader(
1663 new StringReader( ldif ) );
1664
1665 try
1666 {
1667 List<LdifEntry> entries = parseLdif( reader );
1668
1669 if ( LOG.isDebugEnabled() )
1670 {
1671 LOG.debug( "Parsed {} entries.", ( entries == null ? Integer.valueOf( 0 ) : Integer.valueOf( entries
1672 .size() ) ) );
1673 }
1674
1675 return entries;
1676 }
1677 catch ( LdapLdifException ne )
1678 {
1679 LOG.error( I18n.err( I18n.ERR_12069, ne.getLocalizedMessage() ) );
1680 throw new LdapLdifException( I18n.err( I18n.ERR_12070 ) );
1681 }
1682 catch ( LdapException le )
1683 {
1684 throw new LdapLdifException( le.getMessage() );
1685 }
1686 finally
1687 {
1688 // Close the reader
1689 try
1690 {
1691 if ( reader != null )
1692 {
1693 reader.close();
1694 }
1695 }
1696 catch ( IOException ioe )
1697 {
1698 // Nothing to do
1699 }
1700
1701 }
1702 }
1703
1704
1705 // ------------------------------------------------------------------------
1706 // Iterator Methods
1707 // ------------------------------------------------------------------------
1708
1709 /**
1710 * Gets the next LDIF on the channel.
1711 *
1712 * @return the next LDIF as a String.
1713 * @exception NoSuchElementException If we can't read the next entry
1714 */
1715 private LdifEntry nextInternal()
1716 {
1717 try
1718 {
1719 LOG.debug( "next(): -- called" );
1720
1721 LdifEntry entry = prefetched;
1722 readLines();
1723
1724 try
1725 {
1726 prefetched = parseEntry();
1727 }
1728 catch ( LdapLdifException ne )
1729 {
1730 error = ne;
1731 throw new NoSuchElementException( ne.getMessage() );
1732 }
1733 catch ( LdapException le )
1734 {
1735 throw new NoSuchElementException( le.getMessage() );
1736 }
1737
1738 LOG.debug( "next(): -- returning ldif {}\n", entry );
1739
1740 return entry;
1741 }
1742 catch ( LdapLdifException ne )
1743 {
1744 LOG.error( I18n.err( I18n.ERR_12071 ) );
1745 error = ne;
1746 return null;
1747 }
1748 }
1749
1750
1751 /**
1752 * Gets the next LDIF on the channel.
1753 *
1754 * @return the next LDIF as a String.
1755 * @exception NoSuchElementException If we can't read the next entry
1756 */
1757 public LdifEntry next()
1758 {
1759 return nextInternal();
1760 }
1761
1762
1763 /**
1764 * Tests to see if another LDIF is on the input channel.
1765 *
1766 * @return true if another LDIF is available false otherwise.
1767 */
1768 private boolean hasNextInternal()
1769 {
1770 return null != prefetched;
1771 }
1772
1773
1774 /**
1775 * Tests to see if another LDIF is on the input channel.
1776 *
1777 * @return true if another LDIF is available false otherwise.
1778 */
1779 public boolean hasNext()
1780 {
1781 LOG.debug( "hasNext(): -- returning {}", ( prefetched != null ) ? Boolean.TRUE : Boolean.FALSE );
1782
1783 return hasNextInternal();
1784 }
1785
1786
1787 /**
1788 * Always throws UnsupportedOperationException!
1789 *
1790 * @see java.util.Iterator#remove()
1791 */
1792 private void removeInternal()
1793 {
1794 throw new UnsupportedOperationException();
1795 }
1796
1797
1798 /**
1799 * Always throws UnsupportedOperationException!
1800 *
1801 * @see java.util.Iterator#remove()
1802 */
1803 public void remove()
1804 {
1805 removeInternal();
1806 }
1807
1808
1809 /**
1810 * @return An iterator on the file
1811 */
1812 public Iterator<LdifEntry> iterator()
1813 {
1814 return new Iterator<LdifEntry>()
1815 {
1816 public boolean hasNext()
1817 {
1818 return hasNextInternal();
1819 }
1820
1821
1822 public LdifEntry next()
1823 {
1824 return nextInternal();
1825 }
1826
1827
1828 public void remove()
1829 {
1830 throw new UnsupportedOperationException();
1831 }
1832 };
1833 }
1834
1835
1836 /**
1837 * @return True if an error occured during parsing
1838 */
1839 public boolean hasError()
1840 {
1841 return error != null;
1842 }
1843
1844
1845 /**
1846 * @return The exception that occurs during an entry parsing
1847 */
1848 public Exception getError()
1849 {
1850 return error;
1851 }
1852
1853
1854 /**
1855 * The main entry point of the LdifParser. It reads a buffer and returns a
1856 * List of entries.
1857 *
1858 * @param inf
1859 * The buffer being processed
1860 * @return A list of entries
1861 * @throws LdapLdifException
1862 * If something went wrong
1863 * @throws LdapException
1864 */
1865 public List<LdifEntry> parseLdif( BufferedReader reader ) throws LdapLdifException, LdapException
1866 {
1867 // Create a list that will contain the read entries
1868 List<LdifEntry> entries = new ArrayList<LdifEntry>();
1869
1870 this.reader = reader;
1871
1872 // First get the version - if any -
1873 version = parseVersion();
1874 prefetched = parseEntry();
1875
1876 // When done, get the entries one by one.
1877 try
1878 {
1879 for ( LdifEntry entry : this )
1880 {
1881 if ( entry != null )
1882 {
1883 entries.add( entry );
1884 }
1885 }
1886 }
1887 catch ( NoSuchElementException nsee )
1888 {
1889 throw new LdapLdifException( I18n.err( I18n.ERR_12072, error.getLocalizedMessage() ) );
1890 }
1891
1892 return entries;
1893 }
1894
1895
1896 /**
1897 * @return True if the ldif file contains entries, fals if it contains
1898 * changes
1899 */
1900 public boolean containsEntries()
1901 {
1902 return containsEntries;
1903 }
1904
1905
1906 /**
1907 * {@inheritDoc}
1908 */
1909 public void close() throws IOException
1910 {
1911 if ( reader != null )
1912 {
1913 position = new Position();
1914 reader.close();
1915 containsEntries = false;
1916 containsChanges = false;
1917 }
1918 }
1919 }
1920
1921