/*
|
* CDDL HEADER START
|
*
|
* The contents of this file are subject to the terms of the
|
* Common Development and Distribution License, Version 1.0 only
|
* (the "License"). You may not use this file except in compliance
|
* with the License.
|
*
|
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
|
* or http://forgerock.org/license/CDDLv1.0.html.
|
* See the License for the specific language governing permissions
|
* and limitations under the License.
|
*
|
* When distributing Covered Code, include this CDDL HEADER in each
|
* file and include the License file at legal-notices/CDDLv1_0.txt.
|
* If applicable, add the following below this CDDL HEADER, with the
|
* fields enclosed by brackets "[]" replaced with your own identifying
|
* information:
|
* Portions Copyright [yyyy] [name of copyright owner]
|
*
|
* CDDL HEADER END
|
*
|
*
|
* Copyright 2009-2010 Sun Microsystems, Inc.
|
* Portions copyright 2011-2013 ForgeRock AS
|
*/
|
|
package org.forgerock.opendj.ldif;
|
|
import static com.forgerock.opendj.util.StaticUtils.closeSilently;
|
import static org.forgerock.opendj.ldap.CoreMessages.ERR_LDIF_ENTRY_EXCLUDED_BY_DN;
|
import static org.forgerock.opendj.ldap.CoreMessages.ERR_LDIF_ENTRY_EXCLUDED_BY_FILTER;
|
import static org.forgerock.opendj.ldap.CoreMessages.WARN_READ_LDIF_RECORD_MULTIPLE_CHANGE_RECORDS_FOUND;
|
import static org.forgerock.opendj.ldap.CoreMessages.WARN_READ_LDIF_RECORD_NO_CHANGE_RECORD_FOUND;
|
import static org.forgerock.opendj.ldap.CoreMessages.WARN_READ_LDIF_RECORD_UNEXPECTED_IO_ERROR;
|
|
import java.io.IOException;
|
import java.io.InputStream;
|
import java.io.Reader;
|
import java.util.Arrays;
|
import java.util.LinkedList;
|
import java.util.List;
|
import java.util.NoSuchElementException;
|
|
import org.forgerock.i18n.LocalizableMessage;
|
import org.forgerock.i18n.LocalizedIllegalArgumentException;
|
import org.forgerock.opendj.ldap.AttributeDescription;
|
import org.forgerock.opendj.ldap.DN;
|
import org.forgerock.opendj.ldap.DecodeException;
|
import org.forgerock.opendj.ldap.Entry;
|
import org.forgerock.opendj.ldap.LinkedHashMapEntry;
|
import org.forgerock.opendj.ldap.Matcher;
|
import org.forgerock.opendj.ldap.schema.Schema;
|
import org.forgerock.opendj.ldap.schema.SchemaValidationPolicy;
|
|
import com.forgerock.opendj.util.Validator;
|
|
/**
|
* An LDIF entry reader reads attribute value records (entries) using the LDAP
|
* Data Interchange Format (LDIF) from a user defined source.
|
*
|
* @see <a href="http://tools.ietf.org/html/rfc2849">RFC 2849 - The LDAP Data
|
* Interchange Format (LDIF) - Technical Specification </a>
|
*/
|
public final class LDIFEntryReader extends AbstractLDIFReader implements EntryReader {
|
// Poison used to indicate end of LDIF.
|
private static final Entry EOF = new LinkedHashMapEntry();
|
|
/**
|
* Parses the provided array of LDIF lines as a single LDIF entry.
|
*
|
* @param ldifLines
|
* The lines of LDIF to be parsed.
|
* @return The parsed LDIF entry.
|
* @throws LocalizedIllegalArgumentException
|
* If {@code ldifLines} did not contain an LDIF entry, if it
|
* contained multiple entries, if contained malformed LDIF, or
|
* if the entry could not be decoded using the default schema.
|
* @throws NullPointerException
|
* If {@code ldifLines} was {@code null}.
|
*/
|
public static Entry valueOfLDIFEntry(final String... ldifLines) {
|
final LDIFEntryReader reader = new LDIFEntryReader(ldifLines);
|
try {
|
if (!reader.hasNext()) {
|
// No change record found.
|
final LocalizableMessage message =
|
WARN_READ_LDIF_RECORD_NO_CHANGE_RECORD_FOUND.get();
|
throw new LocalizedIllegalArgumentException(message);
|
}
|
|
final Entry entry = reader.readEntry();
|
|
if (reader.hasNext()) {
|
// Multiple change records found.
|
final LocalizableMessage message =
|
WARN_READ_LDIF_RECORD_MULTIPLE_CHANGE_RECORDS_FOUND.get();
|
throw new LocalizedIllegalArgumentException(message);
|
}
|
|
return entry;
|
} catch (final DecodeException e) {
|
// Badly formed LDIF.
|
throw new LocalizedIllegalArgumentException(e.getMessageObject());
|
} catch (final IOException e) {
|
// This should never happen for a String based reader.
|
final LocalizableMessage message =
|
WARN_READ_LDIF_RECORD_UNEXPECTED_IO_ERROR.get(e.getMessage());
|
throw new LocalizedIllegalArgumentException(message);
|
} finally {
|
closeSilently(reader);
|
}
|
}
|
|
private Entry nextEntry = null;
|
|
/**
|
* Creates a new LDIF entry reader whose source is the provided input
|
* stream.
|
*
|
* @param in
|
* The input stream to use.
|
* @throws NullPointerException
|
* If {@code in} was {@code null}.
|
*/
|
public LDIFEntryReader(final InputStream in) {
|
super(in);
|
}
|
|
/**
|
* Creates a new LDIF entry reader which will read lines of LDIF from the
|
* provided list of LDIF lines.
|
*
|
* @param ldifLines
|
* The lines of LDIF to be read.
|
* @throws NullPointerException
|
* If {@code ldifLines} was {@code null}.
|
*/
|
public LDIFEntryReader(final List<String> ldifLines) {
|
super(ldifLines);
|
}
|
|
/**
|
* Creates a new LDIF entry reader whose source is the provided character
|
* stream reader.
|
*
|
* @param reader
|
* The character stream reader to use.
|
* @throws NullPointerException
|
* If {@code reader} was {@code null}.
|
*/
|
public LDIFEntryReader(final Reader reader) {
|
super(reader);
|
}
|
|
/**
|
* Creates a new LDIF entry reader which will read lines of LDIF from the
|
* provided array of LDIF lines.
|
*
|
* @param ldifLines
|
* The lines of LDIF to be read.
|
* @throws NullPointerException
|
* If {@code ldifLines} was {@code null}.
|
*/
|
public LDIFEntryReader(final String... ldifLines) {
|
super(Arrays.asList(ldifLines));
|
}
|
|
/**
|
* {@inheritDoc}
|
*/
|
@Override
|
public void close() throws IOException {
|
close0();
|
}
|
|
/**
|
* {@inheritDoc}
|
*
|
* @throws DecodeException
|
* If the entry could not be decoded because it was malformed.
|
*/
|
@Override
|
public boolean hasNext() throws DecodeException, IOException {
|
return getNextEntry() != EOF;
|
}
|
|
/**
|
* {@inheritDoc}
|
*
|
* @throws DecodeException
|
* If the entry could not be decoded because it was malformed.
|
*/
|
@Override
|
public Entry readEntry() throws DecodeException, IOException {
|
if (!hasNext()) {
|
// LDIF reader has completed successfully.
|
throw new NoSuchElementException();
|
}
|
|
final Entry entry = nextEntry;
|
nextEntry = null;
|
return entry;
|
}
|
|
/**
|
* Specifies whether or not all operational attributes should be excluded
|
* from any entries that are read from LDIF. The default is {@code false}.
|
*
|
* @param excludeOperationalAttributes
|
* {@code true} if all operational attributes should be excluded,
|
* or {@code false} otherwise.
|
* @return A reference to this {@code LDIFEntryReader}.
|
*/
|
public LDIFEntryReader setExcludeAllOperationalAttributes(
|
final boolean excludeOperationalAttributes) {
|
this.excludeOperationalAttributes = excludeOperationalAttributes;
|
return this;
|
}
|
|
/**
|
* Specifies whether or not all user attributes should be excluded from any
|
* entries that are read from LDIF. The default is {@code false}.
|
*
|
* @param excludeUserAttributes
|
* {@code true} if all user attributes should be excluded, or
|
* {@code false} otherwise.
|
* @return A reference to this {@code LDIFEntryReader}.
|
*/
|
public LDIFEntryReader setExcludeAllUserAttributes(final boolean excludeUserAttributes) {
|
this.excludeUserAttributes = excludeUserAttributes;
|
return this;
|
}
|
|
/**
|
* Excludes the named attribute from any entries that are read from LDIF. By
|
* default all attributes are included unless explicitly excluded.
|
*
|
* @param attributeDescription
|
* The name of the attribute to be excluded.
|
* @return A reference to this {@code LDIFEntryReader}.
|
*/
|
public LDIFEntryReader setExcludeAttribute(final AttributeDescription attributeDescription) {
|
Validator.ensureNotNull(attributeDescription);
|
excludeAttributes.add(attributeDescription);
|
return this;
|
}
|
|
/**
|
* Excludes all entries beneath the named entry (inclusive) from being read
|
* from LDIF. By default all entries are written unless explicitly excluded
|
* or included by branches or filters.
|
*
|
* @param excludeBranch
|
* The distinguished name of the branch to be excluded.
|
* @return A reference to this {@code LDIFEntryReader}.
|
*/
|
public LDIFEntryReader setExcludeBranch(final DN excludeBranch) {
|
Validator.ensureNotNull(excludeBranch);
|
excludeBranches.add(excludeBranch);
|
return this;
|
}
|
|
/**
|
* Excludes all entries which match the provided filter matcher from being
|
* read from LDIF. By default all entries are read unless explicitly
|
* excluded or included by branches or filters.
|
*
|
* @param excludeFilter
|
* The filter matcher.
|
* @return A reference to this {@code LDIFEntryReader}.
|
*/
|
public LDIFEntryReader setExcludeFilter(final Matcher excludeFilter) {
|
Validator.ensureNotNull(excludeFilter);
|
excludeFilters.add(excludeFilter);
|
return this;
|
}
|
|
/**
|
* Ensures that the named attribute is not excluded from any entries that
|
* are read from LDIF. By default all attributes are included unless
|
* explicitly excluded.
|
*
|
* @param attributeDescription
|
* The name of the attribute to be included.
|
* @return A reference to this {@code LDIFEntryReader}.
|
*/
|
public LDIFEntryReader setIncludeAttribute(final AttributeDescription attributeDescription) {
|
Validator.ensureNotNull(attributeDescription);
|
includeAttributes.add(attributeDescription);
|
return this;
|
}
|
|
/**
|
* Ensures that all entries beneath the named entry (inclusive) are read
|
* from LDIF. By default all entries are written unless explicitly excluded
|
* or included by branches or filters.
|
*
|
* @param includeBranch
|
* The distinguished name of the branch to be included.
|
* @return A reference to this {@code LDIFEntryReader}.
|
*/
|
public LDIFEntryReader setIncludeBranch(final DN includeBranch) {
|
Validator.ensureNotNull(includeBranch);
|
includeBranches.add(includeBranch);
|
return this;
|
}
|
|
/**
|
* Ensures that all entries which match the provided filter matcher are read
|
* from LDIF. By default all entries are read unless explicitly excluded or
|
* included by branches or filters.
|
*
|
* @param includeFilter
|
* The filter matcher.
|
* @return A reference to this {@code LDIFEntryReader}.
|
*/
|
public LDIFEntryReader setIncludeFilter(final Matcher includeFilter) {
|
Validator.ensureNotNull(includeFilter);
|
includeFilters.add(includeFilter);
|
return this;
|
}
|
|
/**
|
* Sets the rejected record listener which should be notified whenever an
|
* LDIF record is skipped, malformed, or fails schema validation.
|
* <p>
|
* By default the {@link RejectedLDIFListener#FAIL_FAST} listener is used.
|
*
|
* @param listener
|
* The rejected record listener.
|
* @return A reference to this {@code LDIFEntryReader}.
|
*/
|
public LDIFEntryReader setRejectedLDIFListener(final RejectedLDIFListener listener) {
|
this.rejectedRecordListener = listener;
|
return this;
|
}
|
|
/**
|
* Sets the schema which should be used for decoding entries that are read
|
* from LDIF. The default schema is used if no other is specified.
|
*
|
* @param schema
|
* The schema which should be used for decoding entries that are
|
* read from LDIF.
|
* @return A reference to this {@code LDIFEntryReader}.
|
*/
|
public LDIFEntryReader setSchema(final Schema schema) {
|
Validator.ensureNotNull(schema);
|
this.schema =
|
schemaValidationPolicy.checkAttributesAndObjectClasses().needsChecking() ? schema
|
.asStrictSchema() : schema.asNonStrictSchema();
|
return this;
|
}
|
|
/**
|
* Specifies the schema validation which should be used when reading LDIF
|
* entry records. If attribute value validation is enabled then all checks
|
* will be performed.
|
* <p>
|
* Schema validation is disabled by default.
|
* <p>
|
* <b>NOTE:</b> this method copies the provided policy so changes made to it
|
* after this method has been called will have no effect.
|
*
|
* @param policy
|
* The schema validation which should be used when reading LDIF
|
* entry records.
|
* @return A reference to this {@code LDIFEntryReader}.
|
*/
|
public LDIFEntryReader setSchemaValidationPolicy(final SchemaValidationPolicy policy) {
|
this.schemaValidationPolicy = SchemaValidationPolicy.copyOf(policy);
|
this.schema =
|
schemaValidationPolicy.checkAttributesAndObjectClasses().needsChecking() ? schema
|
.asStrictSchema() : schema.asNonStrictSchema();
|
return this;
|
}
|
|
private Entry getNextEntry() throws DecodeException, IOException {
|
while (nextEntry == null) {
|
// Read the set of lines that make up the next entry.
|
final LDIFRecord record = readLDIFRecord();
|
if (record == null) {
|
nextEntry = EOF;
|
break;
|
}
|
|
try {
|
/*
|
* Read the DN of the entry and see if it is one that should be
|
* included in the import.
|
*/
|
final DN entryDN = readLDIFRecordDN(record);
|
if (entryDN == null) {
|
// Skip version record.
|
continue;
|
}
|
|
// Skip if branch containing the entry DN is excluded.
|
if (isBranchExcluded(entryDN)) {
|
final LocalizableMessage message =
|
ERR_LDIF_ENTRY_EXCLUDED_BY_DN
|
.get(record.lineNumber, entryDN.toString());
|
handleSkippedRecord(record, message);
|
continue;
|
}
|
|
// Use an Entry for the AttributeSequence.
|
final Entry entry = new LinkedHashMapEntry(entryDN);
|
boolean schemaValidationFailure = false;
|
final List<LocalizableMessage> schemaErrors = new LinkedList<LocalizableMessage>();
|
while (record.iterator.hasNext()) {
|
final String ldifLine = record.iterator.next();
|
if (!readLDIFRecordAttributeValue(record, ldifLine, entry, schemaErrors)) {
|
schemaValidationFailure = true;
|
}
|
}
|
|
// Skip if the entry is excluded by any filters.
|
if (isEntryExcluded(entry)) {
|
final LocalizableMessage message =
|
ERR_LDIF_ENTRY_EXCLUDED_BY_FILTER.get(record.lineNumber, entryDN
|
.toString());
|
handleSkippedRecord(record, message);
|
continue;
|
}
|
|
if (!schema.validateEntry(entry, schemaValidationPolicy, schemaErrors)) {
|
schemaValidationFailure = true;
|
}
|
|
if (schemaValidationFailure) {
|
handleSchemaValidationFailure(record, schemaErrors);
|
continue;
|
}
|
|
if (!schemaErrors.isEmpty()) {
|
handleSchemaValidationWarning(record, schemaErrors);
|
}
|
|
nextEntry = entry;
|
} catch (final DecodeException e) {
|
handleMalformedRecord(record, e.getMessageObject());
|
continue;
|
}
|
}
|
|
return nextEntry;
|
}
|
|
}
|