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