You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by el...@apache.org on 2006/05/25 20:41:05 UTC
svn commit: r409424 [2/3] - in /directory/trunks:
apacheds/core-unit/src/main/java/org/apache/directory/server/core/unit/
apacheds/core/src/main/java/org/apache/directory/server/core/
apacheds/core/src/main/java/org/apache/directory/server/core/configu...
Modified: directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/ldif/LdifReader.java
URL: http://svn.apache.org/viewvc/directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/ldif/LdifReader.java?rev=409424&r1=409423&r2=409424&view=diff
==============================================================================
--- directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/ldif/LdifReader.java (original)
+++ directory/trunks/shared/ldap/src/main/java/org/apache/directory/shared/ldap/ldif/LdifReader.java Thu May 25 11:41:03 2006
@@ -5,7 +5,9 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.FileReader;
import java.io.IOException;
+import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
@@ -19,6 +21,8 @@
import javax.naming.InvalidNameException;
import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import javax.naming.ldap.Control;
@@ -31,1260 +35,1516 @@
import org.slf4j.LoggerFactory;
/**
- * <pre>
- * <ldif-file> ::= "version:" <fill> <number> <seps> <dn-spec> <sep> <ldif-content-change>
- *
- * <ldif-content-change> ::=
- * <number> <oid> <options-e> <value-spec> <sep> <attrval-specs-e> <ldif-attrval-record-e> |
- * <alpha> <chars-e> <options-e> <value-spec> <sep> <attrval-specs-e> <ldif-attrval-record-e> |
- * "control:" <fill> <number> <oid> <spaces-e> <criticality> <value-spec-e> <sep> <controls-e>
- * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> |
- * "changetype:" <fill> <changerecord-type> <ldif-change-record-e>
- *
- * <ldif-attrval-record-e> ::= <seps> <dn-spec> <sep> <attributeType>
- * <options-e> <value-spec> <sep> <attrval-specs-e>
- * <ldif-attrval-record-e> | e
- *
- * <ldif-change-record-e> ::= <seps> <dn-spec> <sep> <controls-e>
- * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> | e
- *
- * <dn-spec> ::= "dn:" <fill> <safe-string> | "dn::" <fill> <base64-string>
- *
- * <controls-e> ::= "control:" <fill> <number> <oid> <spaces-e> <criticality>
- * <value-spec-e> <sep> <controls-e> | e
- *
- * <criticality> ::= "true" | "false" | e
- *
- * <oid> ::= '.' <number> <oid> | e
- *
- * <attrval-specs-e> ::= <number> <oid> <options-e> <value-spec> <sep> <attrval-specs-e> |
- * <alpha> <chars-e> <options-e> <value-spec> <sep> <attrval-specs-e> | e
- *
- * <value-spec-e> ::= <value-spec> | e
- *
- * <value-spec> ::= ':' <fill> <safe-string-e> |
- * "::" <fill> <base64-chars> |
- * ":<" <fill> <url>
- *
- * <attributeType> ::= <number> <oid> | <alpha> <chars-e>
- *
- * <options-e> ::= ';' <char> <chars-e> <options-e> |e
- *
- * <chars-e> ::= <char> <chars-e> | e
- *
- * <changerecord-type> ::= "add" <sep> <attributeType> <options-e> <value-spec> <sep> <attrval-specs-e> |
- * "delete" <sep> |
- * "modify" <sep> <mod-type> <fill> <attributeType> <options-e> <sep> <attrval-specs-e> <sep> '-' <sep> <mod-specs-e> |
- * "moddn" <sep> <newrdn> <sep> "deleteoldrdn:" <fill> <0-1> <sep> <newsuperior-e> <sep> |
- * "modrdn" <sep> <newrdn> <sep> "deleteoldrdn:" <fill> <0-1> <sep> <newsuperior-e> <sep>
- *
- * <newrdn> ::= ':' <fill> <safe-string> | "::" <fill> <base64-chars>
- *
- * <newsuperior-e> ::= "newsuperior" <newrdn> | e
- *
- * <mod-specs-e> ::= <mod-type> <fill> <attributeType> <options-e>
- * <sep> <attrval-specs-e> <sep> '-' <sep> <mod-specs-e> | e
- *
- * <mod-type> ::= "add:" | "delete:" | "replace:"
- *
- * <url> ::= <a Uniform Resource Locator, as defined in [6]>
- *
- *
- *
- * LEXICAL
- * -------
- *
- * <fill> ::= ' ' <fill> | e
- * <char> ::= <alpha> | <digit> | '-'
- * <number> ::= <digit> <digits>
- * <0-1> ::= '0' | '1'
- * <digits> ::= <digit> <digits> | e
- * <digit> ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
- * <seps> ::= <sep> <seps-e>
- * <seps-e> ::= <sep> <seps-e> | e
- * <sep> ::= 0x0D 0x0A | 0x0A
- * <spaces> ::= ' ' <spaces-e>
- * <spaces-e> ::= ' ' <spaces-e> | e
- * <safe-string-e> ::= <safe-string> | e
- * <safe-string> ::= <safe-init-char> <safe-chars>
- * <safe-init-char> ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x1F] | [0x21-0x39] | 0x3B | [0x3D-0x7F]
- * <safe-chars> ::= <safe-char> <safe-chars> | e
- * <safe-char> ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x7F]
- * <base64-string> ::= <base64-char> <base64-chars>
- * <base64-chars> ::= <base64-char> <base64-chars> | e
- * <base64-char> ::= 0x2B | 0x2F | [0x30-0x39] | 0x3D | [0x41-9x5A] | [0x61-0x7A]
- * <alpha> ::= [0x41-0x5A] | [0x61-0x7A]
- *
- * COMMENTS
- * --------
- * - The ldap-oid VN is not correct in the RFC-2849. It has been changed from 1*DIGIT 0*1("." 1*DIGIT) to
- * DIGIT+ ("." DIGIT+)*
- * - The mod-spec lacks a sep between *attrval-spec and "-".
- * - The BASE64-UTF8-STRING should be BASE64-CHAR BASE64-STRING
- * - The ValueSpec rule must accept multilines values. In this case, we have a LF followed by a
- * single space before the continued value.
- * </pre>
- */
+ * <pre>
+ * <ldif-file> ::= "version:" <fill> <number> <seps> <dn-spec> <sep> <ldif-content-change>
+ *
+ * <ldif-content-change> ::=
+ * <number> <oid> <options-e> <value-spec> <sep> <attrval-specs-e> <ldif-attrval-record-e> |
+ * <alpha> <chars-e> <options-e> <value-spec> <sep> <attrval-specs-e> <ldif-attrval-record-e> |
+ * "control:" <fill> <number> <oid> <spaces-e> <criticality> <value-spec-e> <sep> <controls-e>
+ * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> |
+ * "changetype:" <fill> <changerecord-type> <ldif-change-record-e>
+ *
+ * <ldif-attrval-record-e> ::= <seps> <dn-spec> <sep> <attributeType>
+ * <options-e> <value-spec> <sep> <attrval-specs-e>
+ * <ldif-attrval-record-e> | e
+ *
+ * <ldif-change-record-e> ::= <seps> <dn-spec> <sep> <controls-e>
+ * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> | e
+ *
+ * <dn-spec> ::= "dn:" <fill> <safe-string> | "dn::" <fill> <base64-string>
+ *
+ * <controls-e> ::= "control:" <fill> <number> <oid> <spaces-e> <criticality>
+ * <value-spec-e> <sep> <controls-e> | e
+ *
+ * <criticality> ::= "true" | "false" | e
+ *
+ * <oid> ::= '.' <number> <oid> | e
+ *
+ * <attrval-specs-e> ::= <number> <oid> <options-e> <value-spec> <sep> <attrval-specs-e> |
+ * <alpha> <chars-e> <options-e> <value-spec> <sep> <attrval-specs-e> | e
+ *
+ * <value-spec-e> ::= <value-spec> | e
+ *
+ * <value-spec> ::= ':' <fill> <safe-string-e> |
+ * "::" <fill> <base64-chars> |
+ * ":<" <fill> <url>
+ *
+ * <attributeType> ::= <number> <oid> | <alpha> <chars-e>
+ *
+ * <options-e> ::= ';' <char> <chars-e> <options-e> |e
+ *
+ * <chars-e> ::= <char> <chars-e> | e
+ *
+ * <changerecord-type> ::= "add" <sep> <attributeType> <options-e> <value-spec> <sep> <attrval-specs-e> |
+ * "delete" <sep> |
+ * "modify" <sep> <mod-type> <fill> <attributeType> <options-e> <sep> <attrval-specs-e> <sep> '-' <sep> <mod-specs-e> |
+ * "moddn" <sep> <newrdn> <sep> "deleteoldrdn:" <fill> <0-1> <sep> <newsuperior-e> <sep> |
+ * "modrdn" <sep> <newrdn> <sep> "deleteoldrdn:" <fill> <0-1> <sep> <newsuperior-e> <sep>
+ *
+ * <newrdn> ::= ':' <fill> <safe-string> | "::" <fill> <base64-chars>
+ *
+ * <newsuperior-e> ::= "newsuperior" <newrdn> | e
+ *
+ * <mod-specs-e> ::= <mod-type> <fill> <attributeType> <options-e>
+ * <sep> <attrval-specs-e> <sep> '-' <sep> <mod-specs-e> | e
+ *
+ * <mod-type> ::= "add:" | "delete:" | "replace:"
+ *
+ * <url> ::= <a Uniform Resource Locator, as defined in [6]>
+ *
+ *
+ *
+ * LEXICAL
+ * -------
+ *
+ * <fill> ::= ' ' <fill> | e
+ * <char> ::= <alpha> | <digit> | '-'
+ * <number> ::= <digit> <digits>
+ * <0-1> ::= '0' | '1'
+ * <digits> ::= <digit> <digits> | e
+ * <digit> ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
+ * <seps> ::= <sep> <seps-e>
+ * <seps-e> ::= <sep> <seps-e> | e
+ * <sep> ::= 0x0D 0x0A | 0x0A
+ * <spaces> ::= ' ' <spaces-e>
+ * <spaces-e> ::= ' ' <spaces-e> | e
+ * <safe-string-e> ::= <safe-string> | e
+ * <safe-string> ::= <safe-init-char> <safe-chars>
+ * <safe-init-char> ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x1F] | [0x21-0x39] | 0x3B | [0x3D-0x7F]
+ * <safe-chars> ::= <safe-char> <safe-chars> | e
+ * <safe-char> ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x7F]
+ * <base64-string> ::= <base64-char> <base64-chars>
+ * <base64-chars> ::= <base64-char> <base64-chars> | e
+ * <base64-char> ::= 0x2B | 0x2F | [0x30-0x39] | 0x3D | [0x41-9x5A] | [0x61-0x7A]
+ * <alpha> ::= [0x41-0x5A] | [0x61-0x7A]
+ *
+ * COMMENTS
+ * --------
+ * - The ldap-oid VN is not correct in the RFC-2849. It has been changed from 1*DIGIT 0*1("." 1*DIGIT) to
+ * DIGIT+ ("." DIGIT+)*
+ * - The mod-spec lacks a sep between *attrval-spec and "-".
+ * - The BASE64-UTF8-STRING should be BASE64-CHAR BASE64-STRING
+ * - The ValueSpec rule must accept multilines values. In this case, we have a LF followed by a
+ * single space before the continued value.
+ * </pre>
+ */
public class LdifReader implements Iterator
- {
+{
/** A logger */
private static final Logger log = LoggerFactory.getLogger( LdifReader.class );
-
- /** A private class to track the current position in a line */
- private class Position
- {
- public int pos;
-
- public Position() { pos = 0; }
-
- public void inc() { pos++; }
-
- public void inc( int val ) { pos += val; }
- }
-
+
+ /** A private class to track the current position in a line */
+ private class Position
+ {
+ public int pos;
+
+ public Position()
+ {
+ pos = 0;
+ }
+
+ public void inc()
+ {
+ pos++;
+ }
+
+ public void inc( int val )
+ {
+ pos += val;
+ }
+ }
+
/** A list of read lines */
- private List lines;
-
- /** The current position */
- private Position position;
-
- /** The ldif file version default value*/
- private static final int DEFAULT_VERSION = 1;
-
- /** The ldif version */
- private int version;
-
- /** Type of element read */
- private static final int ENTRY = 0;
- private static final int CHANGE = 1;
- private static final int UNKNOWN = 2;
-
- /** Size limit for file contained values */
- private long sizeLimit = SIZE_LIMIT_DEFAULT;
-
- /** The default size limit : 1Mo */
- private static final long SIZE_LIMIT_DEFAULT = 1024000;
-
- /** State values for the modify operation */
- private static final int MOD_SPEC = 0;
- private static final int ATTRVAL_SPEC = 1;
- private static final int ATTRVAL_SPEC_OR_SEP = 2;
-
- /** Iterator prefetched entry */
- private Entry prefetched;
-
- /** The ldif Reader */
- private Reader in;
-
- /**
- * Constructors
- */
- public LdifReader()
- {
- lines = new ArrayList();
- position = new Position();
- version = DEFAULT_VERSION;
- }
-
- /**
- * @return The ldif file version
- */
- public int getVersion()
- {
- return version;
- }
-
- /**
- * @return The maximum size of a file which is used into an attribute value.
- */
- public long getSizeLimit()
- {
- return sizeLimit;
- }
-
- /**
- * Set the maximum file size that can be accepted for an attribute value
- *
- * @param sizeLimit The size in bytes
- */
- public void setSizeLimit( long sizeLimit )
- {
- this.sizeLimit = sizeLimit;
- }
-
- // <fill> ::= ' ' <fill> | �
- private static void parseFill( char[] document, Position position )
- {
-
- while ( StringTools.isCharASCII( document, position.pos, ' ' ) )
- {
- position.inc();
- }
- }
+ private List lines;
+
+ /** The current position */
+ private Position position;
+
+ /** The ldif file version default value */
+ private static final int DEFAULT_VERSION = 1;
+
+ /** The ldif version */
+ private int version;
+
+ /** Type of element read */
+ private static final int ENTRY = 0;
+
+ private static final int CHANGE = 1;
+
+ private static final int UNKNOWN = 2;
+
+ /** Size limit for file contained values */
+ private long sizeLimit = SIZE_LIMIT_DEFAULT;
+
+ /** The default size limit : 1Mo */
+ private static final long SIZE_LIMIT_DEFAULT = 1024000;
+
+ /** State values for the modify operation */
+ private static final int MOD_SPEC = 0;
+
+ private static final int ATTRVAL_SPEC = 1;
+
+ private static final int ATTRVAL_SPEC_OR_SEP = 2;
+
+ /** Iterator prefetched entry */
+ private Entry prefetched;
+
+ /** The ldif Reader */
+ private Reader in;
+
+ /** A flag set if the ldif contains entries */
+ private boolean containsEntries;
+
+ /** A flag set if the ldif contains changes */
+ private boolean containsChanges;
+
+ /**
+ * An Exception to handle error message, has Iterator.next() can't throw
+ * exceptions
+ */
+ private Exception error;
+
+ /**
+ * Constructors
+ */
+ public LdifReader()
+ {
+ lines = new ArrayList();
+ position = new Position();
+ version = DEFAULT_VERSION;
+ }
+
+ private void init( BufferedReader in ) throws NamingException
+ {
+ this.in = in;
+ lines = new ArrayList();
+ position = new Position();
+ version = DEFAULT_VERSION;
+ containsChanges = false;
+ containsEntries = false;
+
+ // First get the version - if any -
+ version = parseVersion();
+ prefetched = parseEntry();
+ }
+
+ /**
+ * A constructor which takes a file name
+ *
+ * @param in
+ * A file name containing ldif formated input
+ * @throws NamingException
+ * If the file cannot be processed or if the format is incorrect
+ */
+ public LdifReader( String ldifFileName ) throws NamingException
+ {
+ File in = new File( ldifFileName );
+
+ if ( in.exists() == false )
+ {
+ log.error( "File {} cannot be found", in.getAbsoluteFile() );
+ throw new NamingException( "Cannot find file " + in.getAbsoluteFile() );
+ }
+
+ if ( in.canRead() == false )
+ {
+ log.error( "File {} cannot be read", in.getName() );
+ throw new NamingException( "Cannot read file " + in.getName() );
+ }
+
+ try
+ {
+ init( new BufferedReader( new FileReader( in ) ) );
+ }
+ catch (FileNotFoundException fnfe)
+ {
+ log.error( "File {} cannot be found", in.getAbsoluteFile() );
+ throw new NamingException( "Cannot find file " + in.getAbsoluteFile() );
+ }
+ }
+
+ /**
+ * A constructor which takes a BufferedReader
+ *
+ * @param in
+ * A BufferedReader containing ldif formated input
+ * @throws NamingException
+ * If the file cannot be processed or if the format is incorrect
+ */
+ public LdifReader( BufferedReader in ) throws NamingException
+ {
+ init( in );
+ }
+
+ /**
+ * A constructor which takes a Reader
+ *
+ * @param in
+ * A Reader containing ldif formated input
+ * @throws NamingException
+ * If the file cannot be processed or if the format is incorrect
+ */
+ public LdifReader( Reader in ) throws NamingException
+ {
+ init( new BufferedReader( in ) );
+ }
+
+ /**
+ * A constructor which takes an InputStream
+ *
+ * @param in
+ * An InputStream containing ldif formated input
+ * @throws NamingException
+ * If the file cannot be processed or if the format is incorrect
+ */
+ public LdifReader( InputStream in ) throws NamingException
+ {
+ init( new BufferedReader( new InputStreamReader( in ) ) );
+ }
+
+ /**
+ * A constructor which takes a File
+ *
+ * @param in
+ * A File containing ldif formated input
+ * @throws NamingException
+ * If the file cannot be processed or if the format is incorrect
+ */
+ public LdifReader( File in ) throws NamingException
+ {
+ if ( in.exists() == false )
+ {
+ log.error( "File {} cannot be found", in.getAbsoluteFile() );
+ throw new NamingException( "Cannot find file " + in.getAbsoluteFile() );
+ }
+
+ if ( in.canRead() == false )
+ {
+ log.error( "File {} cannot be read", in.getName() );
+ throw new NamingException( "Cannot read file " + in.getName() );
+ }
+
+ try
+ {
+ init( new BufferedReader( new FileReader( in ) ) );
+ }
+ catch (FileNotFoundException fnfe)
+ {
+ log.error( "File {} cannot be found", in.getAbsoluteFile() );
+ throw new NamingException( "Cannot find file " + in.getAbsoluteFile() );
+ }
+ }
+
+ /**
+ * @return The ldif file version
+ */
+ public int getVersion()
+ {
+ return version;
+ }
+
+ /**
+ * @return The maximum size of a file which is used into an attribute value.
+ */
+ public long getSizeLimit()
+ {
+ return sizeLimit;
+ }
+
+ /**
+ * Set the maximum file size that can be accepted for an attribute value
+ *
+ * @param sizeLimit
+ * The size in bytes
+ */
+ public void setSizeLimit( long sizeLimit )
+ {
+ this.sizeLimit = sizeLimit;
+ }
+
+ // <fill> ::= ' ' <fill> | �
+ private static void parseFill( char[] document, Position position )
+ {
+
+ while ( StringTools.isCharASCII( document, position.pos, ' ' ) )
+ {
+ position.inc();
+ }
+ }
/**
* Parse a number following the rules :
*
- * <number> ::= <digit> <digits>
- * <digits> ::= <digit> <digits> | e
- * <digit> ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
+ * <number> ::= <digit> <digits> <digits> ::= <digit> <digits> | e <digit>
+ * ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
*
* Check that the number is in the interval
*/
- private static String parseNumber( char[] document, Position position )
+ private static String parseNumber( char[] document, Position position )
+ {
+ int initPos = position.pos;
+
+ while ( true )
+ {
+ if ( StringTools.isDigit( document, position.pos ) )
+ {
+ position.inc();
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if ( position.pos == initPos )
+ {
+ return null;
+ }
+ else
+ {
+ return new String( document, initPos, position.pos - initPos );
+ }
+ }
+
+ /**
+ * Parse the changeType
+ *
+ * @param line
+ * The line which contains the changeType
+ * @return The operation.
+ */
+ private int parseChangeType( String line )
{
- int initPos = position.pos;
-
- while (true)
- {
- if ( StringTools.isDigit( document, position.pos ) )
- {
- position.inc();
- }
- else
- {
- break;
- }
- }
-
- if (position.pos == initPos)
- {
- return null;
- }
- else
- {
- return new String( document, initPos, position.pos - initPos );
- }
- }
-
- /**
- * Parse the changeType
- *
- * @param line The line which contains the changeType
- * @return The operation.
- */
- private int parseChangeType( String line )
- {
- int operation = Entry.ADD;
-
- String modOp = StringTools.trim( line.substring( "changetype:".length() + 1 ) );
-
- if ( "add".equalsIgnoreCase( modOp ) )
- {
- operation = Entry.ADD;
- }
- else if ( "delete".equalsIgnoreCase( modOp ) )
- {
- operation = Entry.DELETE;
- }
- else if ( "modify".equalsIgnoreCase( modOp ) )
- {
- operation = Entry.MODIFY;
- }
- else if ( "moddn".equalsIgnoreCase( modOp ) )
- {
- operation = Entry.MODDN;
- }
- else if ( "modrdn".equalsIgnoreCase( modOp ) )
- {
- operation = Entry.MODRDN;
- }
-
- return operation;
- }
-
- /**
- * Parse the DN of an entry
- *
- * @param line The line to parse
- * @return A DN
- * @throws NamingException If the DN is invalid
- */
- private String parseDn( String line ) throws NamingException
- {
- String dn = null;
-
- String lowerLine = line.toLowerCase();
-
- if ( lowerLine.startsWith( "dn:" ) || lowerLine.startsWith( "DN:" ) )
- {
- // Ok, we have a DN. Is it base 64 encoded ?
- int length = line.length();
-
- if ( length == 3 )
- {
- // The DN is empty : error
- log.error( "A ldif entry must have a non empty DN" );
- throw new NamingException( "No DN for entry" );
- }
- else if ( line.charAt( 3 ) == ':' )
- {
- if ( length > 4 )
- {
- // This is a base 64 encoded DN.
- String trimmedLine = line.substring( 4 ).trim();
-
- try
- {
- dn = new String( Base64.decode( trimmedLine.toCharArray() ), "UTF-8" );
- }
- catch ( UnsupportedEncodingException uee )
- {
- // The DN is not base 64 encoded
- log.error( "The ldif entry is supposed to have a base 64 encoded DN" );
- throw new NamingException( "Invalid base 64 encoded DN" );
- }
- }
- else
- {
- // The DN is empty : error
- log.error( "A ldif entry must have a non empty DN" );
- throw new NamingException( "No DN for entry" );
- }
- }
- else
- {
- dn = line.substring( 3 ).trim();
- }
- }
- else
- {
- log.error( "A ldif entry must start with a DN" );
- throw new NamingException( "No DN for entry" );
- }
-
- // Check that the DN is valid. If not, an exception will be thrown
- try
- {
- LdapDnParser.parseInternal( dn, new ArrayList() );
- }
- catch ( InvalidNameException ine )
- {
- log.error( "The DN {} is not valid" );
- throw ine;
- }
-
- return dn;
- }
-
- /**
- * Parse the value part.
- * @param line The line which contains the value
- * @param pos The starting position in the line
- * @return A String or a byte[], depending of the kind of value we get
- * @throws NamingException If something went wrong
- */
- private Object parseValue( String line, int pos ) throws NamingException
- {
- if ( line.length() > pos + 1 )
- {
- char c = line.charAt( pos + 1 );
-
- if ( c == ':' )
- {
- String value = StringTools.trim( line.substring( pos + 2 ) );
-
- return Base64.decode( value.toCharArray() );
- }
- else if ( c == '<' )
- {
- String urlName = StringTools.trim( line.substring( pos + 2 ) );
-
- try
- {
- URL url = new URL( urlName );
-
- if ( "file".equals( url.getProtocol() ) )
- {
- String fileName = url.getFile();
-
- File file = new File( fileName );
-
- if ( file.exists() == false )
- {
- log.error( "File {} not found", fileName );
- throw new NamingException( "Bad URL, file not found" );
- }
- else
- {
- long length = file.length();
-
- if ( length > sizeLimit )
- {
- log.error( "File {} is too big", fileName );
- throw new NamingException( "File too big" );
- }
- else
- {
- byte[] data = new byte[ (int)length ];
-
- try
- {
- DataInputStream in = new DataInputStream( new FileInputStream( file ) );
- in.read( data );
-
- return data;
- }
- catch ( FileNotFoundException fnfe )
- {
- // We can't reach this point, the file existence has already been
- // checked
- log.error( "File {} not found", fileName );
- throw new NamingException( "Bad URL, file not found" );
- }
- catch ( IOException ioe )
- {
- log.error( "File {} error reading", fileName );
- throw new NamingException( "Bad URL, file can't be read" );
- }
-
- }
- }
- }
- else
- {
- log.error( "Protocols other than file: are not supported" );
- throw new NamingException( "Unsupported URL protocol" );
- }
- }
- catch ( MalformedURLException mue )
- {
- log.error( "Bad URL {}", urlName );
- throw new NamingException( "Bad URL" );
- }
- }
- else
- {
- return StringTools.trim( line.substring( pos + 1 ) );
- }
- }
- else
- {
- return null;
- }
- }
-
- /**
- * Parse a control. The grammar is :
- * <control> ::= "control:" <fill> <ldap-oid> <critical-e> <value-spec-e> <sep>
- * <critical-e> ::= <spaces> <boolean> | e
- * <boolean> ::= "true" | "false"
- * <value-spec-e> ::= <value-spec> | e
- * <value-spec> ::= ":" <fill> <SAFE-STRING-e> | "::" <fill> <BASE64-STRING> | ":<" <fill> <url>
- *
- * It can be read as :
- * "control:" <fill> <ldap-oid> [ " "+ ( "true" | "false") ] [ ":" <fill> <SAFE-STRING-e> |
- * "::" <fill> <BASE64-STRING> | ":<" <fill> <url> ]
- *
- * @param line The line containing the control
- * @return A control
- */
- private Control parseControl( String line ) throws NamingException
- {
- String lowerLine = line.toLowerCase().trim();
- char[] controlValue = line.trim().toCharArray();
- int pos = 0;
- int length = controlValue.length;
-
- // Get the <ldap-oid>
- if ( pos > length )
- {
- // No OID : error !
- log.error( "The control does not have an OID" );
- throw new NamingException( "Bad control, no oid" );
- }
-
- int initPos = pos;
-
- while ( StringTools.isCharASCII( controlValue, pos, '.' ) ||
- StringTools.isDigit( controlValue, pos ) )
- {
- pos++;
- }
-
- if ( pos == initPos )
- {
- // Not a valid OID !
- log.error( "The control does not have an OID" );
- throw new NamingException( "Bad control, no oid" );
- }
-
- // Create and check the OID
- String oidString = lowerLine.substring( 0, pos );
-
- OID oid = null;
-
- try
- {
- oid = new OID( oidString );
- }
- catch ( DecoderException de )
- {
- log.error( "The OID {} is not valid", oidString );
- throw new NamingException( "Bad control oid" );
- }
-
- LdifControl control = new LdifControl( oid );
-
- // Get the criticality, if any
- // Skip the <fill>
- while ( StringTools.isCharASCII( controlValue, pos, ' ' ) )
- {
- pos++;
- }
-
- // Check if we have a "true" or a "false"
- int criticalPos = lowerLine.indexOf( ':' );
-
- int criticalLength = 0;
-
- if ( criticalPos == -1 )
- {
- criticalLength = length - pos;
- }
- else
- {
- criticalLength = criticalPos - pos;
- }
-
- if ( ( criticalLength == 4 ) && ( "true".equalsIgnoreCase( lowerLine.substring( pos, pos + 4 ) ) ) )
- {
- control.setCriticality( true );
- }
- else if ( ( criticalLength == 5 ) && ( "false".equalsIgnoreCase( lowerLine.substring( pos, pos + 5 ) ) ) )
- {
- control.setCriticality( false );
- }
- else if ( criticalLength != 0 )
- {
- // If we have a criticality, it should be either "true" or "false", nothing else
- log.error( "The control muts have a valid criticality" );
- throw new NamingException( "Bad control criticality" );
- }
-
- if ( criticalPos > 0 )
- {
- // We have a value. It can be a normal value, a base64 encoded value
- // or a file contained value
- if ( StringTools.isCharASCII( controlValue, criticalPos + 1, ':' ) )
- {
- // Base 64 encoded value
- byte[] value = Base64.decode( line.substring( criticalPos + 2 ).toCharArray() );
- control.setValue( value );
- }
- else if ( StringTools.isCharASCII( controlValue, criticalPos + 1, '<' ) )
- {
- // File contained value
- }
- else
- {
- // Standard value
- byte[] value = new byte[ length - criticalPos - 1];
-
- for ( int i = 0; i < length - criticalPos - 1; i++ )
- {
- value[i] = (byte)controlValue[i + criticalPos + 1];
- }
-
- control.setValue( value );
- }
- }
-
- return control;
- }
-
- /**
- * Parse an AttributeType/AttributeValue
- *
- * @param entry The entry where to store the value
- * @param line The line to parse
- * @param lowerLine The same line, lowercased
- * @throws NamingException If anything goes wrong
- */
- private void parseAttributeValue( Entry entry, String line, String lowerLine ) throws NamingException
- {
- int colonIndex = line.indexOf( ':' );
-
- String attributeType = lowerLine.substring( 0, colonIndex );
-
- // We should *not* have a DN twice
- if ( attributeType.equals( "dn" ) )
- {
- log.error( "An entry must not have two DNs" );
- throw new NamingException( "A ldif entry should not have two DN" );
- }
-
- Object attributeValue = parseValue( line, colonIndex );
-
- // Update the entry
- entry.addAttribute( attributeType, attributeValue );
- }
-
- /**
- * Parse a ModRDN operation
- * @param entry The entry to update
- * @param iter The lines iterator
- * @throws NamingException If anything goes wrong
- */
- private void parseModRdn( Entry entry, Iterator iter ) throws NamingException
- {
- // We must have two lines : one starting with "newrdn:" or "newrdn::",
- // and the second starting with "deleteoldrdn:"
- if ( iter.hasNext() )
- {
- String line = (String)iter.next();
- String lowerLine = line.toLowerCase();
-
- if ( lowerLine.startsWith( "newrdn::" ) || lowerLine.startsWith( "newrdn:") )
- {
- int colonIndex = line.indexOf( ':' );
- Object attributeValue = parseValue( line, colonIndex );
- entry.setNewRdn( attributeValue instanceof String ?
- (String)attributeValue :
- StringTools.utf8ToString( (byte[])attributeValue ));
- }
- else
- {
- log.error( "A modrdn operation must start with a \"newrdn:\"" );
- throw new NamingException( "Bad modrdn operation" );
- }
-
- }
- else
- {
- log.error( "A modrdn operation must start with a \"newrdn:\"" );
- throw new NamingException( "Bad modrdn operation, no newrdn" );
- }
-
- if ( iter.hasNext() )
- {
- String line = (String)iter.next();
- String lowerLine = line.toLowerCase();
-
- if ( lowerLine.startsWith( "deleteoldrdn:" ) )
- {
- int colonIndex = line.indexOf( ':' );
- Object attributeValue = parseValue( line, colonIndex );
- entry.setDeleteOldRdn( "1".equals( attributeValue ) );
- }
- else
- {
- log.error( "A modrdn operation must contains a \"deleteoldrdn:\"" );
- throw new NamingException( "Bad modrdn operation, no deleteoldrdn" );
- }
- }
- else
- {
- log.error( "A modrdn operation must contains a \"deleteoldrdn:\"" );
- throw new NamingException( "Bad modrdn operation, no deleteoldrdn" );
- }
-
- return;
- }
-
- /**
- * Parse a modify change type.
- *
- * The grammar is :
- * <changerecord> ::= "changetype:" FILL "modify" SEP <mod-spec> <mod-specs-e>
- * <mod-spec> ::= "add:" <mod-val> | "delete:" <mod-val-del> | "replace:" <mod-val>
- * <mod-specs-e> ::= <mod-spec> <mod-specs-e> | e
- * <mod-val> ::= FILL ATTRIBUTE-DESCRIPTION SEP ATTRVAL-SPEC <attrval-specs-e> "-" SEP
- * <mod-val-del> ::= FILL ATTRIBUTE-DESCRIPTION SEP <attrval-specs-e> "-" SEP
- * <attrval-specs-e> ::= ATTRVAL-SPEC <attrval-specs> | e *
- *
- * @param entry The entry to feed
- * @param iter The lines
- */
- private void parseModify( Entry entry, Iterator iter ) throws NamingException
- {
- int state = MOD_SPEC;
- String modified = null;
- int modification = 0;
-
- // The folowwing flag is used to deal with empty modifications
- boolean isEmptyValue = true;
-
- while ( iter.hasNext() )
- {
- String line = (String)iter.next();
- String lowerLine = line.toLowerCase();
-
- if ( lowerLine.startsWith( "-" ) )
- {
- if ( state != ATTRVAL_SPEC_OR_SEP )
- {
- log.error( "Bad state : we should have come from an ATTRVAL_SPEC" );
- throw new NamingException( "Bad modify separator" );
- }
- else
- {
- if ( isEmptyValue )
- {
- // Update the entry
- entry.addModificationItem( modification, modified, null );
- }
-
- state = MOD_SPEC;
- isEmptyValue = true;
- continue;
- }
- }
- else if ( lowerLine.startsWith( "add:" ) )
- {
- if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) )
- {
- log.error( "Bad state : we should have come from a MOD_SPEC or an ATTRVAL_SPEC" );
- throw new NamingException( "Bad modify state" );
- }
-
- modified = StringTools.trim( line.substring( "add:".length() ) );
- modification = DirContext.ADD_ATTRIBUTE;
-
- state = ATTRVAL_SPEC;
- }
- else if ( lowerLine.startsWith( "delete:" ) )
- {
- if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) )
- {
- log.error( "Bad state : we should have come from a MOD_SPEC or an ATTRVAL_SPEC" );
- throw new NamingException( "Bad modify state" );
- }
-
- modified = StringTools.trim( line.substring( "delete:".length() ) );
- modification = DirContext.REMOVE_ATTRIBUTE;
-
- state = ATTRVAL_SPEC_OR_SEP;
- }
- else if ( lowerLine.startsWith( "replace:" ) )
- {
- if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) )
- {
- log.error( "Bad state : we should have come from a MOD_SPEC or an ATTRVAL_SPEC" );
- throw new NamingException( "Bad modify state" );
- }
-
- modified = StringTools.trim( line.substring( "replace:".length() ) );
- modification = DirContext.REPLACE_ATTRIBUTE;
-
- state = ATTRVAL_SPEC_OR_SEP;
- }
- else
- {
- if ( ( state != ATTRVAL_SPEC ) && ( state != ATTRVAL_SPEC_OR_SEP ) )
- {
- log.error( "Bad state : we should have come from an ATTRVAL_SPEC" );
- throw new NamingException( "Bad modify state" );
- }
-
- // A standard AttributeType/AttributeValue pair
- int colonIndex = line.indexOf( ':' );
-
- String attributeType = lowerLine.substring( 0, colonIndex );
-
- if ( attributeType.equals( modified ) == false )
- {
- log.error( "The modified attribute and the attribute value spec must be equal" );
- throw new NamingException( "Bad modify attribute" );
- }
-
- // We should *not* have a DN twice
- if ( attributeType.equals( "dn" ) )
- {
- log.error( "An entry must not have two DNs" );
- throw new NamingException( "A ldif entry should not have two DN" );
- }
-
- Object attributeValue = parseValue( line, colonIndex );
-
- // Update the entry
- entry.addModificationItem( modification, attributeType, attributeValue );
- isEmptyValue = false;
-
- state = ATTRVAL_SPEC_OR_SEP;
- }
- }
- }
-
- /**
- * Parse a change operation. We have to handle different cases depending on the
- * operation.
- * 1) Delete : there should *not* be any line after the "changetype: delete"
- * 2) Add : we must have a list of AttributeType : AttributeValue elements
- * 3) ModDN : we must have two following lines: a "newrdn:" and a "deleteoldrdn:"
- * 4) ModRDN : the very same, but a "newsuperior:" line is expected
- * 5) Modify :
- *
- * The grammar is :
- * <changerecord> ::= "changetype:" FILL "add" SEP <attrval-spec> <attrval-specs-e> |
- * "changetype:" FILL "delete" |
- * "changetype:" FILL "modrdn" SEP <newrdn> SEP <deleteoldrdn> SEP | // To be checked
- * "changetype:" FILL "moddn" SEP <newrdn> SEP <deleteoldrdn> SEP <newsuperior> SEP |
- * "changetype:" FILL "modify" SEP <mod-spec> <mod-specs-e>
- * <newrdn> ::= "newrdn:" FILL RDN | "newrdn::" FILL BASE64-RDN
- * <deleteoldrdn> ::= "deleteoldrdn:" FILL "0" | "deleteoldrdn:" FILL "1"
- * <newsuperior> ::= "newsuperior:" FILL DN | "newsuperior::" FILL BASE64-DN
- * <mod-specs-e> ::= <mod-spec> <mod-specs-e> | e
- * <mod-spec> ::= "add:" <mod-val> | "delete:" <mod-val> | "replace:" <mod-val>
- * <mod-val> ::= FILL ATTRIBUTE-DESCRIPTION SEP ATTRVAL-SPEC <attrval-specs-e> "-" SEP
- * <attrval-specs-e> ::= ATTRVAL-SPEC <attrval-specs> | e
- *
- * @param entry The entry to feed
- * @param iter The lines iterator
- * @param operation The change operation (add, modify, delete, moddn or modrdn)
- * @param control The associated control, if any
- * @return A modification entry
- */
- private void parseChange( Entry entry, Iterator iter, int operation, Control control ) throws NamingException
- {
- // The changetype and operation has already been parsed.
- entry.setChangeType( operation);
-
- switch ( operation )
- {
- case Entry.DELETE :
- // The change type will tell that it's a delete operation,
- // the dn is used as a key.
- return;
-
- case Entry.ADD :
- // We will iterate through all attribute/value pairs
- while ( iter.hasNext() )
- {
- String line = (String)iter.next();
- String lowerLine = line.toLowerCase();
- parseAttributeValue( entry, line, lowerLine );
- }
-
- return;
-
- case Entry.MODIFY :
- parseModify( entry, iter );
- return;
-
- case Entry.MODRDN :// They are supposed to have the same syntax ???
- case Entry.MODDN :
- // First, parse the modrdn part
- parseModRdn( entry, iter );
-
- // The next line should be the new superior
- if ( iter.hasNext() )
- {
- String line = (String)iter.next();
- String lowerLine = line.toLowerCase();
-
- if ( lowerLine.startsWith( "newsuperior:" ) )
- {
- int colonIndex = line.indexOf( ':' );
- Object attributeValue = parseValue( line, colonIndex );
- entry.setNewSuperior( attributeValue instanceof String ?
- (String)attributeValue :
- StringTools.utf8ToString( (byte[])attributeValue ));
- }
- else
- {
- if ( operation == Entry.MODDN )
- {
- log.error( "A moddn operation must contains a \"newsuperior:\"" );
- throw new NamingException( "Bad moddn operation, no newsuperior" );
- }
- }
- }
- else
- {
- if ( operation == Entry.MODDN )
- {
- log.error( "A moddn operation must contains a \"newsuperior:\"" );
- throw new NamingException( "Bad moddn operation, no newsuperior" );
- }
- }
-
- return;
-
- default :
- // This is an error
- log.error( "Unknown operation" );
- throw new NamingException( "Bad operation" );
- }
- }
-
- /**
- * Parse a ldif file. The following rules are processed :
- *
- * <ldif-file> ::= <ldif-attrval-record> <ldif-attrval-records> |
- * <ldif-change-record> <ldif-change-records>
- * <ldif-attrval-record> ::= <dn-spec> <sep> <attrval-spec> <attrval-specs>
- * <ldif-change-record> ::= <dn-spec> <sep> <controls-e> <changerecord>
- * <dn-spec> ::= "dn:" <fill> <distinguishedName> | "dn::" <fill> <base64-distinguishedName>
- * <changerecord> ::= "changetype:" <fill> <change-op>
- */
- private Entry parseEntry( ) throws NamingException
- {
- if ( ( lines == null ) || ( lines.size() == 0 ) )
- {
- log.warn( "The entry is empty : end of ldif file ???" );
- return null;
- }
-
- // The entry must start with a dn: or a dn::
- String line = ((String)lines.get( 0 ));
-
- String dn = parseDn( line );
-
-
- // Ok, we have found a DN
- Entry entry = new Entry();
- entry.setDn( dn );
-
- // We remove this dn from the lines
- lines.remove( 0 );
-
- // Now, let's iterate through the other lines
- Iterator iter = lines.iterator();
-
- // This flag is used to distinguish between an entry and a change
- int type = UNKNOWN;
-
- // The following boolean is used to check that a control is *not*
- // found elswhere than just after the dn
- boolean controlSeen = false;
-
- // We use this boolean to check that we do not have AttributeValues
- // after a change operation
- boolean changeTypeSeen = false;
-
- int operation = Entry.ADD;
- String lowerLine = null;
- Control control = null;
-
- while ( iter.hasNext() )
- {
- // Each line could start either with an OID, an attribute type, with "control:" or with "changetype:"
- line = (String)iter.next();
- lowerLine = line.toLowerCase();
-
- // We have three cases :
- // 1) The first line after the DN is a "control:"
- // 2) The first line after the DN is a "changeType:"
- // 3) The first line after the DN is anything else
- if ( lowerLine.startsWith( "control:" ) )
- {
- if ( controlSeen )
- {
- log.error( "We already have had a control" );
- throw new NamingException( "Control misplaced" );
- }
-
- // Parse the control
- control = parseControl( line.substring( "control:".length() ) );
- entry.setControl( control );
- }
- else if ( lowerLine.startsWith( "changetype:" ) )
- {
- if ( changeTypeSeen )
- {
- log.error( "We already have had a changeType" );
- throw new NamingException( "ChangeType misplaced" );
- }
-
- // A change request
- type = CHANGE;
- controlSeen = true;
-
- operation = parseChangeType( line );
-
- // Parse the change operation in a separate function
- parseChange( entry, iter, operation, control );
- changeTypeSeen = true;
- }
- else if ( line.indexOf( ':' ) > 0 )
- {
- if ( controlSeen || changeTypeSeen )
- {
- log.error( "We can't have a Attribute/Value pair after a control or a changeType" );
- throw new NamingException( "AttributeType misplaced" );
- }
-
- parseAttributeValue( entry, line, lowerLine );
- type = ENTRY;
- }
- else
- {
- // Invalid attribute Value
- log.error( "Expecting an attribute type" );
- throw new NamingException( "Bad attribute" );
- }
- }
-
- if ( type == ENTRY )
- {
- log.debug( "Read an entry : {}", entry );
- }
- else if ( type == CHANGE )
- {
- entry.setChangeType( operation );
- log.debug( "Read a modification : {}", entry );
- }
- else
- {
- log.error( "Unknown entry type" );
- throw new NamingException( "Unknown entry" );
- }
-
- return entry;
- }
-
- /**
- * Parse the version from the ldif input.
- *
- * @param in The input which contains the ldif data
- * @return A number representing the version (default to 1)
- * @throws NamingException If the version is incorrect
- * @throws IOException If the input is incorrect
- */
- private int parseVersion() throws NamingException
- {
- int version = DEFAULT_VERSION;
-
- // First, read a list of lines
- readLines();
-
- if ( lines.size() == 0 )
- {
- log.warn( "The ldif file is empty" );
- return version;
- }
-
- // get the first line
- String line = (String)lines.get( 0 );
-
- // <ldif-file> ::= "version:" <fill> <number>
- char[] document = line.toCharArray();
- String versionNumber = null;
-
- if ( line.startsWith( "version:" ) )
- {
- position.inc( "version:".length() );
- parseFill( document, position );
-
- // Version number. Must be '1' in this version
- versionNumber = parseNumber( document, position );
-
- // We should not have any other chars after the number
- if ( position.pos != document.length )
- {
- log.error( "The version is not a number" );
- throw new NamingException( "Ldif parsing error" );
- }
-
- try
- {
- version = Integer.parseInt( versionNumber );
- }
- catch ( NumberFormatException nfe )
- {
- log.error( "The version is not a number" );
- throw new NamingException( "Ldif parsing error" );
- }
-
- log.debug( "Ldif version : {}", versionNumber );
-
- // We have found the version, just discard the line from the list
- lines.remove( 0 );
- }
- else
- {
- log.warn( "No version information : assuming version: 1" );
- }
-
+ int operation = Entry.ADD;
+
+ String modOp = StringTools.trim( line.substring( "changetype:".length() + 1 ) );
+
+ if ( "add".equalsIgnoreCase( modOp ) )
+ {
+ operation = Entry.ADD;
+ }
+ else if ( "delete".equalsIgnoreCase( modOp ) )
+ {
+ operation = Entry.DELETE;
+ }
+ else if ( "modify".equalsIgnoreCase( modOp ) )
+ {
+ operation = Entry.MODIFY;
+ }
+ else if ( "moddn".equalsIgnoreCase( modOp ) )
+ {
+ operation = Entry.MODDN;
+ }
+ else if ( "modrdn".equalsIgnoreCase( modOp ) )
+ {
+ operation = Entry.MODRDN;
+ }
+
+ return operation;
+ }
+
+ /**
+ * Parse the DN of an entry
+ *
+ * @param line
+ * The line to parse
+ * @return A DN
+ * @throws NamingException
+ * If the DN is invalid
+ */
+ private String parseDn( String line ) throws NamingException
+ {
+ String dn = null;
+
+ String lowerLine = line.toLowerCase();
+
+ if ( lowerLine.startsWith( "dn:" ) || lowerLine.startsWith( "DN:" ) )
+ {
+ // Ok, we have a DN. Is it base 64 encoded ?
+ int length = line.length();
+
+ if ( length == 3 )
+ {
+ // The DN is empty : error
+ log.error( "A ldif entry must have a non empty DN" );
+ throw new NamingException( "No DN for entry" );
+ }
+ else if ( line.charAt( 3 ) == ':' )
+ {
+ if ( length > 4 )
+ {
+ // This is a base 64 encoded DN.
+ String trimmedLine = line.substring( 4 ).trim();
+
+ try
+ {
+ dn = new String( Base64.decode( trimmedLine.toCharArray() ), "UTF-8" );
+ }
+ catch (UnsupportedEncodingException uee)
+ {
+ // The DN is not base 64 encoded
+ log.error( "The ldif entry is supposed to have a base 64 encoded DN" );
+ throw new NamingException( "Invalid base 64 encoded DN" );
+ }
+ }
+ else
+ {
+ // The DN is empty : error
+ log.error( "A ldif entry must have a non empty DN" );
+ throw new NamingException( "No DN for entry" );
+ }
+ }
+ else
+ {
+ dn = line.substring( 3 ).trim();
+ }
+ }
+ else
+ {
+ log.error( "A ldif entry must start with a DN" );
+ throw new NamingException( "No DN for entry" );
+ }
+
+ // Check that the DN is valid. If not, an exception will be thrown
+ try
+ {
+ LdapDnParser.parseInternal( dn, new ArrayList() );
+ }
+ catch (InvalidNameException ine)
+ {
+ log.error( "The DN {} is not valid" );
+ throw ine;
+ }
+
+ return dn;
+ }
+
+ /**
+ * Parse the value part.
+ *
+ * @param line
+ * The line which contains the value
+ * @param pos
+ * The starting position in the line
+ * @return A String or a byte[], depending of the kind of value we get
+ */
+ private static Object parseSimpleValue( String line, int pos )
+ {
+ if ( line.length() > pos + 1 )
+ {
+ char c = line.charAt( pos + 1 );
+
+ if ( c == ':' )
+ {
+ String value = StringTools.trim( line.substring( pos + 2 ) );
+
+ return Base64.decode( value.toCharArray() );
+ }
+ else
+ {
+ return StringTools.trim( line.substring( pos + 1 ) );
+ }
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Parse the value part.
+ *
+ * @param line
+ * The line which contains the value
+ * @param pos
+ * The starting position in the line
+ * @return A String or a byte[], depending of the kind of value we get
+ * @throws NamingException
+ * If something went wrong
+ */
+ private Object parseValue( String line, int pos ) throws NamingException
+ {
+ if ( line.length() > pos + 1 )
+ {
+ char c = line.charAt( pos + 1 );
+
+ if ( c == ':' )
+ {
+ String value = StringTools.trim( line.substring( pos + 2 ) );
+
+ return Base64.decode( value.toCharArray() );
+ }
+ else if ( c == '<' )
+ {
+ String urlName = StringTools.trim( line.substring( pos + 2 ) );
+
+ try
+ {
+ URL url = new URL( urlName );
+
+ if ( "file".equals( url.getProtocol() ) )
+ {
+ String fileName = url.getFile();
+
+ File file = new File( fileName );
+
+ if ( file.exists() == false )
+ {
+ log.error( "File {} not found", fileName );
+ throw new NamingException( "Bad URL, file not found" );
+ }
+ else
+ {
+ long length = file.length();
+
+ if ( length > sizeLimit )
+ {
+ log.error( "File {} is too big", fileName );
+ throw new NamingException( "File too big" );
+ }
+ else
+ {
+ byte[] data = new byte[(int) length];
+
+ try
+ {
+ DataInputStream in = new DataInputStream( new FileInputStream( file ) );
+ in.read( data );
+
+ return data;
+ }
+ catch (FileNotFoundException fnfe)
+ {
+ // We can't reach this point, the file
+ // existence has already been
+ // checked
+ log.error( "File {} not found", fileName );
+ throw new NamingException( "Bad URL, file not found" );
+ }
+ catch (IOException ioe)
+ {
+ log.error( "File {} error reading", fileName );
+ throw new NamingException( "Bad URL, file can't be read" );
+ }
+
+ }
+ }
+ }
+ else
+ {
+ log.error( "Protocols other than file: are not supported" );
+ throw new NamingException( "Unsupported URL protocol" );
+ }
+ }
+ catch (MalformedURLException mue)
+ {
+ log.error( "Bad URL {}", urlName );
+ throw new NamingException( "Bad URL" );
+ }
+ }
+ else
+ {
+ return StringTools.trim( line.substring( pos + 1 ) );
+ }
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Parse a control. The grammar is : <control> ::= "control:" <fill>
+ * <ldap-oid> <critical-e> <value-spec-e> <sep> <critical-e> ::= <spaces>
+ * <boolean> | e <boolean> ::= "true" | "false" <value-spec-e> ::=
+ * <value-spec> | e <value-spec> ::= ":" <fill> <SAFE-STRING-e> | "::"
+ * <fill> <BASE64-STRING> | ":<" <fill> <url>
+ *
+ * It can be read as : "control:" <fill> <ldap-oid> [ " "+ ( "true" |
+ * "false") ] [ ":" <fill> <SAFE-STRING-e> | "::" <fill> <BASE64-STRING> | ":<"
+ * <fill> <url> ]
+ *
+ * @param line
+ * The line containing the control
+ * @return A control
+ */
+ private Control parseControl( String line ) throws NamingException
+ {
+ String lowerLine = line.toLowerCase().trim();
+ char[] controlValue = line.trim().toCharArray();
+ int pos = 0;
+ int length = controlValue.length;
+
+ // Get the <ldap-oid>
+ if ( pos > length )
+ {
+ // No OID : error !
+ log.error( "The control does not have an OID" );
+ throw new NamingException( "Bad control, no oid" );
+ }
+
+ int initPos = pos;
+
+ while ( StringTools.isCharASCII( controlValue, pos, '.' ) || StringTools.isDigit( controlValue, pos ) )
+ {
+ pos++;
+ }
+
+ if ( pos == initPos )
+ {
+ // Not a valid OID !
+ log.error( "The control does not have an OID" );
+ throw new NamingException( "Bad control, no oid" );
+ }
+
+ // Create and check the OID
+ String oidString = lowerLine.substring( 0, pos );
+
+ OID oid = null;
+
+ try
+ {
+ oid = new OID( oidString );
+ }
+ catch (DecoderException de)
+ {
+ log.error( "The OID {} is not valid", oidString );
+ throw new NamingException( "Bad control oid" );
+ }
+
+ LdifControl control = new LdifControl( oid );
+
+ // Get the criticality, if any
+ // Skip the <fill>
+ while ( StringTools.isCharASCII( controlValue, pos, ' ' ) )
+ {
+ pos++;
+ }
+
+ // Check if we have a "true" or a "false"
+ int criticalPos = lowerLine.indexOf( ':' );
+
+ int criticalLength = 0;
+
+ if ( criticalPos == -1 )
+ {
+ criticalLength = length - pos;
+ }
+ else
+ {
+ criticalLength = criticalPos - pos;
+ }
+
+ if ( ( criticalLength == 4 ) && ( "true".equalsIgnoreCase( lowerLine.substring( pos, pos + 4 ) ) ) )
+ {
+ control.setCriticality( true );
+ }
+ else if ( ( criticalLength == 5 ) && ( "false".equalsIgnoreCase( lowerLine.substring( pos, pos + 5 ) ) ) )
+ {
+ control.setCriticality( false );
+ }
+ else if ( criticalLength != 0 )
+ {
+ // If we have a criticality, it should be either "true" or "false",
+ // nothing else
+ log.error( "The control muts have a valid criticality" );
+ throw new NamingException( "Bad control criticality" );
+ }
+
+ if ( criticalPos > 0 )
+ {
+ // We have a value. It can be a normal value, a base64 encoded value
+ // or a file contained value
+ if ( StringTools.isCharASCII( controlValue, criticalPos + 1, ':' ) )
+ {
+ // Base 64 encoded value
+ byte[] value = Base64.decode( line.substring( criticalPos + 2 ).toCharArray() );
+ control.setValue( value );
+ }
+ else if ( StringTools.isCharASCII( controlValue, criticalPos + 1, '<' ) )
+ {
+ // File contained value
+ }
+ else
+ {
+ // Standard value
+ byte[] value = new byte[length - criticalPos - 1];
+
+ for ( int i = 0; i < length - criticalPos - 1; i++ )
+ {
+ value[i] = (byte) controlValue[i + criticalPos + 1];
+ }
+
+ control.setValue( value );
+ }
+ }
+
+ return control;
+ }
+
+ /**
+ * Parse an AttributeType/AttributeValue
+ *
+ * @param attribute
+ * The create attribute
+ * @param line
+ * The line to parse
+ * @param lowerLine
+ * The same line, lowercased
+ */
+ public static Attribute parseAttributeValue( String line )
+ {
+ int colonIndex = line.indexOf( ':' );
+
+ if ( colonIndex != -1 )
+ {
+ String attributeType = line.toLowerCase().substring( 0, colonIndex );
+ Object attributeValue = parseSimpleValue( line, colonIndex );
+
+ // Create an attribute
+ return new BasicAttribute( attributeType, attributeValue );
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Parse an AttributeType/AttributeValue
+ *
+ * @param entry
+ * The entry where to store the value
+ * @param line
+ * The line to parse
+ * @param lowerLine
+ * The same line, lowercased
+ * @throws NamingException
+ * If anything goes wrong
+ */
+ public void parseAttributeValue( Entry entry, String line, String lowerLine ) throws NamingException
+ {
+ int colonIndex = line.indexOf( ':' );
+
+ String attributeType = lowerLine.substring( 0, colonIndex );
+
+ // We should *not* have a DN twice
+ if ( attributeType.equals( "dn" ) )
+ {
+ log.error( "An entry must not have two DNs" );
+ throw new NamingException( "A ldif entry should not have two DN" );
+ }
+
+ Object attributeValue = parseValue( line, colonIndex );
+
+ // Update the entry
+ entry.addAttribute( attributeType, attributeValue );
+ }
+
+ /**
+ * Parse a ModRDN operation
+ *
+ * @param entry
+ * The entry to update
+ * @param iter
+ * The lines iterator
+ * @throws NamingException
+ * If anything goes wrong
+ */
+ private void parseModRdn( Entry entry, Iterator iter ) throws NamingException
+ {
+ // We must have two lines : one starting with "newrdn:" or "newrdn::",
+ // and the second starting with "deleteoldrdn:"
+ if ( iter.hasNext() )
+ {
+ String line = (String) iter.next();
+ String lowerLine = line.toLowerCase();
+
+ if ( lowerLine.startsWith( "newrdn::" ) || lowerLine.startsWith( "newrdn:" ) )
+ {
+ int colonIndex = line.indexOf( ':' );
+ Object attributeValue = parseValue( line, colonIndex );
+ entry.setNewRdn( attributeValue instanceof String ? (String) attributeValue : StringTools
+ .utf8ToString( (byte[]) attributeValue ) );
+ }
+ else
+ {
+ log.error( "A modrdn operation must start with a \"newrdn:\"" );
+ throw new NamingException( "Bad modrdn operation" );
+ }
+
+ }
+ else
+ {
+ log.error( "A modrdn operation must start with a \"newrdn:\"" );
+ throw new NamingException( "Bad modrdn operation, no newrdn" );
+ }
+
+ if ( iter.hasNext() )
+ {
+ String line = (String) iter.next();
+ String lowerLine = line.toLowerCase();
+
+ if ( lowerLine.startsWith( "deleteoldrdn:" ) )
+ {
+ int colonIndex = line.indexOf( ':' );
+ Object attributeValue = parseValue( line, colonIndex );
+ entry.setDeleteOldRdn( "1".equals( attributeValue ) );
+ }
+ else
+ {
+ log.error( "A modrdn operation must contains a \"deleteoldrdn:\"" );
+ throw new NamingException( "Bad modrdn operation, no deleteoldrdn" );
+ }
+ }
+ else
+ {
+ log.error( "A modrdn operation must contains a \"deleteoldrdn:\"" );
+ throw new NamingException( "Bad modrdn operation, no deleteoldrdn" );
+ }
+
+ return;
+ }
+
+ /**
+ * Parse a modify change type.
+ *
+ * The grammar is : <changerecord> ::= "changetype:" FILL "modify" SEP
+ * <mod-spec> <mod-specs-e> <mod-spec> ::= "add:" <mod-val> | "delete:"
+ * <mod-val-del> | "replace:" <mod-val> <mod-specs-e> ::= <mod-spec>
+ * <mod-specs-e> | e <mod-val> ::= FILL ATTRIBUTE-DESCRIPTION SEP
+ * ATTRVAL-SPEC <attrval-specs-e> "-" SEP <mod-val-del> ::= FILL
+ * ATTRIBUTE-DESCRIPTION SEP <attrval-specs-e> "-" SEP <attrval-specs-e> ::=
+ * ATTRVAL-SPEC <attrval-specs> | e *
+ *
+ * @param entry
+ * The entry to feed
+ * @param iter
+ * The lines
+ */
+ private void parseModify( Entry entry, Iterator iter ) throws NamingException
+ {
+ int state = MOD_SPEC;
+ String modified = null;
+ int modification = 0;
+
+ // The following flag is used to deal with empty modifications
+ boolean isEmptyValue = true;
+
+ while ( iter.hasNext() )
+ {
+ String line = (String) iter.next();
+ String lowerLine = line.toLowerCase();
+
+ if ( lowerLine.startsWith( "-" ) )
+ {
+ if ( state != ATTRVAL_SPEC_OR_SEP )
+ {
+ log.error( "Bad state : we should have come from an ATTRVAL_SPEC" );
+ throw new NamingException( "Bad modify separator" );
+ }
+ else
+ {
+ if ( isEmptyValue )
+ {
+ // Update the entry
+ entry.addModificationItem( modification, modified, null );
+ }
+
+ state = MOD_SPEC;
+ isEmptyValue = true;
+ continue;
+ }
+ }
+ else if ( lowerLine.startsWith( "add:" ) )
+ {
+ if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) )
+ {
+ log.error( "Bad state : we should have come from a MOD_SPEC or an ATTRVAL_SPEC" );
+ throw new NamingException( "Bad modify state" );
+ }
+
+ modified = StringTools.trim( line.substring( "add:".length() ) );
+ modification = DirContext.ADD_ATTRIBUTE;
+
+ state = ATTRVAL_SPEC;
+ }
+ else if ( lowerLine.startsWith( "delete:" ) )
+ {
+ if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) )
+ {
+ log.error( "Bad state : we should have come from a MOD_SPEC or an ATTRVAL_SPEC" );
+ throw new NamingException( "Bad modify state" );
+ }
+
+ modified = StringTools.trim( line.substring( "delete:".length() ) );
+ modification = DirContext.REMOVE_ATTRIBUTE;
+
+ state = ATTRVAL_SPEC_OR_SEP;
+ }
+ else if ( lowerLine.startsWith( "replace:" ) )
+ {
+ if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) )
+ {
+ log.error( "Bad state : we should have come from a MOD_SPEC or an ATTRVAL_SPEC" );
+ throw new NamingException( "Bad modify state" );
+ }
+
+ modified = StringTools.trim( line.substring( "replace:".length() ) );
+ modification = DirContext.REPLACE_ATTRIBUTE;
+
+ state = ATTRVAL_SPEC_OR_SEP;
+ }
+ else
+ {
+ if ( ( state != ATTRVAL_SPEC ) && ( state != ATTRVAL_SPEC_OR_SEP ) )
+ {
+ log.error( "Bad state : we should have come from an ATTRVAL_SPEC" );
+ throw new NamingException( "Bad modify state" );
+ }
+
+ // A standard AttributeType/AttributeValue pair
+ int colonIndex = line.indexOf( ':' );
+
+ String attributeType = lowerLine.substring( 0, colonIndex );
+
+ if ( attributeType.equals( modified ) == false )
+ {
+ log.error( "The modified attribute and the attribute value spec must be equal" );
+ throw new NamingException( "Bad modify attribute" );
+ }
+
+ // We should *not* have a DN twice
+ if ( attributeType.equals( "dn" ) )
+ {
+ log.error( "An entry must not have two DNs" );
+ throw new NamingException( "A ldif entry should not have two DN" );
+ }
+
+ Object attributeValue = parseValue( line, colonIndex );
+
+ // Update the entry
+ entry.addModificationItem( modification, attributeType, attributeValue );
+ isEmptyValue = false;
+
+ state = ATTRVAL_SPEC_OR_SEP;
+ }
+ }
+ }
+
+ /**
+ * Parse a change operation. We have to handle different cases depending on
+ * the operation. 1) Delete : there should *not* be any line after the
+ * "changetype: delete" 2) Add : we must have a list of AttributeType :
+ * AttributeValue elements 3) ModDN : we must have two following lines: a
+ * "newrdn:" and a "deleteoldrdn:" 4) ModRDN : the very same, but a
+ * "newsuperior:" line is expected 5) Modify :
+ *
+ * The grammar is : <changerecord> ::= "changetype:" FILL "add" SEP
+ * <attrval-spec> <attrval-specs-e> | "changetype:" FILL "delete" |
+ * "changetype:" FILL "modrdn" SEP <newrdn> SEP <deleteoldrdn> SEP | // To
+ * be checked "changetype:" FILL "moddn" SEP <newrdn> SEP <deleteoldrdn> SEP
+ * <newsuperior> SEP | "changetype:" FILL "modify" SEP <mod-spec>
+ * <mod-specs-e> <newrdn> ::= "newrdn:" FILL RDN | "newrdn::" FILL
+ * BASE64-RDN <deleteoldrdn> ::= "deleteoldrdn:" FILL "0" | "deleteoldrdn:"
+ * FILL "1" <newsuperior> ::= "newsuperior:" FILL DN | "newsuperior::" FILL
+ * BASE64-DN <mod-specs-e> ::= <mod-spec> <mod-specs-e> | e <mod-spec> ::=
+ * "add:" <mod-val> | "delete:" <mod-val> | "replace:" <mod-val> <mod-val>
+ * ::= FILL ATTRIBUTE-DESCRIPTION SEP ATTRVAL-SPEC <attrval-specs-e> "-" SEP
+ * <attrval-specs-e> ::= ATTRVAL-SPEC <attrval-specs> | e
+ *
+ * @param entry
+ * The entry to feed
+ * @param iter
+ * The lines iterator
+ * @param operation
+ * The change operation (add, modify, delete, moddn or modrdn)
+ * @param control
+ * The associated control, if any
+ * @return A modification entry
+ */
+ private void parseChange( Entry entry, Iterator iter, int operation, Control control ) throws NamingException
+ {
+ // The changetype and operation has already been parsed.
+ entry.setChangeType( operation );
+
+ switch ( operation )
+ {
+ case Entry.DELETE:
+ // The change type will tell that it's a delete operation,
+ // the dn is used as a key.
+ return;
+
+ case Entry.ADD:
+ // We will iterate through all attribute/value pairs
+ while ( iter.hasNext() )
+ {
+ String line = (String) iter.next();
+ String lowerLine = line.toLowerCase();
+ parseAttributeValue( entry, line, lowerLine );
+ }
+
+ return;
+
+ case Entry.MODIFY:
+ parseModify( entry, iter );
+ return;
+
+ case Entry.MODRDN:// They are supposed to have the same syntax ???
+ case Entry.MODDN:
+ // First, parse the modrdn part
+ parseModRdn( entry, iter );
+
+ // The next line should be the new superior
+ if ( iter.hasNext() )
+ {
+ String line = (String) iter.next();
+ String lowerLine = line.toLowerCase();
+
+ if ( lowerLine.startsWith( "newsuperior:" ) )
+ {
+ int colonIndex = line.indexOf( ':' );
+ Object attributeValue = parseValue( line, colonIndex );
+ entry.setNewSuperior( attributeValue instanceof String ? (String) attributeValue : StringTools
+ .utf8ToString( (byte[]) attributeValue ) );
+ }
+ else
+ {
+ if ( operation == Entry.MODDN )
+ {
+ log.error( "A moddn operation must contains a \"newsuperior:\"" );
+ throw new NamingException( "Bad moddn operation, no newsuperior" );
+ }
+ }
+ }
+ else
+ {
+ if ( operation == Entry.MODDN )
+ {
+ log.error( "A moddn operation must contains a \"newsuperior:\"" );
+ throw new NamingException( "Bad moddn operation, no newsuperior" );
+ }
+ }
+
+ return;
+
+ default:
+ // This is an error
+ log.error( "Unknown operation" );
+ throw new NamingException( "Bad operation" );
+ }
+ }
+
+ /**
+ * Parse a ldif file. The following rules are processed :
+ *
+ * <ldif-file> ::= <ldif-attrval-record> <ldif-attrval-records> |
+ * <ldif-change-record> <ldif-change-records> <ldif-attrval-record> ::=
+ * <dn-spec> <sep> <attrval-spec> <attrval-specs> <ldif-change-record> ::=
+ * <dn-spec> <sep> <controls-e> <changerecord> <dn-spec> ::= "dn:" <fill>
+ * <distinguishedName> | "dn::" <fill> <base64-distinguishedName>
+ * <changerecord> ::= "changetype:" <fill> <change-op>
+ */
+ private Entry parseEntry() throws NamingException
+ {
+ if ( ( lines == null ) || ( lines.size() == 0 ) )
+ {
+ log.debug( "The entry is empty : end of ldif file" );
+ return null;
+ }
+
+ // The entry must start with a dn: or a dn::
+ String line = ( (String) lines.get( 0 ) );
+
+ String dn = parseDn( line );
+
+ // Ok, we have found a DN
+ Entry entry = new Entry();
+ entry.setDn( dn );
+
+ // We remove this dn from the lines
+ lines.remove( 0 );
+
+ // Now, let's iterate through the other lines
+ Iterator iter = lines.iterator();
+
+ // This flag is used to distinguish between an entry and a change
+ int type = UNKNOWN;
+
+ // The following boolean is used to check that a control is *not*
+ // found elswhere than just after the dn
+ boolean controlSeen = false;
+
+ // We use this boolean to check that we do not have AttributeValues
+ // after a change operation
+ boolean changeTypeSeen = false;
+
+ int operation = Entry.ADD;
+ String lowerLine = null;
+ Control control = null;
+
+ while ( iter.hasNext() )
+ {
+ // Each line could start either with an OID, an attribute type, with
+ // "control:" or with "changetype:"
+ line = (String) iter.next();
+ lowerLine = line.toLowerCase();
+
+ // We have three cases :
+ // 1) The first line after the DN is a "control:"
+ // 2) The first line after the DN is a "changeType:"
+ // 3) The first line after the DN is anything else
+ if ( lowerLine.startsWith( "control:" ) )
+ {
+ if ( containsEntries )
+ {
+ log.error( "We cannot have changes when reading a file which already contains entries" );
+ throw new NamingException( "No changes withing entries" );
+ }
+
+ containsChanges = true;
+
+ if ( controlSeen )
+ {
+ log.error( "We already have had a control" );
+ throw new NamingException( "Control misplaced" );
+ }
+
+ // Parse the control
+ control = parseControl( line.substring( "control:".length() ) );
+ entry.setControl( control );
+
+ }
+ else if ( lowerLine.startsWith( "changetype:" ) )
+ {
+ if ( containsEntries )
+ {
+ log.error( "We cannot have changes when reading a file which already contains entries" );
+ throw new NamingException( "No changes withing entries" );
+ }
+
+ containsChanges = true;
+
+ if ( changeTypeSeen )
+ {
+ log.error( "We already have had a changeType" );
+ throw new NamingException( "ChangeType misplaced" );
+ }
+
+ // A change request
+ type = CHANGE;
+ controlSeen = true;
+
+ operation = parseChangeType( line );
+
+ // Parse the change operation in a separate function
+ parseChange( entry, iter, operation, control );
+ changeTypeSeen = true;
+ }
+ else if ( line.indexOf( ':' ) > 0 )
+ {
+ if ( containsChanges )
+ {
+ log.error( "We cannot have entries when reading a file which already contains changes" );
+ throw new NamingException( "No entries within changes" );
+ }
+
+ containsEntries = true;
+
+ if ( controlSeen || changeTypeSeen )
+ {
+ log.error( "We can't have a Attribute/Value pair after a control or a changeType" );
+ throw new NamingException( "AttributeType misplaced" );
+ }
+
+ parseAttributeValue( entry, line, lowerLine );
+ type = ENTRY;
+ }
+ else
+ {
+ // Invalid attribute Value
+ log.error( "Expecting an attribute type" );
+ throw new NamingException( "Bad attribute" );
+ }
+ }
+
+ if ( type == ENTRY )
+ {
+ log.debug( "Read an entry : {}", entry );
+ }
+ else if ( type == CHANGE )
+ {
+ entry.setChangeType( operation );
+ log.debug( "Read a modification : {}", entry );
+ }
+ else
+ {
+ log.error( "Unknown entry type" );
+ throw new NamingException( "Unknown entry" );
+ }
+
+ return entry;
+ }
+
+ /**
+ * Parse the version from the ldif input.
+ *
+ * @param in
+ * The input which contains the ldif data
+ * @return A number representing the version (default to 1)
+ * @throws NamingException
+ * If the version is incorrect
+ * @throws IOException
+ * If the input is incorrect
+ */
+ private int parseVersion() throws NamingException
+ {
+ int version = DEFAULT_VERSION;
+
+ // First, read a list of lines
+ readLines();
+
+ if ( lines.size() == 0 )
+ {
+ log.warn( "The ldif file is empty" );
+ return version;
+ }
+
+ // get the first line
+ String line = (String) lines.get( 0 );
+
+ // <ldif-file> ::= "version:" <fill> <number>
+ char[] document = line.toCharArray();
+ String versionNumber = null;
+
+ if ( line.startsWith( "version:" ) )
+ {
+ position.inc( "version:".length() );
+ parseFill( document, position );
+
+ // Version number. Must be '1' in this version
+ versionNumber = parseNumber( document, position );
+
+ // We should not have any other chars after the number
+ if ( position.pos != document.length )
+ {
+ log.error( "The version is not a number" );
+ throw new NamingException( "Ldif parsing error" );
+ }
+
+ try
+ {
+ version = Integer.parseInt( versionNumber );
+ }
+ catch (NumberFormatException nfe)
+ {
+ log.error( "The version is not a number" );
+ throw new NamingException( "Ldif parsing error" );
+ }
+
+ log.debug( "Ldif version : {}", versionNumber );
+
+ // We have found the version, just discard the line from the list
+ lines.remove( 0 );
+ }
+ else
+ {
+ log.warn( "No version information : assuming version: 1" );
+ }
+
return version;
- }
-
- /**
- * Reads an entry in a ldif buffer, and returns the resulting lines,
- * without comments, and unfolded.
- *
- * The lines represent *one* entry.
- *
- * @param in The buffer
- * @throws NamingException If something went wrong
- */
- private void readLines() throws NamingException
- {
- String line = null;
- boolean insideComment = true;
- boolean isFirstLine = true;
-
- lines.clear();
- StringBuffer sb = new StringBuffer();
-
- try
- {
- while ( ( line = ((BufferedReader)in).readLine() ) != null )
- {
- if ( line.length() == 0 )
- {
- if ( isFirstLine )
- {
- continue;
- }
- else
- {
- // The line is empty, we have read an entry
- insideComment = false;
- break;
- }
- }
-
- isFirstLine = false;
-
- // We will read the first line which is not a comment
- switch ( line.charAt( 0 ) )
- {
- case '#' :
- insideComment = true;
- break;
-
- case ' ' :
- if ( insideComment )
- {
- continue;
- }
- else if ( sb.length() == 0 )
- {
- log.error( "Cannot have an empty continuation line" );
- throw new NamingException( "Ldif Parsing error" );
- }
- else
- {
- sb.append( line.substring( 1 ) );
- }
-
- insideComment = false;
- break;
-
- default :
- // We have found a new entry
- // First, stores the previous one if any.
- if ( sb.length() != 0 )
- {
- lines.add( sb.toString() );
- }
-
- sb = new StringBuffer( line );
- insideComment = false;
- break;
- }
- }
- }
- catch ( IOException ioe )
- {
- throw new NamingException( "Error while reading ldif lines" );
- }
-
- // Stores the current line if necessary.
- if ( sb.length() != 0 )
- {
- lines.add( sb.toString() );
- }
-
- return;
- }
-
- /**
+ }
+
+ /**
+ * Reads an entry in a ldif buffer, and returns the resulting lines, without
+ * comments, and unfolded.
+ *
+ * The lines represent *one* entry.
+ *
+ * @param in
+ * The buffer
+ * @throws NamingException
+ * If something went wrong
+ */
+ private void readLines() throws NamingException
+ {
+ String line = null;
+ boolean insideComment = true;
+ boolean isFirstLine = true;
+
+ lines.clear();
+ StringBuffer sb = new StringBuffer();
+
+ try
+ {
+ while ( ( line = ( (BufferedReader) in ).readLine() ) != null )
+ {
+ if ( line.length() == 0 )
+ {
+ if ( isFirstLine )
+ {
+ continue;
+ }
+ else
+ {
+ // The line is empty, we have read an entry
+ insideComment = false;
+ break;
+ }
+ }
+
+ isFirstLine = false;
+
+ // We will read the first line which is not a comment
+ switch ( line.charAt( 0 ) )
+ {
+ case '#':
+ insideComment = true;
+ break;
+
+ case ' ':
+ if ( insideComment )
+ {
+ continue;
+ }
+ else if ( sb.length() == 0 )
+ {
+ log.error( "Cannot have an empty continuation line" );
+ throw new NamingException( "Ldif Parsing error" );
+ }
+ else
+ {
+ sb.append( line.substring( 1 ) );
+ }
+
+ insideComment = false;
+ break;
+
+ default:
+ // We have found a new entry
+ // First, stores the previous one if any.
+ if ( sb.length() != 0 )
+ {
+ lines.add( sb.toString() );
+ }
+
+ sb = new StringBuffer( line );
+ insideComment = false;
+ break;
+ }
+ }
+ }
+ catch (IOException ioe)
+ {
+ throw new NamingException( "Error while reading ldif lines" );
+ }
+
+ // Stores the current line if necessary.
+ if ( sb.length() != 0 )
+ {
+ lines.add( sb.toString() );
+ }
+
+ return;
+ }
+
+ /**
* Parse a ldif file (using the default encoding).
*
- * @param fileName The ldif file
- * @return A list of entries
- * @throws NamingException If the parsing fails
- */
- public List parseLdifFile( String fileName ) throws NamingException
- {
- return parseLdifFile( fileName, Charset.defaultCharset().toString() );
- }
-
- /**
- * Parse a ldif file, decoding it using the given charset encoding
- *
- * @param fileName The ldif file
- * @param encoding The charset encoding to use
- * @return A list of entries
- * @throws NamingException If the parsing fails
- */
- public List parseLdifFile( String fileName, String encoding ) throws NamingException
- {
- if (StringTools.isEmpty(fileName))
- {
- log.error("Cannot parse an empty file name !");
- throw new NamingException( "Empty filename" );
- }
-
- File file = new File(fileName);
-
- if ( file.exists() == false )
- {
- log.error("Cannot parse the file {}, it does not exist", fileName);
- throw new NamingException( "Filename " + fileName + " not found." );
- }
-
- // Open the file and then get a channel from the stream
- BufferedReader in;
-
- try
- {
- in = new BufferedReader(
- new InputStreamReader(
- new FileInputStream( file ), Charset.forName( encoding ) ) );
- }
- catch (FileNotFoundException fnfe)
- {
- log.error("Cannot find file {}", fileName);
- throw new NamingException( "Filename " + fileName + " not found." );
- }
+ * @param fileName
+ * The ldif file
+ * @return A list of entries
+ * @throws NamingException
+ * If the parsing fails
+ */
+ public List parseLdifFile( String fileName ) throws NamingException
+ {
+ return parseLdifFile( fileName, Charset.forName( StringTools.getDefaultCharsetName() ).toString() );
+ }
+
+ /**
+ * Parse a ldif file, decoding it using the given charset encoding
+ *
+ * @param fileName
+ * The ldif file
+ * @param encoding
+ * The charset encoding to use
+ * @return A list of entries
+ * @throws NamingException
+ * If the parsing fails
+ */
+ public List parseLdifFile( String fileName, String encoding ) throws NamingException
+ {
+ if ( StringTools.isEmpty( fileName ) )
+ {
+ log.error( "Cannot parse an empty file name !" );
+ throw new NamingException( "Empty filename" );
+ }
+
+ File file = new File( fileName );
+
+ if ( file.exists() == false )
+ {
+ log.error( "Cannot parse the file {}, it does not exist", fileName );
+ throw new NamingException( "Filename " + fileName + " not found." );
+ }
+
+ // Open the file and then get a channel from the stream
+ BufferedReader in;
+
+ try
+ {
+ in = new BufferedReader( new InputStreamReader( new FileInputStream( file ), Charset.forName( encoding ) ) );
+ }
+ catch (FileNotFoundException fnfe)
+ {
+ log.error( "Cannot find file {}", fileName );
+ throw new NamingException( "Filename " + fileName + " not found." );
+ }
return parseLdif( in );
- }
+ }
+
+ /**
+ * A method which parses a ldif string and returns a list of entries.
+ *
+ * @param ldif
+ * The ldif string
+ * @return A list of entries, or an empty List
+ * @throws NamingException
+ * If something went wrong
+ */
+ public List parseLdif( String ldif ) throws NamingException
+ {
+ log.debug( "Starts parsing ldif buffer" );
+
+ if ( StringTools.isEmpty( ldif ) )
+ {
+ return new ArrayList();
+ }
- /**
- * A method which parses a ldif string and returns a list of entries.
- *
- * @param ldif The ldif string
- * @return A list of entries
- * @throws NamingException If something went wrong
- */
- public List parseLdif( String ldif ) throws NamingException
- {
[... 208 lines stripped ...]