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