| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.directory.api.ldap.model.ldif; |
| |
| |
| import java.io.BufferedReader; |
| import java.io.Closeable; |
| import java.io.DataInputStream; |
| 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; |
| import java.io.UnsupportedEncodingException; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.nio.charset.Charset; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.NoSuchElementException; |
| |
| import org.apache.directory.api.asn1.util.Oid; |
| import org.apache.directory.api.i18n.I18n; |
| import org.apache.directory.api.ldap.model.constants.SchemaConstants; |
| import org.apache.directory.api.ldap.model.entry.Attribute; |
| import org.apache.directory.api.ldap.model.entry.DefaultAttribute; |
| import org.apache.directory.api.ldap.model.entry.ModificationOperation; |
| import org.apache.directory.api.ldap.model.exception.LdapException; |
| import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; |
| import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException; |
| import org.apache.directory.api.ldap.model.message.Control; |
| import org.apache.directory.api.ldap.model.name.Ava; |
| import org.apache.directory.api.ldap.model.name.Dn; |
| import org.apache.directory.api.ldap.model.name.Rdn; |
| import org.apache.directory.api.ldap.model.schema.AttributeType; |
| import org.apache.directory.api.ldap.model.schema.MutableAttributeType; |
| import org.apache.directory.api.ldap.model.schema.SchemaManager; |
| import org.apache.directory.api.util.Base64; |
| import org.apache.directory.api.util.Chars; |
| import org.apache.directory.api.util.Strings; |
| import org.apache.directory.api.util.exception.NotImplementedException; |
| import org.slf4j.Logger; |
| 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> |
| * The relaxed mode is used when a SchemaManager is injected. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public class LdifReader implements Iterable<LdifEntry>, Closeable |
| { |
| /** A logger */ |
| private static final Logger LOG = LoggerFactory.getLogger( LdifReader.class ); |
| |
| /** A list of read lines */ |
| protected List<String> lines; |
| |
| /** The current position */ |
| protected int position; |
| |
| /** The ldif file version default value */ |
| protected static final int DEFAULT_VERSION = 1; |
| |
| /** The ldif version */ |
| protected int version; |
| |
| /** Type of element read : ENTRY */ |
| protected static final int LDIF_ENTRY = 0; |
| |
| /** Type of element read : CHANGE */ |
| protected static final int CHANGE = 1; |
| |
| /** Type of element read : UNKNOWN */ |
| protected static final int UNKNOWN = 2; |
| |
| /** Size limit for file contained values */ |
| protected long sizeLimit = SIZE_LIMIT_DEFAULT; |
| |
| /** The default size limit : 1Mo */ |
| protected static final long SIZE_LIMIT_DEFAULT = 1024000; |
| |
| /** State values for the modify operation : MOD_SPEC */ |
| protected static final int MOD_SPEC = 0; |
| |
| /** State values for the modify operation : ATTRVAL_SPEC */ |
| protected static final int ATTRVAL_SPEC = 1; |
| |
| /** State values for the modify operation : ATTRVAL_SPEC_OR_SEP */ |
| protected static final int ATTRVAL_SPEC_OR_SEP = 2; |
| |
| /** Iterator prefetched entry */ |
| protected LdifEntry prefetched; |
| |
| /** The ldif Reader */ |
| protected Reader reader; |
| |
| /** A flag set if the ldif contains entries */ |
| protected boolean containsEntries; |
| |
| /** A flag set if the ldif contains changes */ |
| protected boolean containsChanges; |
| |
| /** The SchemaManager instance, if any */ |
| protected SchemaManager schemaManager; |
| |
| /** |
| * An Exception to handle error message, has Iterator.next() can't throw |
| * exceptions |
| */ |
| protected Exception error; |
| |
| /** total length of an LDIF entry including the comments */ |
| protected int entryLen = 0; |
| |
| /** the parsed entry's starting position */ |
| protected long entryOffset = 0; |
| |
| /** the current offset of the reader */ |
| protected long offset = 0; |
| |
| /** the numer of the current line being parsed by the reader */ |
| protected int lineNumber; |
| |
| /** flag to turn on/off of the DN validation. By default DNs are validated after parsing */ |
| protected boolean validateDn = true; |
| |
| /** A counter used to create facked OIDs */ |
| private int oidCounter = 0; |
| |
| |
| /** |
| * Constructors |
| */ |
| public LdifReader() |
| { |
| lines = new ArrayList<String>(); |
| position = 0; |
| version = DEFAULT_VERSION; |
| } |
| |
| |
| /** |
| * Constructors |
| */ |
| public LdifReader( SchemaManager schemaManager ) |
| { |
| lines = new ArrayList<String>(); |
| position = 0; |
| version = DEFAULT_VERSION; |
| this.schemaManager = schemaManager; |
| } |
| |
| |
| /** |
| * Store the reader and intialize the LdifReader |
| */ |
| private void initReader( BufferedReader reader ) throws LdapException |
| { |
| this.reader = reader; |
| init(); |
| } |
| |
| |
| /** |
| * Initialize the LdifReader |
| * |
| * @throws LdapException If the initialization failed |
| */ |
| public void init() throws LdapException |
| { |
| lines = new ArrayList<String>(); |
| position = 0; |
| 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 ldifFileName A file name containing ldif formated input |
| * @throws LdapLdifException If the file cannot be processed or if the format is incorrect |
| */ |
| public LdifReader( String ldifFileName ) throws LdapLdifException |
| { |
| File file = new File( ldifFileName ); |
| |
| if ( !file.exists() ) |
| { |
| String msg = I18n.err( I18n.ERR_12010_CANNOT_FIND_FILE, file.getAbsoluteFile() ); |
| LOG.error( msg ); |
| throw new LdapLdifException( msg ); |
| } |
| |
| if ( !file.canRead() ) |
| { |
| String msg = I18n.err( I18n.ERR_12011_CANNOT_READ_FILE, file.getName() ); |
| LOG.error( msg ); |
| throw new LdapLdifException( msg ); |
| } |
| |
| try |
| { |
| initReader( new BufferedReader( new FileReader( file ) ) ); |
| } |
| catch ( FileNotFoundException fnfe ) |
| { |
| String msg = I18n.err( I18n.ERR_12010_CANNOT_FIND_FILE, file.getAbsoluteFile() ); |
| LOG.error( msg ); |
| throw new LdapLdifException( msg, fnfe ); |
| } |
| catch ( LdapInvalidDnException lide ) |
| { |
| throw new LdapLdifException( lide.getMessage(), lide ); |
| } |
| catch ( LdapException le ) |
| { |
| throw new LdapLdifException( le.getMessage(), le ); |
| } |
| } |
| |
| |
| /** |
| * A constructor which takes a Reader |
| * |
| * @param in A Reader containing ldif formated input |
| * @throws LdapException If the file cannot be processed or if the format is incorrect |
| */ |
| public LdifReader( Reader in ) throws LdapException |
| { |
| initReader( new BufferedReader( in ) ); |
| } |
| |
| |
| /** |
| * A constructor which takes an InputStream |
| * |
| * @param in An InputStream containing ldif formated input |
| * @throws LdapException If the file cannot be processed or if the format is incorrect |
| */ |
| public LdifReader( InputStream in ) throws LdapException |
| { |
| initReader( new BufferedReader( new InputStreamReader( in ) ) ); |
| } |
| |
| |
| /** |
| * A constructor which takes a File |
| * |
| * @param file A File containing ldif formated input |
| * @throws LdapLdifException If the file cannot be processed or if the format is incorrect |
| */ |
| public LdifReader( File file ) throws LdapLdifException |
| { |
| if ( !file.exists() ) |
| { |
| String msg = I18n.err( I18n.ERR_12010_CANNOT_FIND_FILE, file.getAbsoluteFile() ); |
| LOG.error( msg ); |
| throw new LdapLdifException( msg ); |
| } |
| |
| if ( !file.canRead() ) |
| { |
| String msg = I18n.err( I18n.ERR_12011_CANNOT_READ_FILE, file.getName() ); |
| LOG.error( msg ); |
| throw new LdapLdifException( msg ); |
| } |
| |
| try |
| { |
| initReader( new BufferedReader( new FileReader( file ) ) ); |
| } |
| catch ( FileNotFoundException fnfe ) |
| { |
| String msg = I18n.err( I18n.ERR_12010_CANNOT_FIND_FILE, file.getAbsoluteFile() ); |
| LOG.error( msg ); |
| throw new LdapLdifException( msg, fnfe ); |
| } |
| catch ( LdapInvalidDnException lide ) |
| { |
| throw new LdapLdifException( lide.getMessage(), lide ); |
| } |
| catch ( LdapException le ) |
| { |
| throw new LdapLdifException( le.getMessage(), le ); |
| } |
| } |
| |
| |
| /** |
| * A constructor which takes a File and a SchemaManager |
| * |
| * @param file A File containing ldif formated input |
| * @param schemaManager The SchemaManager instance to use |
| * @throws LdapLdifException If the file cannot be processed or if the format is incorrect |
| */ |
| public LdifReader( File file, SchemaManager schemaManager ) throws LdapLdifException |
| { |
| if ( !file.exists() ) |
| { |
| String msg = I18n.err( I18n.ERR_12010_CANNOT_FIND_FILE, file.getAbsoluteFile() ); |
| LOG.error( msg ); |
| throw new LdapLdifException( msg ); |
| } |
| |
| if ( !file.canRead() ) |
| { |
| String msg = I18n.err( I18n.ERR_12011_CANNOT_READ_FILE, file.getName() ); |
| LOG.error( msg ); |
| throw new LdapLdifException( msg ); |
| } |
| |
| this.schemaManager = schemaManager; |
| |
| try |
| { |
| initReader( new BufferedReader( new FileReader( file ) ) ); |
| } |
| catch ( FileNotFoundException fnfe ) |
| { |
| String msg = I18n.err( I18n.ERR_12010_CANNOT_FIND_FILE, file.getAbsoluteFile() ); |
| LOG.error( msg ); |
| throw new LdapLdifException( msg, fnfe ); |
| } |
| catch ( LdapInvalidDnException lide ) |
| { |
| throw new LdapLdifException( lide.getMessage(), lide ); |
| } |
| catch ( LdapException le ) |
| { |
| throw new LdapLdifException( le.getMessage(), le ); |
| } |
| } |
| |
| |
| /** |
| * @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> | e |
| private void parseFill( char[] document ) |
| { |
| while ( Chars.isCharASCII( document, position, ' ' ) ) |
| { |
| position++; |
| } |
| } |
| |
| |
| /** |
| * Parse a number following the rules : |
| * |
| * <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 |
| * |
| * @param document The document containing the number to parse |
| * @return a String representing the parsed number |
| */ |
| private String parseNumber( char[] document ) |
| { |
| int initPos = position; |
| |
| while ( true ) |
| { |
| if ( Chars.isDigit( document, position ) ) |
| { |
| position++; |
| } |
| else |
| { |
| break; |
| } |
| } |
| |
| if ( position == initPos ) |
| { |
| return null; |
| } |
| else |
| { |
| return new String( document, initPos, position - initPos ); |
| } |
| } |
| |
| |
| /** |
| * Parse the changeType |
| * |
| * @param line The line which contains the changeType |
| * @return The operation. |
| */ |
| protected ChangeType parseChangeType( String line ) |
| { |
| ChangeType operation = ChangeType.Add; |
| |
| String modOp = Strings.trim( line.substring( "changetype:".length() ) ); |
| |
| if ( "add".equalsIgnoreCase( modOp ) ) |
| { |
| operation = ChangeType.Add; |
| } |
| else if ( "delete".equalsIgnoreCase( modOp ) ) |
| { |
| operation = ChangeType.Delete; |
| } |
| else if ( "modify".equalsIgnoreCase( modOp ) ) |
| { |
| operation = ChangeType.Modify; |
| } |
| else if ( "moddn".equalsIgnoreCase( modOp ) ) |
| { |
| operation = ChangeType.ModDn; |
| } |
| else if ( "modrdn".equalsIgnoreCase( modOp ) ) |
| { |
| operation = ChangeType.ModRdn; |
| } |
| |
| return operation; |
| } |
| |
| |
| /** |
| * Parse the Dn of an entry |
| * |
| * @param line The line to parse |
| * @return A Dn |
| * @throws LdapLdifException If the Dn is invalid |
| */ |
| protected String parseDn( String line ) throws LdapLdifException |
| { |
| String dn; |
| |
| String lowerLine = Strings.toLowerCaseAscii( line ); |
| |
| 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 : it's a rootDSE |
| dn = ""; |
| } |
| 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( I18n.err( I18n.ERR_12014_BASE64_DN_EXPECTED, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12015_INVALID_BASE64_DN ), uee ); |
| } |
| } |
| else |
| { |
| // The Dn is empty : error |
| LOG.error( I18n.err( I18n.ERR_12012_EMPTY_DN_NOT_ALLOWED, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12013_NO_DN ) ); |
| } |
| } |
| else |
| { |
| dn = line.substring( 3 ).trim(); |
| } |
| } |
| else |
| { |
| LOG.error( I18n.err( I18n.ERR_12016_DN_EXPECTED, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12013_NO_DN ) ); |
| } |
| |
| // Check that the Dn is valid. If not, an exception will be thrown |
| if ( validateDn && !Dn.isValid( dn ) ) |
| { |
| String message = I18n.err( I18n.ERR_12017_INVALID_DN, dn, lineNumber ); |
| LOG.error( message ); |
| throw new LdapLdifException( message ); |
| } |
| |
| 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 |
| */ |
| protected static Object parseSimpleValue( String line, int pos ) |
| { |
| if ( line.length() > pos + 1 ) |
| { |
| char c = line.charAt( pos + 1 ); |
| |
| if ( c == ':' ) |
| { |
| String value = Strings.trim( line.substring( pos + 2 ) ); |
| |
| return Base64.decode( value.toCharArray() ); |
| } |
| else |
| { |
| return Strings.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 LdapLdifException If something went wrong |
| */ |
| protected Object parseValue( String line, int pos ) throws LdapLdifException |
| { |
| if ( line.length() > pos + 1 ) |
| { |
| char c = line.charAt( pos + 1 ); |
| |
| if ( c == ':' ) |
| { |
| String value = Strings.trim( line.substring( pos + 2 ) ); |
| |
| return Base64.decode( value.toCharArray() ); |
| } |
| else if ( c == '<' ) |
| { |
| String urlName = Strings.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() ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12018_FILE_NOT_FOUND, fileName, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12019_BAD_URL_FILE_NOT_FOUND ) ); |
| } |
| else |
| { |
| long length = file.length(); |
| |
| if ( length > sizeLimit ) |
| { |
| String message = I18n.err( I18n.ERR_12020_FILE_TOO_BIG, fileName, lineNumber ); |
| LOG.error( message ); |
| throw new LdapLdifException( message ); |
| } |
| else |
| { |
| byte[] data = new byte[( int ) length]; |
| DataInputStream inf = null; |
| |
| try |
| { |
| inf = new DataInputStream( new FileInputStream( file ) ); |
| inf.readFully( data ); |
| |
| return data; |
| } |
| catch ( FileNotFoundException fnfe ) |
| { |
| // We can't reach this point, the file |
| // existence has already been |
| // checked |
| LOG.error( I18n.err( I18n.ERR_12018_FILE_NOT_FOUND, fileName, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12019_BAD_URL_FILE_NOT_FOUND ), |
| fnfe ); |
| } |
| catch ( IOException ioe ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12022_ERROR_READING_FILE, fileName, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12023_ERROR_READING_BAD_URL ), ioe ); |
| } |
| finally |
| { |
| try |
| { |
| if ( inf != null ) |
| { |
| inf.close(); |
| } |
| } |
| catch ( IOException ioe ) |
| { |
| LOG.error( |
| I18n.err( I18n.ERR_12024_CANNOT_CLOSE_FILE, ioe.getMessage(), lineNumber ), |
| ioe ); |
| // Just do nothing ... |
| } |
| } |
| } |
| } |
| } |
| else |
| { |
| LOG.error( I18n.err( I18n.ERR_12025_BAD_PROTOCOL ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12026_UNSUPPORTED_PROTOCOL, lineNumber ) ); |
| } |
| } |
| catch ( MalformedURLException mue ) |
| { |
| String message = I18n.err( I18n.ERR_12027_BAD_URL, urlName, lineNumber ); |
| LOG.error( message ); |
| throw new LdapLdifException( message, mue ); |
| } |
| } |
| else |
| { |
| String value = Strings.trimLeft( line.substring( pos + 1 ) ); |
| int end = value.length(); |
| |
| for ( int i = value.length() - 1; i > 0; i-- ) |
| { |
| char cc = value.charAt( i ); |
| |
| if ( cc == ' ' ) |
| { |
| if ( value.charAt( i - 1 ) == '\\' ) |
| { |
| // Escaped space : do nothing |
| break; |
| } |
| else |
| { |
| end = i; |
| } |
| } |
| else |
| { |
| break; |
| } |
| } |
| |
| String result = null; |
| |
| result = value.substring( 0, end ); |
| |
| return result; |
| } |
| } |
| else |
| { |
| return null; |
| } |
| } |
| |
| |
| /** |
| * Parse a control. The grammar is : |
| * <pre> |
| * <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> |
| * </pre> |
| * |
| * It can be read as : |
| * <pre> |
| * "control:" <fill> <ldap-oid> [ " "+ ( "true" | |
| * "false") ] [ ":" <fill> <SAFE-STRING-e> | "::" <fill> <BASE64-STRING> | ":<" |
| * <fill> <url> ] |
| * </pre> |
| * |
| * @param line The line containing the control |
| * @return A control |
| * @exception LdapLdifException If the control has no OID or if the OID is incorrect, |
| * of if the criticality is not set when it's mandatory. |
| */ |
| private Control parseControl( String line ) throws LdapLdifException |
| { |
| String lowerLine = Strings.toLowerCaseAscii( line ).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( I18n.err( I18n.ERR_12029_CONTROL_WITHOUT_OID, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12029_CONTROL_WITHOUT_OID ) ); |
| } |
| |
| int initPos = pos; |
| |
| while ( Chars.isCharASCII( controlValue, pos, '.' ) || Chars.isDigit( controlValue, pos ) ) |
| { |
| pos++; |
| } |
| |
| if ( pos == initPos ) |
| { |
| // Not a valid OID ! |
| LOG.error( I18n.err( I18n.ERR_12029_CONTROL_WITHOUT_OID, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12029_CONTROL_WITHOUT_OID ) ); |
| } |
| |
| // Create and check the OID |
| String oidString = lowerLine.substring( 0, pos ); |
| |
| if ( !Oid.isOid( oidString ) ) |
| { |
| String message = I18n.err( I18n.ERR_12031_INVALID_OID, oidString, lineNumber ); |
| LOG.error( message ); |
| throw new LdapLdifException( message ); |
| } |
| |
| LdifControl control = new LdifControl( oidString ); |
| |
| // Get the criticality, if any |
| // Skip the <fill> |
| while ( Chars.isCharASCII( controlValue, pos, ' ' ) ) |
| { |
| pos++; |
| } |
| |
| // Check if we have a "true" or a "false" |
| int criticalPos = lowerLine.indexOf( ':' ); |
| |
| int criticalLength; |
| |
| if ( criticalPos == -1 ) |
| { |
| criticalLength = length - pos; |
| } |
| else |
| { |
| criticalLength = criticalPos - pos; |
| } |
| |
| if ( ( criticalLength == 4 ) && ( "true".equalsIgnoreCase( lowerLine.substring( pos, pos + 4 ) ) ) ) |
| { |
| control.setCritical( true ); |
| } |
| else if ( ( criticalLength == 5 ) && ( "false".equalsIgnoreCase( lowerLine.substring( pos, pos + 5 ) ) ) ) |
| { |
| control.setCritical( false ); |
| } |
| else if ( criticalLength != 0 ) |
| { |
| // If we have a criticality, it should be either "true" or "false", |
| // nothing else |
| LOG.error( I18n.err( I18n.ERR_12033_INVALID_CRITICALITY, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12033_INVALID_CRITICALITY ) ); |
| } |
| |
| if ( criticalPos > 0 ) |
| { |
| // We have a value. It can be a normal value, a base64 encoded value |
| // or a file contained value |
| if ( Chars.isCharASCII( controlValue, criticalPos + 1, ':' ) ) |
| { |
| // Base 64 encoded value |
| |
| // Skip the <fill> |
| pos = criticalPos + 2; |
| |
| while ( Chars.isCharASCII( controlValue, pos, ' ' ) ) |
| { |
| pos++; |
| } |
| |
| byte[] value = Base64.decode( line.substring( pos ).toCharArray() ); |
| control.setValue( value ); |
| } |
| else if ( Chars.isCharASCII( controlValue, criticalPos + 1, '<' ) ) |
| { |
| // File contained value |
| throw new NotImplementedException( "See DIRSERVER-1547" ); |
| } |
| else |
| { |
| // Skip the <fill> |
| pos = criticalPos + 1; |
| |
| while ( Chars.isCharASCII( controlValue, pos, ' ' ) ) |
| { |
| pos++; |
| } |
| |
| // Standard value |
| byte[] value = new byte[length - pos]; |
| |
| for ( int i = 0; i < length - pos; i++ ) |
| { |
| value[i] = ( byte ) controlValue[i + pos]; |
| } |
| |
| control.setValue( value ); |
| } |
| } |
| |
| return control; |
| } |
| |
| |
| /** |
| * Parse an AttributeType/AttributeValue |
| * |
| * @param line The line to parse |
| * @return the parsed Attribute |
| */ |
| public static Attribute parseAttributeValue( String line ) |
| { |
| int colonIndex = line.indexOf( ':' ); |
| |
| if ( colonIndex != -1 ) |
| { |
| String attributeType = line.substring( 0, colonIndex ); |
| Object attributeValue = parseSimpleValue( line, colonIndex ); |
| |
| // Create an attribute |
| if ( attributeValue instanceof String ) |
| { |
| return new DefaultAttribute( attributeType, ( String ) attributeValue ); |
| } |
| else |
| { |
| return new DefaultAttribute( attributeType, ( byte[] ) 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 LdapException If anything goes wrong |
| */ |
| public void parseAttributeValue( LdifEntry entry, String line, String lowerLine ) throws LdapException |
| { |
| int colonIndex = line.indexOf( ':' ); |
| |
| String attributeType = lowerLine.substring( 0, colonIndex ); |
| |
| // We should *not* have a Dn twice |
| if ( attributeType.equals( "dn" ) ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12002_ENTRY_WITH_TWO_DNS, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12003_LDIF_ENTRY_WITH_TWO_DNS ) ); |
| } |
| |
| Object attributeValue = parseValue( line, colonIndex ); |
| |
| if ( schemaManager != null ) |
| { |
| AttributeType at = schemaManager.getAttributeType( attributeType ); |
| |
| if ( at != null ) |
| { |
| if ( at.getSyntax().isHumanReadable() ) |
| { |
| if ( attributeValue instanceof byte[] ) |
| { |
| attributeValue = Strings.utf8ToString( ( byte[] ) attributeValue ); |
| } |
| } |
| else |
| { |
| if ( attributeValue instanceof String ) |
| { |
| attributeValue = Strings.getBytesUtf8( ( String ) attributeValue ); |
| } |
| } |
| } |
| } |
| |
| // Update the entry |
| try |
| { |
| entry.addAttribute( attributeType, attributeValue ); |
| } |
| catch ( Exception e ) |
| { |
| // The attribute does not exist already, create a fake one |
| if ( ( schemaManager != null ) && schemaManager.isRelaxed() ) |
| { |
| MutableAttributeType newAttributeType = new MutableAttributeType( "1.3.6.1.4.1.18060.0.9999." + oidCounter++ ); |
| newAttributeType.setNames( attributeType ); |
| newAttributeType.setSyntax( schemaManager.getLdapSyntaxRegistry().get( SchemaConstants.DIRECTORY_STRING_SYNTAX ) ); |
| schemaManager.add( newAttributeType ); |
| entry.addAttribute( attributeType, attributeValue ); |
| } |
| } |
| } |
| |
| |
| /** |
| * Parse a ModRDN operation |
| * |
| * @param entry The entry to update |
| * @param iter The lines iterator |
| * @throws LdapLdifException If anything goes wrong |
| */ |
| private void parseModRdn( LdifEntry entry, Iterator<String> iter ) throws LdapLdifException |
| { |
| // We must have two lines : one starting with "newrdn:" or "newrdn::", |
| // and the second starting with "deleteoldrdn:" |
| if ( iter.hasNext() ) |
| { |
| String line = iter.next(); |
| String lowerLine = Strings.toLowerCaseAscii( line ); |
| |
| if ( lowerLine.startsWith( "newrdn::" ) || lowerLine.startsWith( "newrdn:" ) ) |
| { |
| int colonIndex = line.indexOf( ':' ); |
| Object attributeValue = parseValue( line, colonIndex ); |
| |
| if ( attributeValue instanceof String ) |
| { |
| entry.setNewRdn( ( String ) attributeValue ); |
| } |
| else |
| { |
| entry.setNewRdn( Strings.utf8ToString( ( byte[] ) attributeValue ) ); |
| } |
| } |
| else |
| { |
| LOG.error( I18n.err( I18n.ERR_12035_BAD_MODRDN_OPERATION, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12035_BAD_MODRDN_OPERATION ) ); |
| } |
| } |
| else |
| { |
| LOG.error( I18n.err( I18n.ERR_12035_BAD_MODRDN_OPERATION, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12035_BAD_MODRDN_OPERATION ) ); |
| } |
| |
| if ( iter.hasNext() ) |
| { |
| String line = iter.next(); |
| String lowerLine = Strings.toLowerCaseAscii( line ); |
| |
| if ( lowerLine.startsWith( "deleteoldrdn:" ) ) |
| { |
| int colonIndex = line.indexOf( ':' ); |
| Object attributeValue = parseValue( line, colonIndex ); |
| entry.setDeleteOldRdn( "1".equals( attributeValue ) ); |
| } |
| else |
| { |
| LOG.error( I18n.err( I18n.ERR_12038_NO_DELETEOLDRDN, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12038_NO_DELETEOLDRDN ) ); |
| } |
| } |
| else |
| { |
| LOG.error( I18n.err( I18n.ERR_12038_NO_DELETEOLDRDN, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12038_NO_DELETEOLDRDN ) ); |
| } |
| } |
| |
| |
| /** |
| * Parse a modify change type. |
| * |
| * The grammar is : |
| * <pre> |
| * <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 |
| * </pre> |
| * |
| * @param entry The entry to feed |
| * @param iter The lines |
| * @exception LdapLdifException If the modify operation is invalid |
| */ |
| private void parseModify( LdifEntry entry, Iterator<String> iter ) throws LdapLdifException |
| { |
| int state = MOD_SPEC; |
| String modified = null; |
| ModificationOperation modificationType = ModificationOperation.ADD_ATTRIBUTE; |
| Attribute attribute = null; |
| |
| // The following flag is used to deal with empty modifications |
| boolean isEmptyValue = true; |
| |
| while ( iter.hasNext() ) |
| { |
| String line = iter.next(); |
| String lowerLine = Strings.toLowerCaseAscii( line ); |
| |
| if ( lowerLine.startsWith( "-" ) ) |
| { |
| if ( ( state != ATTRVAL_SPEC_OR_SEP ) && ( state != ATTRVAL_SPEC ) ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12040_BAD_MODIFY_SEPARATOR, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12040_BAD_MODIFY_SEPARATOR ) ); |
| } |
| else |
| { |
| if ( isEmptyValue ) |
| { |
| if ( state == ATTRVAL_SPEC_OR_SEP ) |
| { |
| entry.addModification( modificationType, modified ); |
| } |
| else |
| { |
| // Update the entry with a null value |
| entry.addModification( modificationType, modified, null ); |
| } |
| } |
| else |
| { |
| // Update the entry with the attribute |
| entry.addModification( modificationType, attribute ); |
| } |
| |
| state = MOD_SPEC; |
| isEmptyValue = true; |
| } |
| } |
| else if ( lowerLine.startsWith( "add:" ) ) |
| { |
| if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2 ) ); |
| } |
| |
| modified = Strings.trim( line.substring( "add:".length() ) ); |
| modificationType = ModificationOperation.ADD_ATTRIBUTE; |
| attribute = new DefaultAttribute( modified ); |
| |
| state = ATTRVAL_SPEC; |
| } |
| else if ( lowerLine.startsWith( "delete:" ) ) |
| { |
| if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2 ) ); |
| } |
| |
| modified = Strings.trim( line.substring( "delete:".length() ) ); |
| modificationType = ModificationOperation.REMOVE_ATTRIBUTE; |
| attribute = new DefaultAttribute( modified ); |
| isEmptyValue = false; |
| |
| state = ATTRVAL_SPEC_OR_SEP; |
| } |
| else if ( lowerLine.startsWith( "replace:" ) ) |
| { |
| if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2 ) ); |
| } |
| |
| modified = Strings.trim( line.substring( "replace:".length() ) ); |
| modificationType = ModificationOperation.REPLACE_ATTRIBUTE; |
| attribute = new DefaultAttribute( modified ); |
| |
| state = ATTRVAL_SPEC_OR_SEP; |
| } |
| else |
| { |
| if ( ( state != ATTRVAL_SPEC ) && ( state != ATTRVAL_SPEC_OR_SEP ) ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12040_BAD_MODIFY_SEPARATOR, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12040_BAD_MODIFY_SEPARATOR ) ); |
| } |
| |
| // A standard AttributeType/AttributeValue pair |
| int colonIndex = line.indexOf( ':' ); |
| |
| String attributeType = line.substring( 0, colonIndex ); |
| |
| if ( !attributeType.equalsIgnoreCase( modified ) ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12044, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12045 ) ); |
| } |
| |
| // We should *not* have a Dn twice |
| if ( attributeType.equalsIgnoreCase( "dn" ) ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12002_ENTRY_WITH_TWO_DNS, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12003_LDIF_ENTRY_WITH_TWO_DNS ) ); |
| } |
| |
| Object attributeValue = parseValue( line, colonIndex ); |
| |
| try |
| { |
| if ( attributeValue instanceof String ) |
| { |
| attribute.add( ( String ) attributeValue ); |
| } |
| else |
| { |
| attribute.add( ( byte[] ) attributeValue ); |
| } |
| } |
| catch ( LdapInvalidAttributeValueException liave ) |
| { |
| throw new LdapLdifException( liave.getMessage(), liave ); |
| } |
| |
| isEmptyValue = false; |
| |
| state = ATTRVAL_SPEC_OR_SEP; |
| } |
| } |
| |
| if ( state != MOD_SPEC ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2, lineNumber ) ); |
| } |
| } |
| |
| |
| /** |
| * Parse a change operation. We have to handle different cases depending on |
| * the operation. |
| * <ul> |
| * <li>1) Delete : there should *not* be any line after the "changetype: delete" </li> |
| * <li>2) Add : we must have a list of AttributeType : AttributeValue elements </li> |
| * <li>3) ModDN : we must have two following lines: a "newrdn:" and a "deleteoldrdn:" </li> |
| * <li>4) ModRDN : the very same, but a "newsuperior:" line is expected </li> |
| * <li>5) Modify</li> |
| * </ul> |
| * |
| * The grammar is : |
| * <pre> |
| * <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 |
| * </pre> |
| * |
| * @param entry The entry to feed |
| * @param iter The lines iterator |
| * @param operation The change operation (add, modify, delete, moddn or modrdn) |
| * @exception LdapException If the change operation is invalid |
| */ |
| private void parseChange( LdifEntry entry, Iterator<String> iter, ChangeType operation ) throws LdapException |
| { |
| // The changetype and operation has already been parsed. |
| entry.setChangeType( operation ); |
| |
| switch ( operation ) |
| { |
| case Delete: |
| // The change type will tell that it's a delete operation, |
| // the dn is used as a key. |
| return; |
| |
| case Add: |
| // We will iterate through all attribute/value pairs |
| while ( iter.hasNext() ) |
| { |
| String line = iter.next(); |
| String lowerLine = Strings.toLowerCaseAscii( line ); |
| parseAttributeValue( entry, line, lowerLine ); |
| } |
| |
| return; |
| |
| case Modify: |
| parseModify( entry, iter ); |
| return; |
| |
| case ModDn: |
| // They are supposed to have the same syntax : |
| // No break ! |
| case ModRdn: |
| // First, parse the modrdn part |
| parseModRdn( entry, iter ); |
| |
| // The next line should be the new superior, if we have one |
| if ( iter.hasNext() ) |
| { |
| String line = iter.next(); |
| String lowerLine = Strings.toLowerCaseAscii( line ); |
| |
| if ( lowerLine.startsWith( "newsuperior:" ) ) |
| { |
| int colonIndex = line.indexOf( ':' ); |
| Object attributeValue = parseValue( line, colonIndex ); |
| |
| if ( attributeValue instanceof String ) |
| { |
| entry.setNewSuperior( ( String ) attributeValue ); |
| } |
| else |
| { |
| entry.setNewSuperior( Strings.utf8ToString( ( byte[] ) attributeValue ) ); |
| } |
| } |
| else |
| { |
| if ( operation == ChangeType.ModDn ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12046, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12047 ) ); |
| } |
| } |
| } |
| |
| return; |
| |
| default: |
| // This is an error |
| LOG.error( I18n.err( I18n.ERR_12048, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12049 ) ); |
| } |
| } |
| |
| |
| /** |
| * Parse a ldif file. The following rules are processed : |
| * <pre> |
| * <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> |
| * </pre> |
| * |
| * @return the parsed ldifEntry |
| * @exception LdapException If the ldif file does not contain a valid entry |
| */ |
| protected LdifEntry parseEntry() throws LdapException |
| { |
| 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 = lines.get( 0 ); |
| |
| lineNumber -= ( lines.size() - 1 ); |
| |
| String name = parseDn( line ); |
| |
| Dn dn = null; |
| |
| try |
| { |
| dn = new Dn( schemaManager, name ); |
| } |
| catch ( LdapInvalidDnException lide ) |
| { |
| // Deal with the RDN whihc is not in the schema |
| // First parse the DN without the schema |
| dn = new Dn( name ); |
| |
| Rdn rdn = dn.getRdn(); |
| |
| // Process each Ava |
| for ( Ava ava : rdn ) |
| { |
| if ( ( schemaManager != null ) && ( schemaManager.getAttributeType( ava.getType() ) == null ) |
| && schemaManager.isRelaxed() ) |
| { |
| // Not found : create a new one |
| MutableAttributeType newAttributeType = new MutableAttributeType( "1.3.6.1.4.1.18060.0.9999." + oidCounter++ ); |
| newAttributeType.setNames( ava.getType() ); |
| newAttributeType.setSyntax( schemaManager.getLdapSyntaxRegistry().get( SchemaConstants.DIRECTORY_STRING_SYNTAX ) ); |
| schemaManager.add( newAttributeType ); |
| } |
| } |
| |
| dn = new Dn( schemaManager, name ); |
| } |
| |
| // Ok, we have found a Dn |
| LdifEntry entry = createLdifEntry( schemaManager ); |
| entry.setLengthBeforeParsing( entryLen ); |
| entry.setOffset( entryOffset ); |
| |
| entry.setDn( dn ); |
| |
| // We remove this dn from the lines |
| lines.remove( 0 ); |
| |
| // Now, let's iterate through the other lines |
| Iterator<String> iter = lines.iterator(); |
| |
| // This flag is used to distinguish between an entry and a change |
| int type = LDIF_ENTRY; |
| |
| // 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; |
| |
| ChangeType operation = ChangeType.Add; |
| String lowerLine; |
| Control control; |
| |
| while ( iter.hasNext() ) |
| { |
| lineNumber++; |
| |
| // Each line could start either with an OID, an attribute type, with |
| // "control:" or with "changetype:" |
| line = iter.next(); |
| lowerLine = Strings.toLowerCaseAscii( line ); |
| |
| // 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( I18n.err( I18n.ERR_12004_CHANGE_NOT_ALLOWED, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12005_NO_CHANGE ) ); |
| } |
| |
| containsChanges = true; |
| |
| if ( controlSeen ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12050, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12051 ) ); |
| } |
| |
| // Parse the control |
| control = parseControl( line.substring( "control:".length() ) ); |
| entry.addControl( control ); |
| } |
| else if ( lowerLine.startsWith( "changetype:" ) ) |
| { |
| if ( containsEntries ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12004_CHANGE_NOT_ALLOWED, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12005_NO_CHANGE ) ); |
| } |
| |
| containsChanges = true; |
| |
| if ( changeTypeSeen ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12052, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12053 ) ); |
| } |
| |
| // A change request |
| type = CHANGE; |
| controlSeen = true; |
| |
| operation = parseChangeType( line ); |
| |
| // Parse the change operation in a separate function |
| parseChange( entry, iter, operation ); |
| changeTypeSeen = true; |
| } |
| else if ( line.indexOf( ':' ) > 0 ) |
| { |
| if ( containsChanges ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12004_CHANGE_NOT_ALLOWED, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12005_NO_CHANGE ) ); |
| } |
| |
| containsEntries = true; |
| |
| if ( controlSeen || changeTypeSeen ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12054, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12055 ) ); |
| } |
| |
| parseAttributeValue( entry, line, lowerLine ); |
| type = LDIF_ENTRY; |
| } |
| else |
| { |
| // Invalid attribute Value |
| LOG.error( I18n.err( I18n.ERR_12056, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12057_BAD_ATTRIBUTE ) ); |
| } |
| } |
| |
| if ( type == LDIF_ENTRY ) |
| { |
| LOG.debug( "Read an entry : {}", entry ); |
| } |
| else if ( type == CHANGE ) |
| { |
| entry.setChangeType( operation ); |
| LOG.debug( "Read a modification : {}", entry ); |
| } |
| else |
| { |
| LOG.error( I18n.err( I18n.ERR_12058_UNKNOWN_ENTRY_TYPE, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12059_UNKNOWN_ENTRY ) ); |
| } |
| |
| return entry; |
| } |
| |
| |
| /** |
| * Parse the version from the ldif input. |
| * |
| * @return A number representing the version (default to 1) |
| * @throws LdapLdifException If the version is incorrect or if the input is incorrect |
| */ |
| protected int parseVersion() throws LdapLdifException |
| { |
| int ver = DEFAULT_VERSION; |
| |
| // First, read a list of lines |
| readLines(); |
| |
| if ( lines.size() == 0 ) |
| { |
| LOG.warn( "The ldif file is empty" ); |
| return ver; |
| } |
| |
| // get the first line |
| String line = lines.get( 0 ); |
| |
| // <ldif-file> ::= "version:" <fill> <number> |
| char[] document = line.toCharArray(); |
| String versionNumber; |
| |
| if ( line.startsWith( "version:" ) ) |
| { |
| position += "version:".length(); |
| parseFill( document ); |
| |
| // Version number. Must be '1' in this version |
| versionNumber = parseNumber( document ); |
| |
| // We should not have any other chars after the number |
| if ( position != document.length ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12060_VERSION_NOT_A_NUMBER, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12061_LDIF_PARSING_ERROR ) ); |
| } |
| |
| try |
| { |
| ver = Integer.parseInt( versionNumber ); |
| } |
| catch ( NumberFormatException nfe ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12060_VERSION_NOT_A_NUMBER, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12061_LDIF_PARSING_ERROR ), nfe ); |
| } |
| |
| LOG.debug( "Ldif version : {}", versionNumber ); |
| |
| // We have found the version, just discard the line from the list |
| lines.remove( 0 ); |
| |
| // and read the next lines if the current buffer is empty |
| if ( lines.size() == 0 ) |
| { |
| // include the version line as part of the first entry |
| int tmpEntryLen = entryLen; |
| |
| readLines(); |
| |
| entryLen += tmpEntryLen; |
| } |
| } |
| else |
| { |
| LOG.info( "No version information : assuming version: 1" ); |
| } |
| |
| return ver; |
| } |
| |
| |
| /** |
| * gets a line from the underlying data store |
| * |
| * @return a line of characters or null if EOF reached |
| * @throws IOException on read failure |
| */ |
| protected String getLine() throws IOException |
| { |
| return ( ( BufferedReader ) reader ).readLine(); |
| } |
| |
| |
| /** |
| * Reads an entry in a ldif buffer, and returns the resulting lines, without |
| * comments, and unfolded. |
| * |
| * The lines represent *one* entry. |
| * |
| * @throws LdapLdifException If something went wrong |
| */ |
| protected void readLines() throws LdapLdifException |
| { |
| String line; |
| boolean insideComment = true; |
| boolean isFirstLine = true; |
| |
| lines.clear(); |
| entryLen = 0; |
| entryOffset = offset; |
| |
| StringBuffer sb = new StringBuffer(); |
| |
| try |
| { |
| while ( ( line = getLine() ) != null ) |
| { |
| lineNumber++; |
| |
| if ( line.length() == 0 ) |
| { |
| if ( isFirstLine ) |
| { |
| continue; |
| } |
| else |
| { |
| // The line is empty, we have read an entry |
| insideComment = false; |
| offset++; |
| break; |
| } |
| } |
| |
| // We will read the first line which is not a comment |
| switch ( line.charAt( 0 ) ) |
| { |
| case '#': |
| insideComment = true; |
| break; |
| |
| case ' ': |
| isFirstLine = false; |
| |
| if ( insideComment ) |
| { |
| continue; |
| } |
| else if ( sb.length() == 0 ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12062_EMPTY_CONTINUATION_LINE, lineNumber ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12061_LDIF_PARSING_ERROR ) ); |
| } |
| else |
| { |
| sb.append( line.substring( 1 ) ); |
| } |
| |
| insideComment = false; |
| break; |
| |
| default: |
| isFirstLine = false; |
| |
| // 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; |
| } |
| |
| byte[] data = Strings.getBytesUtf8( line ); |
| // FIXME might fail on windows in the new line issue, yet to check |
| offset += ( data.length + 1 ); |
| entryLen += ( data.length + 1 ); |
| } |
| } |
| catch ( IOException ioe ) |
| { |
| throw new LdapLdifException( I18n.err( I18n.ERR_12063_ERROR_WHILE_READING_LDIF_LINE ), ioe ); |
| } |
| |
| // Stores the current line if necessary. |
| if ( sb.length() != 0 ) |
| { |
| lines.add( sb.toString() ); |
| } |
| } |
| |
| |
| /** |
| * Parse a ldif file (using the default encoding). |
| * |
| * @param fileName The ldif file |
| * @return A list of entries |
| * @throws LdapLdifException If the parsing fails |
| */ |
| public List<LdifEntry> parseLdifFile( String fileName ) throws LdapLdifException |
| { |
| return parseLdifFile( fileName, Charset.forName( Strings.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 LdapLdifException If the parsing fails |
| */ |
| public List<LdifEntry> parseLdifFile( String fileName, String encoding ) throws LdapLdifException |
| { |
| if ( Strings.isEmpty( fileName ) ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12064_EMPTY_FILE_NAME ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12064_EMPTY_FILE_NAME ) ); |
| } |
| |
| File file = new File( fileName ); |
| |
| if ( !file.exists() ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12066, fileName ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12067, fileName ) ); |
| } |
| |
| BufferedReader bufferReader = null; |
| |
| // Open the file and then get a channel from the stream |
| try |
| { |
| bufferReader = new BufferedReader( |
| new InputStreamReader( new FileInputStream( file ), Charset.forName( encoding ) ) ); |
| |
| return parseLdif( bufferReader ); |
| } |
| catch ( FileNotFoundException fnfe ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12068, fileName ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12067, fileName ), fnfe ); |
| } |
| catch ( LdapException le ) |
| { |
| throw new LdapLdifException( le.getMessage(), le ); |
| } |
| finally |
| { |
| // close the reader |
| try |
| { |
| if ( bufferReader != null ) |
| { |
| bufferReader.close(); |
| } |
| } |
| catch ( IOException ioe ) |
| { |
| // Nothing to do |
| } |
| } |
| } |
| |
| |
| /** |
| * 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 LdapLdifException If something went wrong |
| */ |
| public List<LdifEntry> parseLdif( String ldif ) throws LdapLdifException |
| { |
| LOG.debug( "Starts parsing ldif buffer" ); |
| |
| if ( Strings.isEmpty( ldif ) ) |
| { |
| return new ArrayList<LdifEntry>(); |
| } |
| |
| BufferedReader bufferReader = new BufferedReader( new StringReader( ldif ) ); |
| |
| try |
| { |
| List<LdifEntry> entries = parseLdif( bufferReader ); |
| |
| if ( LOG.isDebugEnabled() ) |
| { |
| LOG.debug( "Parsed {} entries.", Integer.valueOf( entries.size() ) ); |
| } |
| |
| return entries; |
| } |
| catch ( LdapLdifException ne ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12069, ne.getLocalizedMessage() ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12070 ), ne ); |
| } |
| catch ( LdapException le ) |
| { |
| throw new LdapLdifException( le.getMessage(), le ); |
| } |
| finally |
| { |
| // Close the reader |
| try |
| { |
| bufferReader.close(); |
| } |
| catch ( IOException ioe ) |
| { |
| throw new LdapLdifException( I18n.err( I18n.ERR_12024_CANNOT_CLOSE_FILE ), ioe ); |
| } |
| |
| } |
| } |
| |
| |
| // ------------------------------------------------------------------------ |
| // Iterator Methods |
| // ------------------------------------------------------------------------ |
| /** |
| * Gets the next LDIF on the channel. |
| * |
| * @return the next LDIF as a String. |
| */ |
| private LdifEntry nextInternal() |
| { |
| try |
| { |
| LOG.debug( "next(): -- called" ); |
| |
| LdifEntry entry = prefetched; |
| readLines(); |
| |
| try |
| { |
| prefetched = parseEntry(); |
| } |
| catch ( LdapLdifException ne ) |
| { |
| error = ne; |
| throw new NoSuchElementException( ne.getMessage() ); |
| } |
| catch ( LdapException le ) |
| { |
| throw new NoSuchElementException( le.getMessage() ); |
| } |
| |
| LOG.debug( "next(): -- returning ldif {}\n", entry ); |
| |
| return entry; |
| } |
| catch ( LdapLdifException ne ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12071 ) ); |
| error = ne; |
| return null; |
| } |
| } |
| |
| |
| /** |
| * Gets the next LDIF on the channel. |
| * |
| * @return the next LDIF as a String. |
| */ |
| public LdifEntry next() |
| { |
| return nextInternal(); |
| } |
| |
| |
| /** |
| * Gets the current entry, but don't move forward. |
| * |
| * @return the pre-fetched entry |
| */ |
| public LdifEntry fetch() |
| { |
| return prefetched; |
| } |
| |
| |
| /** |
| * Tests to see if another LDIF is on the input channel. |
| * |
| * @return true if another LDIF is available false otherwise. |
| */ |
| private boolean hasNextInternal() |
| { |
| return null != prefetched; |
| } |
| |
| |
| /** |
| * Tests to see if another LDIF is on the input channel. |
| * |
| * @return true if another LDIF is available false otherwise. |
| */ |
| public boolean hasNext() |
| { |
| if ( prefetched != null ) |
| { |
| LOG.debug( "hasNext(): -- returning true" ); |
| } |
| else |
| { |
| LOG.debug( "hasNext(): -- returning false" ); |
| } |
| |
| return hasNextInternal(); |
| } |
| |
| |
| /** |
| * Always throws UnsupportedOperationException! |
| * |
| * @see java.util.Iterator#remove() |
| */ |
| private void removeInternal() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| |
| /** |
| * Always throws UnsupportedOperationException! |
| * |
| * @see java.util.Iterator#remove() |
| */ |
| public void remove() |
| { |
| removeInternal(); |
| } |
| |
| |
| /** |
| * @return An iterator on the file |
| */ |
| public Iterator<LdifEntry> iterator() |
| { |
| return new Iterator<LdifEntry>() |
| { |
| public boolean hasNext() |
| { |
| return hasNextInternal(); |
| } |
| |
| |
| public LdifEntry next() |
| { |
| try |
| { |
| return nextInternal(); |
| } |
| catch ( NoSuchElementException nse ) |
| { |
| LOG.error( nse.getMessage() ); |
| return null; |
| } |
| } |
| |
| |
| public void remove() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| }; |
| } |
| |
| |
| /** |
| * @return True if an error occurred during parsing |
| */ |
| public boolean hasError() |
| { |
| return error != null; |
| } |
| |
| |
| /** |
| * @return The exception that occurs during an entry parsing |
| */ |
| public Exception getError() |
| { |
| return error; |
| } |
| |
| |
| /** |
| * The main entry point of the LdifParser. It reads a buffer and returns a |
| * List of entries. |
| * |
| * @param reader The buffer being processed |
| * @return A list of entries |
| * @throws LdapException If something went wrong |
| */ |
| public List<LdifEntry> parseLdif( BufferedReader reader ) throws LdapException |
| { |
| // Create a list that will contain the read entries |
| List<LdifEntry> entries = new ArrayList<LdifEntry>(); |
| |
| this.reader = reader; |
| |
| // First get the version - if any - |
| version = parseVersion(); |
| prefetched = parseEntry(); |
| |
| // When done, get the entries one by one. |
| for ( LdifEntry entry : this ) |
| { |
| if ( entry != null ) |
| { |
| entries.add( entry ); |
| } |
| else |
| { |
| throw new LdapLdifException( I18n.err( I18n.ERR_12072, error.getLocalizedMessage() ) ); |
| } |
| } |
| |
| return entries; |
| } |
| |
| |
| /** |
| * @return True if the ldif file contains entries, fals if it contains changes |
| */ |
| public boolean containsEntries() |
| { |
| return containsEntries; |
| } |
| |
| |
| /** |
| * @return the current line that is being processed by the reader |
| */ |
| public int getLineNumber() |
| { |
| return lineNumber; |
| } |
| |
| |
| /** |
| * creates a non-schemaaware LdifEntry |
| * @return an LdifEntry that is not schemaaware |
| */ |
| protected LdifEntry createLdifEntry( SchemaManager schemaManager ) |
| { |
| if ( schemaManager != null ) |
| { |
| return new LdifEntry( schemaManager ); |
| } |
| else |
| { |
| return new LdifEntry(); |
| } |
| } |
| |
| |
| /** |
| * @return true if the DN validation is turned on |
| */ |
| public boolean isValidateDn() |
| { |
| return validateDn; |
| } |
| |
| |
| /** |
| * Turns on/off the DN validation |
| * |
| * @param validateDn the boolean flag |
| */ |
| public void setValidateDn( boolean validateDn ) |
| { |
| this.validateDn = validateDn; |
| } |
| |
| |
| /** |
| * @param schemaManager the schemaManager to set |
| */ |
| public void setSchemaManager( SchemaManager schemaManager ) |
| { |
| this.schemaManager = schemaManager; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void close() throws IOException |
| { |
| if ( reader != null ) |
| { |
| position = 0; |
| reader.close(); |
| containsEntries = false; |
| containsChanges = false; |
| offset = 0; |
| entryOffset = 0; |
| lineNumber = 0; |
| } |
| } |
| } |