LDIFReader.java revision a89f7014aeb71dba5c94404dfea7eb89e7eeee74
/*
* 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
* 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 2006-2010 Sun Microsystems, Inc.
* Portions Copyright 2012-2015 ForgeRock AS
*/
/**
* This class provides the ability to read information from an LDIF file. It
* provides support for both standard entries and change entries (as would be
* used with a tool like ldapmodify).
*/
mayInstantiate=true,
mayExtend=false,
mayInvoke=true)
public class LDIFReader implements Closeable
{
/** The reader that will be used to read the data. */
private BufferedReader reader;
/** The import configuration that specifies what should be imported. */
protected LDIFImportConfig importConfig;
/** The lines that comprise the body of the last entry read. */
/**
* The lines that comprise the header (DN and any comments) for the last entry
* read.
*/
/**
* The number of entries that have been ignored by this LDIF reader because
* they didn't match the criteria.
*/
/**
* The number of entries that have been read by this LDIF reader, including
* those that were ignored because they didn't match the criteria, and
* including those that were rejected because they were invalid in some way.
*/
/** The number of entries that have been rejected by this LDIF reader. */
/** The line number on which the last entry started. */
protected long lastEntryLineNumber = -1;
/**
* The line number of the last line read from the LDIF file, starting with 1.
*/
private long lineNumber;
/**
* The plugin config manager that will be used if we are to invoke plugins on
* the entries as they are read.
*/
protected PluginConfigManager pluginConfigManager;
/**
* Creates a new LDIF reader that will read information from the specified
* file.
*
* @param importConfig The import configuration for this LDIF reader. It
* must not be <CODE>null</CODE>.
*
* @throws IOException If a problem occurs while opening the LDIF file for
* reading.
*/
throws IOException
{
this.importConfig = importConfig;
lastEntryBodyLines = new LinkedList<>();
lastEntryHeaderLines = new LinkedList<>();
// If we should invoke import plugins, then do so.
if (importConfig.invokeImportPlugins())
{
// Inform LDIF import plugins that an import session is ending
}
}
/**
* Reads the next entry from the LDIF source.
*
* @return The next entry read from the LDIF source, or <CODE>null</CODE> if
* the end of the LDIF data is reached.
*
* @throws IOException If an I/O problem occurs while reading from the file.
*
* @throws LDIFException If the information read cannot be parsed as an LDIF
* entry.
*/
throws IOException, LDIFException
{
}
/**
* Reads the next entry from the LDIF source.
*
* @param checkSchema Indicates whether this reader should perform schema
* checking on the entry before returning it to the
* caller. Note that some basic schema checking (like
* refusing multiple values for a single-valued
* attribute) may always be performed.
*
*
* @return The next entry read from the LDIF source, or <CODE>null</CODE> if
* the end of the LDIF data is reached.
*
* @throws IOException If an I/O problem occurs while reading from the file.
*
* @throws LDIFException If the information read cannot be parsed as an LDIF
* entry.
*/
throws IOException, LDIFException
{
while (true)
{
// Read the set of lines that make up the next entry.
{
return null;
}
lastEntryHeaderLines = new LinkedList<>();
// Read the DN of the entry and see if it is one that should be included
// in the import.
{
// This should only happen if the LDIF starts with the "version:" line
// and has a blank line immediately after that. In that case, simply
// read and return the next entry.
continue;
}
{
+ "should be included based on the include and exclude branches.", entryDN);
continue;
}
else
{
}
// Create the entry and see if it is one that should be included in the import.
{
continue;
}
// The entry should be included in the import, so return it.
return entry;
}
}
private Entry createEntry(DN entryDN, List<StringBuilder> lines, boolean checkSchema) throws LDIFException
{
{
readAttribute(lines, line, entryDN, objectClasses, userAttrBuilders, operationalAttrBuilders, checkSchema);
}
return entry;
}
private boolean isIncludedInImport(Entry entry, LinkedList<StringBuilder> lines) throws LDIFException
{
try
{
{
+ "should be included based on the include and exclude filters.", entryDN);
return false;
}
}
catch (Exception e)
{
logger.traceException(e);
}
return true;
}
{
if (importConfig.invokeImportPlugins())
{
if (!pluginResult.continueProcessing())
{
if (rejectMessage == null)
{
}
else
{
}
logToRejectWriter(lines, m);
return false;
}
}
return true;
}
private void validateAgainstSchemaIfNeeded(boolean checkSchema, final Entry entry, LinkedList<StringBuilder> lines)
throws LDIFException
{
if (checkSchema)
{
{
LocalizableMessage message = ERR_LDIF_SCHEMA_VIOLATION.get(entryDN, lastEntryLineNumber, invalidReason);
}
// Add any superior objectclass(s) missing in an entries objectclass map.
}
}
/**
* Returns a new Map where the provided Map with AttributeBuilders is converted to another Map
* with Attributes.
*
* @param attrBuilders
* the provided Map containing AttributeBuilders
* @return a new Map containing Attributes
*/
protected Map<AttributeType, List<Attribute>> toAttributesMap(Map<AttributeType, List<AttributeBuilder>> attrBuilders)
{
{
}
return attributes;
}
/**
* Converts the provided List of AttributeBuilders to a new list of Attributes.
*
* @param builders the list of AttributeBuilders
* @return a new list of Attributes
*/
{
{
}
return results;
}
/**
* Reads the next change record from the LDIF source.
*
* @param defaultAdd Indicates whether the change type should default to
* "add" if none is explicitly provided.
*
* @return The next change record from the LDIF source, or <CODE>null</CODE>
* if the end of the LDIF data is reached.
*
* @throws IOException If an I/O problem occurs while reading from the file.
*
* @throws LDIFException If the information read cannot be parsed as an LDIF
* entry.
*/
throws IOException, LDIFException
{
while (true)
{
// Read the set of lines that make up the next entry.
{
return null;
}
// Read the DN of the entry and see if it is one that should be included
// in the import.
{
// This should only happen if the LDIF starts with the "version:" line
// and has a blank line immediately after that. In that case, simply
// read and return the next entry.
continue;
}
if(changeType != null)
{
{
{
{
{
{
} else
{
changeType, "add, delete, modify, moddn, modrdn");
}
} else
{
// default to "add"?
if(defaultAdd)
{
} else
{
null, "add, delete, modify, moddn, modrdn");
}
}
return entry;
}
}
/**
* Reads a set of lines from the next entry in the LDIF source.
*
* @return A set of lines from the next entry in the LDIF source.
*
* @throws IOException If a problem occurs while reading from the LDIF
* source.
*
* @throws LDIFException If the information read is not valid LDIF.
*/
{
// Read the entry lines into a buffer.
int lastLine = -1;
{
return null;
}
while (true)
{
lineNumber++;
{
// This must mean that we have reached the end of the LDIF source.
// If the set of lines read so far is empty, then move onto the next
// file or return null. Otherwise, break out of this loop.
{
break;
}
{
return readEntryLines();
}
return null;
}
{
// This is a blank line. If the set of lines read so far is empty,
// then just skip over it. Otherwise, break out of this loop.
{
break;
}
continue;
}
{
// This is a comment. Ignore it.
continue;
}
{
// This is a continuation of the previous line. If there is no
// previous line, then that's a problem. Note that while RFC 2849
// technically only allows a space in this position, both OpenLDAP and
// the Sun Java System Directory Server allow a tab as well, so we will
// too for compatibility reasons. See issue #852 for details.
if (lastLine >= 0)
{
}
else
{
}
}
else
{
// This is a new line.
{
}
{
// This is a UTF-8 BOM that Java doesn't skip. We will skip it here.
}
lastLine++;
}
}
return lines;
}
/**
* Reads the DN of the entry from the provided list of lines. The DN must be
* the first line in the list, unless the first line starts with "version",
* in which case the DN should be the second line.
*
* @param lines The set of lines from which the DN should be read.
*
* @return The decoded entry DN.
*
* @throws LDIFException If DN is not the first element in the list (or the
* second after the LDIF version), or if a problem
* occurs while trying to parse it.
*/
{
{
// This is possible if the contents of the first "entry" were just
// the version identifier. If that is the case, then return null and
// use that as a signal to the caller to go ahead and read the next entry.
return null;
}
if (colonPos <= 0)
{
}
{
// This is the version line, and we can skip it.
}
{
}
// Look at the character immediately after the colon. If there is none,
// then assume the null DN. If it is another colon, then the DN must be
// base64-encoded. Otherwise, it may be one or more spaces.
{
}
{
// The DN is base64-encoded. Find the first non-blank character and
// take the rest of the line, base64-decode it, and parse it as a DN.
}
else
{
// The rest of the value should be the DN. Skip over any spaces and
// attempt to decode the rest of the line as the DN.
}
}
{
{
pos++;
}
return pos;
}
{
try
{
}
catch (Exception e)
{
// The value did not have a valid base64-encoding.
if (logger.isTraceEnabled())
{
"Base64 decode failed for dn '%s', exception stacktrace: %s",
}
}
}
{
try
{
}
catch (DirectoryException de)
{
if (logger.isTraceEnabled())
{
}
}
catch (Exception e)
{
if (logger.isTraceEnabled())
{
}
lastEntryLineNumber, line, e);
}
}
/**
* Reads the changetype of the entry from the provided list of lines. If
* there is no changetype attribute then an add is assumed.
*
* @param lines The set of lines from which the DN should be read.
*
* @return The decoded entry DN.
*
* @throws LDIFException If DN is not the first element in the list (or the
* second after the LDIF version), or if a problem
* occurs while trying to parse it.
*/
throws LDIFException
{
{
// Error. There must be other entries.
return null;
}
if (colonPos <= 0)
{
}
{
// No changetype attribute - return null
return null;
}
// Remove the line
// Look at the character immediately after the colon. If there is none,
// then no value was specified. Throw an exception
{
null, "add, delete, modify, moddn, modrdn");
}
{
// The change type is base64-encoded. Find the first non-blank character
// and take the rest of the line, and base64-decode it.
}
else
{
// The rest of the value should be the changetype. Skip over any spaces
// and attempt to decode the rest of the line as the changetype string.
}
}
/**
* Decodes the provided line as an LDIF attribute and adds it to the
* appropriate hash.
*
* @param lines The full set of lines that comprise the
* entry (used for writing reject information).
* @param line The line to decode.
* @param entryDN The DN of the entry being decoded.
* @param objectClasses The set of objectclasses decoded so far for
* the current entry.
* @param userAttrBuilders The map of user attribute builders decoded
* so far for the current entry.
* @param operationalAttrBuilders The map of operational attribute builders
* decoded so far for the current entry.
* @param checkSchema Indicates whether to perform schema
* validation for the attribute.
*
* @throws LDIFException If a problem occurs while trying to decode the
* attribute contained in the provided entry.
*/
boolean checkSchema)
throws LDIFException
{
// Parse the attribute type description.
// Now parse the attribute value.
// See if this is an objectclass or an attribute. Then get the
// corresponding definition and add the value to the appropriate hash.
{
if (! importConfig.includeObjectClasses())
{
if (logger.isTraceEnabled())
{
}
return;
}
if (objectClass == null)
{
}
{
}
else
{
}
}
else
{
{
}
{
if (logger.isTraceEnabled())
{
}
return;
}
//The attribute is not being ignored so check for binary option.
if(checkSchema
{
}
if (checkSchema &&
{
{
{
}
else
{
}
}
}
if (attrType.isOperational())
{
}
else
{
}
{
return;
}
// Check to see if any of the attributes in the list have the same set of
// options. If so, then try to add a value to that attribute.
for (AttributeBuilder a : attrList)
{
{
{
}
{
}
return;
}
}
// No set of matching options was found, so create a new one and
// add it to the list.
}
}
/**
* Decodes the provided line as an LDIF attribute and returns the
* Attribute (name and values) for the specified attribute name.
*
* @param lines The full set of lines that comprise the
* entry (used for writing reject information).
* @param line The line to decode.
* @param entryDN The DN of the entry being decoded.
* @param attributeName The name and options of the attribute to
* return the values for.
*
* @return The attribute in octet string form.
* @throws LDIFException If a problem occurs while trying to decode
* the attribute contained in the provided
* entry or if the parsed attribute name does
* not match the specified attribute name.
*/
private Attribute readSingleValueAttribute(
{
// Parse the attribute type description.
if (attributeName != null)
{
{
}
}
// Now parse the attribute value.
return builder.toAttribute();
}
/**
* Retrieves the starting line number for the last entry read from the LDIF
* source.
*
* @return The starting line number for the last entry read from the LDIF
* source.
*/
public long getLastEntryLineNumber()
{
return lastEntryLineNumber;
}
/**
* Rejects the last entry read from the LDIF. This method is intended for use
* by components that perform their own validation of entries (e.g., backends
* during import processing) in which the entry appeared valid to the LDIF
* reader but some other problem was encountered.
*
* @param message A human-readable message providing the reason that the
* last entry read was not acceptable.
*/
{
if (rejectWriter != null)
{
try
{
{
}
{
}
{
}
}
catch (Exception e)
{
logger.traceException(e);
}
}
}
/**
* Log the specified entry and messages in the reject writer. The method is
* intended to be used in a threaded environment, where individual import
* threads need to log an entry and message to the reject file.
*
* @param e The entry to log.
* @param message The message to log.
*/
if (rejectWriter != null) {
try {
}
for(StringBuilder l : eLDIF) {
}
} catch (IOException ex) {
}
}
}
/**
* Closes this LDIF reader and the underlying file or input stream.
*/
public void close()
{
// If we should invoke import plugins, then do so.
if (importConfig.invokeImportPlugins())
{
// Inform LDIF import plugins that an import session is ending
}
}
/**
* Parse an AttributeDescription (an attribute type name and its
* options).
*
* @param attrDescr
* The attribute description to be parsed.
* @return A new attribute with no values, representing the
* attribute type and its options.
*/
{
if (semicolonPos > 0)
{
while (nextPos > 0)
{
{
}
}
{
}
}
else
{
}
{
//resetting doesn't hurt and returns false.
}
return builder.toAttribute();
}
/**
* Retrieves the total number of entries read so far by this LDIF reader,
* including those that have been ignored or rejected.
*
* @return The total number of entries read so far by this LDIF reader.
*/
public long getEntriesRead()
{
return entriesRead.get();
}
/**
* Retrieves the total number of entries that have been ignored so far by this
* LDIF reader because they did not match the import criteria.
*
* @return The total number of entries ignored so far by this LDIF reader.
*/
public long getEntriesIgnored()
{
return entriesIgnored.get();
}
/**
* Retrieves the total number of entries rejected so far by this LDIF reader.
* This includes both entries that were rejected because of internal
* validation failure (e.g., they didn't conform to the defined server
* schema) or an external validation failure (e.g., the component using this
* LDIF reader didn't accept the entry because it didn't have a parent).
*
* @return The total number of entries rejected so far by this LDIF reader.
*/
public long getEntriesRejected()
{
return entriesRejected.get();
}
/**
* Parse a modifyDN change record entry from LDIF.
*
* @param entryDN
* The name of the entry being modified.
* @param lines
* The lines to parse.
* @return Returns the parsed modifyDN change record entry.
* @throws LDIFException
* If there was an error when parsing the change record.
*/
boolean deleteOldRDN;
{
}
try
{
} catch (DirectoryException de)
{
} catch (Exception e)
{
logger.traceException(e);
}
{
}
lineNumber++;
entryDN, "deleteoldrdn");
{
deleteOldRDN = false;
{
deleteOldRDN = true;
} else
{
}
{
lineNumber++;
entryDN, "newsuperior");
try
{
} catch (DirectoryException de)
{
} catch (Exception e)
{
logger.traceException(e);
}
}
}
/**
* Return the string value for the specified attribute name which only
* has one value.
*
* @param lines
* The set of lines for this change record entry.
* @param line
* The line currently being examined.
* @param entryDN
* The name of the entry being modified.
* @param attributeName
* The attribute name
* @return the string value for the attribute name.
* @throws LDIFException
* If a problem occurs while attempting to determine the
* attribute value.
*/
{
}
/**
* Parse a modify change record entry from LDIF.
*
* @param entryDN
* The name of the entry being modified.
* @param lines
* The lines to parse.
* @return Returns the parsed modify change record entry.
* @throws LDIFException
* If there was an error when parsing the change record.
*/
{
// Get the attribute description
{
}
{
}
{
}
{
}
else
{
// Invalid attribute name.
"add, delete, replace, increment");
}
// Now go through the rest of the attributes till the "-" line is reached.
{
{
break;
}
}
}
}
/**
* Parse a delete change record entry from LDIF.
*
* @param entryDN
* The name of the entry being deleted.
* @param lines
* The lines to parse.
* @return Returns the parsed delete change record entry.
* @throws LDIFException
* If there was an error when parsing the change record.
*/
{
{
}
return new DeleteChangeRecordEntry(entryDN);
}
/**
* Parse an add change record entry from LDIF.
*
* @param entryDN
* The name of the entry being added.
* @param lines
* The lines to parse.
* @return Returns the parsed add change record entry.
* @throws LDIFException
* If there was an error when parsing the change record.
*/
{
{
}
// Reconstruct the object class attribute.
}
{
}
}
/**
* Parse colon position in an attribute description.
*
* @param lines
* The current set of lines.
* @param line
* The current line.
* @return The colon position.
* @throws LDIFException
* If the colon was badly placed or not found.
*/
if (colonPos <= 0)
{
}
return colonPos;
}
/**
* Parse a single attribute value from a line of LDIF.
*
* @param lines
* The current set of lines.
* @param line
* The current line.
* @param entryDN
* The DN of the entry being parsed.
* @param colonPos
* The position of the separator colon in the line.
* @param attrName
* The name of the attribute being parsed.
* @return The parsed attribute value.
* @throws LDIFException
* If an error occurred when parsing the attribute value.
*/
private ByteString parseSingleValue(
int colonPos,
// Look at the character immediately after the colon. If there is
// none, then assume an attribute with an empty value. If it is another
// colon, then the value must be base64-encoded. If it is a less-than
// sign, then assume that it is a URL. Otherwise, it is a regular value.
{
}
else
{
if (c == ':')
{
// The value is base64-encoded. Find the first non-blank
// character, take the rest of the line, and base64-decode it.
try
{
}
catch (Exception e)
{
// The value did not have a valid base64-encoding.
logger.traceException(e);
}
}
else if (c == '<')
{
// Find the first non-blank character, decode the rest of the
// line as a URL, and read its contents.
try
{
}
catch (Exception e)
{
// The URL was malformed or had an invalid protocol.
logger.traceException(e);
}
try
{
}
catch (Exception e)
{
// We were unable to read the contents of that URL for some reason.
logger.traceException(e);
}
finally
{
}
}
else
{
// The rest of the line should be the value. Skip over any
// spaces and take the rest of the line as the value.
}
}
return value;
}
/**
* Log a message to the reject writer if one is configured.
*
* @param lines
* The set of rejected lines.
* @param message
* The associated error message.
*/
{
if (rejectWriter != null)
{
}
}
/**
* Log a message to the reject writer if one is configured.
*
* @param lines
* The set of rejected lines.
* @param message
* The associated error message.
*/
{
if (skipWriter != null)
{
}
}
/**
* Log a message to the given writer.
*
* @param writer
* The writer to write to.
* @param lines
* The set of rejected lines.
* @param message
* The associated error message.
*/
{
{
try
{
{
}
}
catch (Exception e)
{
logger.traceException(e);
}
}
}
/**
* Adds any missing RDN attributes to the entry that is being imported.
* @param entryDN the entry DN
* @param userAttributes the user attributes
* @param operationalAttributes the operational attributes
*/
{
for (int i=0; i < numAVAs; i++)
{
if (t.isOperational())
{
addRDNAttributesIfNecessary(operationalAttributes, t, v, n);
}
else
{
addRDNAttributesIfNecessary(userAttributes, t, v, n);
}
}
}
private void addRDNAttributesIfNecessary(
ByteString v, String n)
{
{
}
else
{
boolean found = false;
{
if (a.hasOptions())
{
continue;
}
if (!a.contains(v))
{
}
found = true;
break;
}
if (!found)
{
}
}
}
}