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