| /* |
| * 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.IOException; |
| import java.io.StringReader; |
| import java.util.ArrayList; |
| |
| import javax.naming.directory.Attributes; |
| import javax.naming.directory.BasicAttributes; |
| |
| import org.apache.directory.api.i18n.I18n; |
| import org.apache.directory.api.ldap.model.entry.Attribute; |
| import org.apache.directory.api.ldap.model.entry.DefaultEntry; |
| import org.apache.directory.api.ldap.model.entry.Entry; |
| 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.schema.AttributeType; |
| import org.apache.directory.api.ldap.model.schema.SchemaManager; |
| import org.apache.directory.api.util.Strings; |
| 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> |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public class LdifAttributesReader extends LdifReader |
| { |
| /** A logger */ |
| private static final Logger LOG = LoggerFactory.getLogger( LdifAttributesReader.class ); |
| |
| |
| /** |
| * Constructors |
| */ |
| public LdifAttributesReader() |
| { |
| lines = new ArrayList<String>(); |
| position = 0; |
| version = DEFAULT_VERSION; |
| } |
| |
| |
| /** |
| * Parse an AttributeType/AttributeValue |
| * |
| * @param attributes The entry where to store the value |
| * @param line The line to parse |
| * @param lowerLine The same line, lowercased |
| * @throws LdapLdifException If anything goes wrong |
| */ |
| private void parseAttribute( Attributes attributes, String line, String lowerLine ) throws LdapLdifException |
| { |
| 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 ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12003_LDIF_ENTRY_WITH_TWO_DNS ) ); |
| } |
| |
| Object attributeValue = parseValue( line, colonIndex ); |
| |
| // Update the entry |
| javax.naming.directory.Attribute attribute = attributes.get( attributeType ); |
| |
| if ( attribute == null ) |
| { |
| attributes.put( attributeType, attributeValue ); |
| } |
| else |
| { |
| attribute.add( attributeValue ); |
| } |
| } |
| |
| |
| /** |
| * Parse an AttributeType/AttributeValue |
| * |
| * @param schemaManager The SchemaManager |
| * @param entry The entry where to store the value |
| * @param line The line to parse |
| * @param lowerLine The same line, lowercased |
| * @throws LdapLdifException If anything goes wrong |
| */ |
| private void parseEntryAttribute( SchemaManager schemaManager, Entry entry, String line, String lowerLine ) |
| throws LdapLdifException |
| { |
| int colonIndex = line.indexOf( ':' ); |
| |
| String attributeName = lowerLine.substring( 0, colonIndex ); |
| AttributeType attributeType = null; |
| |
| // We should *not* have a Dn twice |
| if ( attributeName.equals( "dn" ) ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12002_ENTRY_WITH_TWO_DNS ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12003_LDIF_ENTRY_WITH_TWO_DNS ) ); |
| } |
| |
| if ( schemaManager != null ) |
| { |
| attributeType = schemaManager.getAttributeType( attributeName ); |
| |
| if ( attributeType == null ) |
| { |
| LOG.error( "" ); |
| throw new LdapLdifException( "" ); |
| } |
| } |
| |
| Object attributeValue = parseValue( line, colonIndex ); |
| |
| // Update the entry |
| Attribute attribute = null; |
| |
| if ( schemaManager == null ) |
| { |
| attribute = entry.get( attributeName ); |
| } |
| else |
| { |
| attribute = entry.get( attributeType ); |
| } |
| |
| if ( attribute == null ) |
| { |
| if ( schemaManager == null ) |
| { |
| if ( attributeValue instanceof String ) |
| { |
| entry.put( attributeName, ( String ) attributeValue ); |
| } |
| else |
| { |
| entry.put( attributeName, ( byte[] ) attributeValue ); |
| } |
| } |
| else |
| { |
| try |
| { |
| if ( attributeValue instanceof String ) |
| { |
| entry.put( attributeName, attributeType, ( String ) attributeValue ); |
| } |
| else |
| { |
| entry.put( attributeName, attributeType, ( byte[] ) attributeValue ); |
| } |
| } |
| catch ( LdapException le ) |
| { |
| throw new LdapLdifException( I18n.err( I18n.ERR_12057_BAD_ATTRIBUTE ), le ); |
| } |
| } |
| } |
| else |
| { |
| try |
| { |
| if ( attributeValue instanceof String ) |
| { |
| attribute.add( ( String ) attributeValue ); |
| } |
| else |
| { |
| attribute.add( ( byte[] ) attributeValue ); |
| } |
| } |
| catch ( LdapInvalidAttributeValueException liave ) |
| { |
| throw new LdapLdifException( liave.getMessage(), liave ); |
| } |
| } |
| } |
| |
| |
| /** |
| * 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> |
| * |
| * @param schemaManager The SchemaManager |
| * @return The read entry |
| * @throws LdapLdifException If the entry can't be read or is invalid |
| */ |
| private Entry parseEntry( SchemaManager schemaManager ) throws LdapLdifException |
| { |
| if ( ( lines == null ) || ( lines.size() == 0 ) ) |
| { |
| LOG.debug( "The entry is empty : end of ldif file" ); |
| return null; |
| } |
| |
| Entry entry = new DefaultEntry( schemaManager ); |
| |
| // Now, let's iterate through the other lines |
| for ( String line : lines ) |
| { |
| // Each line could start either with an OID, an attribute type, with |
| // "control:" or with "changetype:" |
| String lowerLine = Strings.toLowerCase( line ); |
| |
| // We have three cases : |
| // 1) The first line after the Dn is a "control:" -> this is an error |
| // 2) The first line after the Dn is a "changeType:" -> this is an error |
| // 3) The first line after the Dn is anything else |
| if ( lowerLine.startsWith( "control:" ) ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12004_CHANGE_NOT_ALLOWED ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12005_NO_CHANGE ) ); |
| } |
| else if ( lowerLine.startsWith( "changetype:" ) ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12004_CHANGE_NOT_ALLOWED ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12005_NO_CHANGE ) ); |
| } |
| else if ( line.indexOf( ':' ) > 0 ) |
| { |
| parseEntryAttribute( schemaManager, entry, line, lowerLine ); |
| } |
| else |
| { |
| // Invalid attribute Value |
| LOG.error( I18n.err( I18n.ERR_12006_EXPECTING_ATTRIBUTE_TYPE ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12007_BAD_ATTRIBUTE ) ); |
| } |
| } |
| |
| LOG.debug( "Read an attributes : {}", entry ); |
| |
| return entry; |
| } |
| |
| |
| /** |
| * 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> |
| * |
| * @return The read entry |
| * @throws LdapLdifException If the entry can't be read or is invalid |
| */ |
| private Attributes parseAttributes() throws LdapLdifException |
| { |
| if ( ( lines == null ) || ( lines.size() == 0 ) ) |
| { |
| LOG.debug( "The entry is empty : end of ldif file" ); |
| return null; |
| } |
| |
| Attributes attributes = new BasicAttributes( true ); |
| |
| // Now, let's iterate through the other lines |
| for ( String line : lines ) |
| { |
| // Each line could start either with an OID, an attribute type, with |
| // "control:" or with "changetype:" |
| String lowerLine = Strings.toLowerCase( line ); |
| |
| // We have three cases : |
| // 1) The first line after the Dn is a "control:" -> this is an error |
| // 2) The first line after the Dn is a "changeType:" -> this is an error |
| // 3) The first line after the Dn is anything else |
| if ( lowerLine.startsWith( "control:" ) ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12004_CHANGE_NOT_ALLOWED ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12005_NO_CHANGE ) ); |
| } |
| else if ( lowerLine.startsWith( "changetype:" ) ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12004_CHANGE_NOT_ALLOWED ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12005_NO_CHANGE ) ); |
| } |
| else if ( line.indexOf( ':' ) > 0 ) |
| { |
| parseAttribute( attributes, line, lowerLine ); |
| } |
| else |
| { |
| // Invalid attribute Value |
| LOG.error( I18n.err( I18n.ERR_12006_EXPECTING_ATTRIBUTE_TYPE ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12007_BAD_ATTRIBUTE ) ); |
| } |
| } |
| |
| LOG.debug( "Read an attributes : {}", attributes ); |
| |
| return attributes; |
| } |
| |
| |
| /** |
| * A method which parses a ldif string and returns a list of Attributes. |
| * |
| * @param ldif The ldif string |
| * @return A list of Attributes, or an empty List |
| * @throws LdapLdifException If something went wrong |
| */ |
| public Attributes parseAttributes( String ldif ) throws LdapLdifException |
| { |
| lines = new ArrayList<String>(); |
| position = 0; |
| |
| LOG.debug( "Starts parsing ldif buffer" ); |
| |
| if ( Strings.isEmpty( ldif ) ) |
| { |
| return new BasicAttributes( true ); |
| } |
| |
| StringReader strIn = new StringReader( ldif ); |
| reader = new BufferedReader( strIn ); |
| |
| try |
| { |
| readLines(); |
| |
| Attributes attributes = parseAttributes(); |
| |
| if ( LOG.isDebugEnabled() ) |
| { |
| if ( attributes == null ) |
| { |
| LOG.debug( "Parsed no entry." ); |
| } |
| else |
| { |
| LOG.debug( "Parsed one entry." ); |
| } |
| } |
| |
| return attributes; |
| } |
| catch ( LdapLdifException ne ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12008_CANNOT_PARSE_LDIF_BUFFER, ne.getLocalizedMessage() ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12009_ERROR_PARSING_LDIF_BUFFER ), ne ); |
| } |
| finally |
| { |
| try |
| { |
| reader.close(); |
| } |
| catch ( IOException ioe ) |
| { |
| throw new LdapLdifException( I18n.err( I18n.ERR_12024_CANNOT_CLOSE_FILE ), ioe ); |
| } |
| } |
| } |
| |
| |
| /** |
| * A method which parses a ldif string and returns an Entry. |
| * |
| * @param ldif The ldif string |
| * @return An entry |
| * @throws LdapLdifException If something went wrong |
| */ |
| public Entry parseEntry( String ldif ) throws LdapLdifException |
| { |
| lines = new ArrayList<String>(); |
| position = 0; |
| |
| LOG.debug( "Starts parsing ldif buffer" ); |
| |
| if ( Strings.isEmpty( ldif ) ) |
| { |
| return new DefaultEntry(); |
| } |
| |
| StringReader strIn = new StringReader( ldif ); |
| reader = new BufferedReader( strIn ); |
| |
| try |
| { |
| readLines(); |
| |
| Entry entry = parseEntry( ( SchemaManager ) null ); |
| |
| if ( LOG.isDebugEnabled() ) |
| { |
| if ( entry == null ) |
| { |
| LOG.debug( "Parsed no entry." ); |
| } |
| else |
| { |
| LOG.debug( "Parsed one entry." ); |
| } |
| |
| } |
| |
| return entry; |
| } |
| catch ( LdapLdifException ne ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12008_CANNOT_PARSE_LDIF_BUFFER, ne.getLocalizedMessage() ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12009_ERROR_PARSING_LDIF_BUFFER ), ne ); |
| } |
| finally |
| { |
| try |
| { |
| reader.close(); |
| } |
| catch ( IOException ioe ) |
| { |
| throw new LdapLdifException( I18n.err( I18n.ERR_12024_CANNOT_CLOSE_FILE ), ioe ); |
| } |
| } |
| } |
| |
| |
| /** |
| * A method which parses a ldif string and returns an Entry. |
| * |
| * @param schemaManager The SchemaManager |
| * @param ldif The ldif string |
| * @return An entry |
| * @throws LdapLdifException If something went wrong |
| */ |
| public Entry parseEntry( SchemaManager schemaManager, String ldif ) throws LdapLdifException |
| { |
| lines = new ArrayList<String>(); |
| position = 0; |
| |
| LOG.debug( "Starts parsing ldif buffer" ); |
| |
| if ( Strings.isEmpty( ldif ) ) |
| { |
| return new DefaultEntry( schemaManager ); |
| } |
| |
| StringReader strIn = new StringReader( ldif ); |
| reader = new BufferedReader( strIn ); |
| |
| try |
| { |
| readLines(); |
| |
| Entry entry = parseEntry( schemaManager ); |
| |
| if ( LOG.isDebugEnabled() ) |
| { |
| if ( entry == null ) |
| { |
| LOG.debug( "Parsed no entry." ); |
| } |
| else |
| { |
| LOG.debug( "Parsed one entry." ); |
| } |
| |
| } |
| |
| return entry; |
| } |
| catch ( LdapLdifException ne ) |
| { |
| LOG.error( I18n.err( I18n.ERR_12008_CANNOT_PARSE_LDIF_BUFFER, ne.getLocalizedMessage() ) ); |
| throw new LdapLdifException( I18n.err( I18n.ERR_12009_ERROR_PARSING_LDIF_BUFFER ), ne ); |
| } |
| finally |
| { |
| try |
| { |
| reader.close(); |
| } |
| catch ( IOException ioe ) |
| { |
| throw new LdapLdifException( I18n.err( I18n.ERR_12024_CANNOT_CLOSE_FILE ), ioe ); |
| } |
| } |
| } |
| } |