LDIFReader.java revision 868
0N/A/*
0N/A * CDDL HEADER START
0N/A *
0N/A * The contents of this file are subject to the terms of the
0N/A * Common Development and Distribution License, Version 1.0 only
0N/A * (the "License"). You may not use this file except in compliance
0N/A * with the License.
0N/A *
0N/A * You can obtain a copy of the license at
0N/A * trunk/opends/resource/legal-notices/OpenDS.LICENSE
0N/A * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
0N/A * See the License for the specific language governing permissions
0N/A * and limitations under the License.
0N/A *
0N/A * When distributing Covered Code, include this CDDL HEADER in each
0N/A * file and include the License file at
0N/A * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
0N/A * add the following below this CDDL HEADER, with the fields enclosed
0N/A * by brackets "[]" replaced with your own identifying * information:
0N/A * Portions Copyright [yyyy] [name of copyright owner]
0N/A *
0N/A * CDDL HEADER END
0N/A *
0N/A *
733N/A * Portions Copyright 2006-2007 Sun Microsystems, Inc.
0N/A */
0N/Apackage org.opends.server.util;
0N/A
0N/A
0N/A
868N/Aimport static org.opends.server.loggers.debug.DebugLogger.debugEnabled;
868N/Aimport static org.opends.server.loggers.debug.DebugLogger.debugVerbose;
868N/Aimport static org.opends.server.loggers.debug.DebugLogger.debugInfo;
868N/Aimport static org.opends.server.loggers.debug.DebugLogger.debugCought;
868N/Aimport static org.opends.server.loggers.debug.DebugLogger.debugProtocolElement;
165N/Aimport static org.opends.server.loggers.Error.logError;
165N/Aimport static org.opends.server.messages.MessageHandler.getMessage;
165N/Aimport static org.opends.server.messages.UtilityMessages.*;
165N/Aimport static org.opends.server.util.StaticUtils.toLowerCase;
402N/Aimport static org.opends.server.util.Validator.*;
165N/A
0N/Aimport java.io.BufferedReader;
0N/Aimport java.io.BufferedWriter;
0N/Aimport java.io.ByteArrayOutputStream;
165N/Aimport java.io.IOException;
0N/Aimport java.io.InputStream;
0N/Aimport java.net.URL;
0N/Aimport java.util.ArrayList;
0N/Aimport java.util.HashMap;
0N/Aimport java.util.LinkedHashSet;
0N/Aimport java.util.LinkedList;
0N/Aimport java.util.List;
0N/A
0N/Aimport org.opends.server.api.plugin.LDIFPluginResult;
0N/Aimport org.opends.server.core.DirectoryServer;
0N/Aimport org.opends.server.core.PluginConfigManager;
0N/Aimport org.opends.server.protocols.asn1.ASN1OctetString;
165N/Aimport org.opends.server.protocols.ldap.LDAPAttribute;
165N/Aimport org.opends.server.protocols.ldap.LDAPModification;
0N/Aimport org.opends.server.types.Attribute;
0N/Aimport org.opends.server.types.AttributeType;
0N/Aimport org.opends.server.types.AttributeValue;
338N/Aimport org.opends.server.types.DirectoryException;
165N/Aimport org.opends.server.types.DN;
868N/Aimport org.opends.server.types.DebugLogLevel;
0N/Aimport org.opends.server.types.Entry;
0N/Aimport org.opends.server.types.ErrorLogCategory;
0N/Aimport org.opends.server.types.ErrorLogSeverity;
0N/Aimport org.opends.server.types.LDIFImportConfig;
165N/Aimport org.opends.server.types.ModificationType;
0N/Aimport org.opends.server.types.ObjectClass;
0N/Aimport org.opends.server.types.RDN;
0N/A
0N/A
0N/A/**
0N/A * This class provides the ability to read information from an LDIF file. It
0N/A * provides support for both standard entries and change entries (as would be
0N/A * used with a tool like ldapmodify).
0N/A */
165N/Apublic final class LDIFReader
0N/A{
0N/A
0N/A
0N/A
0N/A // The reader that will be used to read the data.
0N/A private BufferedReader reader;
0N/A
0N/A // The buffer to use to read data from a URL.
0N/A private byte[] buffer;
0N/A
0N/A // The import configuration that specifies what should be imported.
0N/A private LDIFImportConfig importConfig;
0N/A
0N/A // The lines that comprise the body of the last entry read.
0N/A private LinkedList<StringBuilder> lastEntryBodyLines;
0N/A
0N/A // The lines that comprise the header (DN and any comments) for the last entry
0N/A // read.
0N/A private LinkedList<StringBuilder> lastEntryHeaderLines;
0N/A
0N/A // The number of entries that have been ignored by this LDIF reader because
0N/A // they didn't match the criteria.
0N/A private long entriesIgnored;
0N/A
0N/A // The number of entries that have been read by this LDIF reader, including
0N/A // those that were ignored because they didn't match the criteria, and
0N/A // including those that were rejected because they were invalid in some way.
0N/A private long entriesRead;
0N/A
0N/A // The number of entries that have been rejected by this LDIF reader.
0N/A private long entriesRejected;
0N/A
0N/A // The line number on which the last entry started.
0N/A private long lastEntryLineNumber;
0N/A
0N/A // The line number of the last line read from the LDIF file, starting with 1.
0N/A private long lineNumber;
0N/A
0N/A // The plugin config manager that will be used if we are to invoke plugins
0N/A // on the entries as they are read.
0N/A private PluginConfigManager pluginConfigManager;
0N/A
0N/A
0N/A
0N/A /**
0N/A * Creates a new LDIF reader that will read information from the specified
0N/A * file.
0N/A *
402N/A * @param importConfig The import configuration for this LDIF reader. It
402N/A * must not be <CODE>null</CODE>.
0N/A *
0N/A * @throws IOException If a problem occurs while opening the LDIF file for
0N/A * reading.
0N/A */
0N/A public LDIFReader(LDIFImportConfig importConfig)
0N/A throws IOException
0N/A {
0N/A
402N/A ensureNotNull(importConfig);
0N/A this.importConfig = importConfig;
0N/A
0N/A reader = importConfig.getReader();
0N/A buffer = new byte[4096];
0N/A entriesRead = 0;
0N/A entriesIgnored = 0;
0N/A entriesRejected = 0;
0N/A lineNumber = 0;
0N/A lastEntryLineNumber = -1;
0N/A lastEntryBodyLines = new LinkedList<StringBuilder>();
0N/A lastEntryHeaderLines = new LinkedList<StringBuilder>();
0N/A pluginConfigManager = DirectoryServer.getPluginConfigManager();
0N/A }
0N/A
0N/A
0N/A
0N/A /**
0N/A * Reads the next entry from the LDIF source.
0N/A *
0N/A * @return The next entry read from the LDIF source, or <CODE>null</CODE> if
0N/A * the end of the LDIF data is reached.
0N/A *
0N/A * @throws IOException If an I/O problem occurs while reading from the file.
0N/A *
0N/A * @throws LDIFException If the information read cannot be parsed as an LDIF
0N/A * entry.
0N/A */
0N/A public Entry readEntry()
0N/A throws IOException, LDIFException
0N/A {
0N/A
0N/A return readEntry(importConfig.validateSchema());
0N/A }
0N/A
0N/A
0N/A
0N/A /**
0N/A * Reads the next entry from the LDIF source.
0N/A *
0N/A * @param checkSchema Indicates whether this reader should perform schema
0N/A * checking on the entry before returning it to the
0N/A * caller. Note that some basic schema checking (like
0N/A * refusing multiple values for a single-valued
0N/A * attribute) may always be performed.
0N/A *
0N/A *
0N/A * @return The next entry read from the LDIF source, or <CODE>null</CODE> if
0N/A * the end of the LDIF data is reached.
0N/A *
0N/A * @throws IOException If an I/O problem occurs while reading from the file.
0N/A *
0N/A * @throws LDIFException If the information read cannot be parsed as an LDIF
0N/A * entry.
0N/A */
0N/A public Entry readEntry(boolean checkSchema)
0N/A throws IOException, LDIFException
0N/A {
0N/A
0N/A
0N/A while (true)
0N/A {
0N/A // Read the set of lines that make up the next entry.
0N/A LinkedList<StringBuilder> lines = readEntryLines();
0N/A if (lines == null)
0N/A {
0N/A return null;
0N/A }
0N/A lastEntryBodyLines = lines;
0N/A lastEntryHeaderLines = new LinkedList<StringBuilder>();
0N/A
0N/A
0N/A // Read the DN of the entry and see if it is one that should be included
0N/A // in the import.
0N/A DN entryDN = readDN(lines);
0N/A if (entryDN == null)
0N/A {
0N/A // This should only happen if the LDIF starts with the "version:" line
0N/A // and has a blank line immediately after that. In that case, simply
0N/A // read and return the next entry.
0N/A continue;
0N/A }
868N/A else if (!importConfig.includeEntry(entryDN))
0N/A {
868N/A if (debugEnabled())
868N/A {
868N/A debugInfo("Skipping entry %s because the DN is not one that should " +
868N/A "be included based on the include and exclude branches.",
868N/A entryDN);
868N/A }
0N/A entriesRead++;
0N/A entriesIgnored++;
0N/A continue;
0N/A }
0N/A else
0N/A {
0N/A entriesRead++;
0N/A }
0N/A
0N/A // Read the set of attributes from the entry.
0N/A HashMap<ObjectClass,String> objectClasses =
0N/A new HashMap<ObjectClass,String>();
0N/A HashMap<AttributeType,List<Attribute>> userAttributes =
0N/A new HashMap<AttributeType,List<Attribute>>();
0N/A HashMap<AttributeType,List<Attribute>> operationalAttributes =
0N/A new HashMap<AttributeType,List<Attribute>>();
16N/A try
0N/A {
16N/A for (StringBuilder line : lines)
16N/A {
16N/A readAttribute(lines, line, entryDN, objectClasses, userAttributes,
16N/A operationalAttributes);
16N/A }
0N/A }
16N/A catch (LDIFException e)
16N/A {
16N/A entriesRejected++;
16N/A throw e;
16N/A }
0N/A
0N/A // Create the entry and see if it is one that should be included in the
0N/A // import.
0N/A Entry entry = new Entry(entryDN, objectClasses, userAttributes,
0N/A operationalAttributes);
868N/A debugProtocolElement(DebugLogLevel.VERBOSE, entry);
0N/A
0N/A try
0N/A {
0N/A if (! importConfig.includeEntry(entry))
0N/A {
868N/A if (debugEnabled())
868N/A {
868N/A debugInfo("Skipping entry %s because the DN is not one that " +
868N/A "should be included based on the include and exclude filters.",
868N/A entryDN);
868N/A }
0N/A entriesIgnored++;
0N/A continue;
0N/A }
0N/A }
0N/A catch (Exception e)
0N/A {
868N/A if (debugEnabled())
868N/A {
868N/A debugCought(DebugLogLevel.ERROR, e);
868N/A }
0N/A
0N/A int msgID = MSGID_LDIF_COULD_NOT_EVALUATE_FILTERS_FOR_IMPORT;
0N/A String message = getMessage(msgID, String.valueOf(entry.getDN()),
0N/A lastEntryLineNumber, String.valueOf(e));
0N/A throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
0N/A }
0N/A
0N/A
0N/A // If we should invoke import plugins, then do so.
0N/A if (importConfig.invokeImportPlugins())
0N/A {
0N/A LDIFPluginResult pluginResult =
0N/A pluginConfigManager.invokeLDIFImportPlugins(importConfig, entry);
0N/A if (! pluginResult.continueEntryProcessing())
0N/A {
0N/A entriesIgnored++;
0N/A continue;
0N/A }
0N/A }
0N/A
0N/A
0N/A // Make sure that the entry is valid as per the server schema if it is
0N/A // appropriate to do so.
0N/A if (checkSchema)
0N/A {
0N/A StringBuilder invalidReason = new StringBuilder();
723N/A if (! entry.conformsToSchema(null, false, true, false, invalidReason))
0N/A {
0N/A int msgID = MSGID_LDIF_SCHEMA_VIOLATION;
0N/A String message = getMessage(msgID, String.valueOf(entryDN),
0N/A lastEntryLineNumber,
0N/A invalidReason.toString());
165N/A logToRejectWriter(lines, message);
0N/A entriesRejected++;
0N/A throw new LDIFException(msgID, message, lastEntryLineNumber, true);
0N/A }
0N/A }
0N/A
0N/A
0N/A // The entry should be included in the import, so return it.
0N/A return entry;
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * Reads the next change record from the LDIF source.
0N/A *
0N/A * @param defaultAdd Indicates whether the change type should default to
0N/A * "add" if none is explicitly provided.
0N/A *
0N/A * @return The next change record from the LDIF source, or <CODE>null</CODE>
0N/A * if the end of the LDIF data is reached.
0N/A *
0N/A * @throws IOException If an I/O problem occurs while reading from the file.
0N/A *
0N/A * @throws LDIFException If the information read cannot be parsed as an LDIF
0N/A * entry.
0N/A */
0N/A public ChangeRecordEntry readChangeRecord(boolean defaultAdd)
0N/A throws IOException, LDIFException
0N/A {
0N/A
0N/A while (true)
0N/A {
0N/A // Read the set of lines that make up the next entry.
0N/A LinkedList<StringBuilder> lines = readEntryLines();
0N/A if (lines == null)
0N/A {
0N/A return null;
0N/A }
0N/A
0N/A
0N/A // Read the DN of the entry and see if it is one that should be included
0N/A // in the import.
0N/A DN entryDN = readDN(lines);
0N/A if (entryDN == null)
0N/A {
0N/A // This should only happen if the LDIF starts with the "version:" line
0N/A // and has a blank line immediately after that. In that case, simply
0N/A // read and return the next entry.
0N/A continue;
0N/A }
0N/A
0N/A String changeType = readChangeType(lines);
0N/A
0N/A ChangeRecordEntry entry = null;
0N/A
0N/A if(changeType != null)
0N/A {
0N/A if(changeType.equals("add"))
0N/A {
165N/A entry = parseAddChangeRecordEntry(entryDN, lines);
0N/A } else if (changeType.equals("delete"))
0N/A {
165N/A entry = parseDeleteChangeRecordEntry(entryDN, lines);
0N/A } else if (changeType.equals("modify"))
0N/A {
165N/A entry = parseModifyChangeRecordEntry(entryDN, lines);
0N/A } else if (changeType.equals("modrdn"))
0N/A {
165N/A entry = parseModifyDNChangeRecordEntry(entryDN, lines);
0N/A } else if (changeType.equals("moddn"))
0N/A {
165N/A entry = parseModifyDNChangeRecordEntry(entryDN, lines);
0N/A } else
0N/A {
0N/A int msgID = MSGID_LDIF_INVALID_CHANGETYPE_ATTRIBUTE;
0N/A String message = getMessage(msgID, changeType,
0N/A "add, delete, modify, moddn, modrdn");
0N/A throw new LDIFException(msgID, message, lastEntryLineNumber, false);
0N/A }
0N/A } else
0N/A {
0N/A // default to "add"?
0N/A if(defaultAdd)
0N/A {
165N/A entry = parseAddChangeRecordEntry(entryDN, lines);
0N/A } else
0N/A {
0N/A int msgID = MSGID_LDIF_INVALID_CHANGETYPE_ATTRIBUTE;
0N/A String message = getMessage(msgID, null,
0N/A "add, delete, modify, moddn, modrdn");
0N/A throw new LDIFException(msgID, message, lastEntryLineNumber, false);
0N/A }
0N/A }
0N/A
0N/A return entry;
0N/A }
0N/A }
0N/A
0N/A
0N/A
0N/A /**
0N/A * Reads a set of lines from the next entry in the LDIF source.
0N/A *
0N/A * @return A set of lines from the next entry in the LDIF source.
0N/A *
0N/A * @throws IOException If a problem occurs while reading from the LDIF
0N/A * source.
0N/A *
0N/A * @throws LDIFException If the information read is not valid LDIF.
0N/A */
0N/A private LinkedList<StringBuilder> readEntryLines()
0N/A throws IOException, LDIFException
0N/A {
0N/A
0N/A // Read the entry lines into a buffer.
0N/A LinkedList<StringBuilder> lines = new LinkedList<StringBuilder>();
0N/A int lastLine = -1;
0N/A
0N/A while (true)
0N/A {
0N/A String line = reader.readLine();
0N/A lineNumber++;
0N/A
0N/A if (line == null)
0N/A {
0N/A // This must mean that we have reached the end of the LDIF source.
0N/A // If the set of lines read so far is empty, then move onto the next
0N/A // file or return null. Otherwise, break out of this loop.
0N/A if (lines.isEmpty())
0N/A {
0N/A reader = importConfig.nextReader();
0N/A if (reader == null)
0N/A {
0N/A return null;
0N/A }
0N/A else
0N/A {
0N/A return readEntryLines();
0N/A }
0N/A }
0N/A else
0N/A {
0N/A break;
0N/A }
0N/A }
0N/A else if (line.length() == 0)
0N/A {
0N/A // This is a blank line. If the set of lines read so far is empty,
0N/A // then just skip over it. Otherwise, break out of this loop.
0N/A if (lines.isEmpty())
0N/A {
0N/A continue;
0N/A }
0N/A else
0N/A {
0N/A break;
0N/A }
0N/A }
0N/A else if (line.charAt(0) == '#')
0N/A {
0N/A // This is a comment. Ignore it.
0N/A continue;
0N/A }
465N/A else if ((line.charAt(0) == ' ') || (line.charAt(0) == '\t'))
0N/A {
0N/A // This is a continuation of the previous line. If there is no
465N/A // previous line, then that's a problem. Note that while RFC 2849
465N/A // technically only allows a space in this position, both OpenLDAP and
465N/A // the Sun Java System Directory Server allow a tab as well, so we will
465N/A // too for compatibility reasons. See issue #852 for details.
0N/A if (lastLine >= 0)
0N/A {
0N/A lines.get(lastLine).append(line.substring(1));
0N/A }
0N/A else
0N/A {
0N/A int msgID = MSGID_LDIF_INVALID_LEADING_SPACE;
0N/A String message = getMessage(msgID, lineNumber, line);
165N/A logToRejectWriter(lines, message);
0N/A throw new LDIFException(msgID, message, lineNumber, false);
0N/A }
0N/A }
0N/A else
0N/A {
0N/A // This is a new line.
0N/A if (lines.isEmpty())
0N/A {
0N/A lastEntryLineNumber = lineNumber;
0N/A }
0N/A lines.add(new StringBuilder(line));
0N/A lastLine++;
0N/A }
0N/A }
0N/A
0N/A
0N/A return lines;
0N/A }
0N/A
0N/A
0N/A
0N/A /**
0N/A * Reads the DN of the entry from the provided list of lines. The DN must be
0N/A * the first line in the list, unless the first line starts with "version",
0N/A * in which case the DN should be the second line.
0N/A *
0N/A * @param lines The set of lines from which the DN should be read.
0N/A *
0N/A * @return The decoded entry DN.
0N/A *
0N/A * @throws LDIFException If DN is not the first element in the list (or the
0N/A * second after the LDIF version), or if a problem
0N/A * occurs while trying to parse it.
0N/A */
0N/A private DN readDN(LinkedList<StringBuilder> lines)
0N/A throws LDIFException
0N/A {
0N/A
0N/A if (lines.isEmpty())
0N/A {
0N/A // This is possible if the contents of the first "entry" were just
0N/A // the version identifier. If that is the case, then return null and
0N/A // use that as a signal to the caller to go ahead and read the next entry.
0N/A return null;
0N/A }
0N/A
0N/A StringBuilder line = lines.remove();
0N/A lastEntryHeaderLines.add(line);
0N/A int colonPos = line.indexOf(":");
0N/A if (colonPos <= 0)
0N/A {
0N/A int msgID = MSGID_LDIF_NO_ATTR_NAME;
0N/A String message = getMessage(msgID, lastEntryLineNumber, line.toString());
0N/A
165N/A logToRejectWriter(lines, message);
0N/A
0N/A throw new LDIFException(msgID, message, lastEntryLineNumber, true);
0N/A }
0N/A
0N/A String attrName = toLowerCase(line.substring(0, colonPos));
0N/A if (attrName.equals("version"))
0N/A {
0N/A // This is the version line, and we can skip it.
0N/A return readDN(lines);
0N/A }
0N/A else if (! attrName.equals("dn"))
0N/A {
0N/A int msgID = MSGID_LDIF_NO_DN;
0N/A String message = getMessage(msgID, lastEntryLineNumber, line.toString());
0N/A
165N/A logToRejectWriter(lines, message);
0N/A
0N/A throw new LDIFException(msgID, message, lastEntryLineNumber, true);
0N/A }
0N/A
0N/A
0N/A // Look at the character immediately after the colon. If there is none,
0N/A // then assume the null DN. If it is another colon, then the DN must be
0N/A // base64-encoded. Otherwise, it may be one or more spaces.
0N/A int length = line.length();
0N/A if (colonPos == (length-1))
0N/A {
509N/A return DN.nullDN();
0N/A }
0N/A
0N/A if (line.charAt(colonPos+1) == ':')
0N/A {
0N/A // The DN is base64-encoded. Find the first non-blank character and
0N/A // take the rest of the line, base64-decode it, and parse it as a DN.
0N/A int pos = colonPos+2;
0N/A while ((pos < length) && (line.charAt(pos) == ' '))
0N/A {
0N/A pos++;
0N/A }
0N/A
0N/A String encodedDNStr = line.substring(pos);
0N/A
0N/A String dnStr;
0N/A try
0N/A {
0N/A dnStr = new String(Base64.decode(encodedDNStr), "UTF-8");
0N/A }
0N/A catch (Exception e)
0N/A {
0N/A // The value did not have a valid base64-encoding.
868N/A if (debugEnabled())
868N/A {
868N/A debugCought(DebugLogLevel.ERROR, e);
868N/A }
0N/A
0N/A int msgID = MSGID_LDIF_COULD_NOT_BASE64_DECODE_DN;
0N/A String message = getMessage(msgID, lastEntryLineNumber, line,
0N/A String.valueOf(e));
0N/A
165N/A logToRejectWriter(lines, message);
0N/A
0N/A throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
0N/A }
0N/A
0N/A try
0N/A {
0N/A return DN.decode(dnStr);
0N/A }
0N/A catch (DirectoryException de)
0N/A {
868N/A if (debugEnabled())
868N/A {
868N/A debugCought(DebugLogLevel.ERROR, de);
868N/A }
0N/A
0N/A int msgID = MSGID_LDIF_INVALID_DN;
0N/A String message = getMessage(msgID, lastEntryLineNumber, line.toString(),
0N/A de.getErrorMessage());
0N/A
165N/A logToRejectWriter(lines, message);
0N/A
0N/A throw new LDIFException(msgID, message, lastEntryLineNumber, true, de);
0N/A }
0N/A catch (Exception e)
0N/A {
868N/A if (debugEnabled())
868N/A {
868N/A debugCought(DebugLogLevel.ERROR, e);
868N/A }
0N/A
0N/A int msgID = MSGID_LDIF_INVALID_DN;
0N/A String message = getMessage(msgID, lastEntryLineNumber, line.toString(),
0N/A String.valueOf(e));
0N/A
165N/A logToRejectWriter(lines, message);
0N/A
0N/A throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
0N/A }
0N/A }
0N/A else
0N/A {
0N/A // The rest of the value should be the DN. Skip over any spaces and
0N/A // attempt to decode the rest of the line as the DN.
0N/A int pos = colonPos+1;
0N/A while ((pos < length) && (line.charAt(pos) == ' '))
0N/A {
0N/A pos++;
0N/A }
0N/A
0N/A String dnString = line.substring(pos);
0N/A
0N/A try
0N/A {
0N/A return DN.decode(dnString);
0N/A }
0N/A catch (DirectoryException de)
0N/A {
868N/A if (debugEnabled())
868N/A {
868N/A debugCought(DebugLogLevel.ERROR, de);
868N/A }
0N/A
0N/A int msgID = MSGID_LDIF_INVALID_DN;
0N/A String message = getMessage(msgID, lastEntryLineNumber, line.toString(),
0N/A de.getErrorMessage());
0N/A
165N/A logToRejectWriter(lines, message);
0N/A
0N/A throw new LDIFException(msgID, message, lastEntryLineNumber, true, de);
0N/A }
0N/A catch (Exception e)
0N/A {
868N/A if (debugEnabled())
868N/A {
868N/A debugCought(DebugLogLevel.ERROR, e);
868N/A }
0N/A
0N/A int msgID = MSGID_LDIF_INVALID_DN;
0N/A String message = getMessage(msgID, lastEntryLineNumber, line.toString(),
0N/A String.valueOf(e));
0N/A
165N/A logToRejectWriter(lines, message);
0N/A
0N/A throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
0N/A }
0N/A }
0N/A }
0N/A
0N/A
165N/A
0N/A /**
0N/A * Reads the changetype of the entry from the provided list of lines. If
0N/A * there is no changetype attribute then an add is assumed.
0N/A *
0N/A * @param lines The set of lines from which the DN should be read.
0N/A *
0N/A * @return The decoded entry DN.
0N/A *
0N/A * @throws LDIFException If DN is not the first element in the list (or the
0N/A * second after the LDIF version), or if a problem
0N/A * occurs while trying to parse it.
0N/A */
0N/A private String readChangeType(LinkedList<StringBuilder> lines)
0N/A throws LDIFException
0N/A {
0N/A
0N/A if (lines.isEmpty())
0N/A {
0N/A // Error. There must be other entries.
0N/A return null;
0N/A }
0N/A
0N/A StringBuilder line = lines.get(0);
0N/A lastEntryHeaderLines.add(line);
0N/A int colonPos = line.indexOf(":");
0N/A if (colonPos <= 0)
0N/A {
0N/A int msgID = MSGID_LDIF_NO_ATTR_NAME;
0N/A String message = getMessage(msgID, lastEntryLineNumber, line.toString());
165N/A logToRejectWriter(lines, message);
0N/A throw new LDIFException(msgID, message, lastEntryLineNumber, true);
0N/A }
0N/A
0N/A String attrName = toLowerCase(line.substring(0, colonPos));
0N/A if (! attrName.equals("changetype"))
0N/A {
0N/A // No changetype attribute - return null
0N/A return null;
0N/A } else
0N/A {
0N/A // Remove the line
165N/A lines.remove();
0N/A }
0N/A
0N/A
0N/A // Look at the character immediately after the colon. If there is none,
0N/A // then no value was specified. Throw an exception
0N/A int length = line.length();
0N/A if (colonPos == (length-1))
0N/A {
0N/A int msgID = MSGID_LDIF_INVALID_CHANGETYPE_ATTRIBUTE;
0N/A String message = getMessage(msgID, null,
0N/A "add, delete, modify, moddn, modrdn");
0N/A throw new LDIFException(msgID, message, lastEntryLineNumber, false );
0N/A }
0N/A
0N/A if (line.charAt(colonPos+1) == ':')
0N/A {
0N/A // The change type is base64-encoded. Find the first non-blank
0N/A // character and
0N/A // take the rest of the line, and base64-decode it.
0N/A int pos = colonPos+2;
0N/A while ((pos < length) && (line.charAt(pos) == ' '))
0N/A {
0N/A pos++;
0N/A }
0N/A
0N/A String encodedChangeTypeStr = line.substring(pos);
0N/A
0N/A String changeTypeStr;
0N/A try
0N/A {
0N/A changeTypeStr = new String(Base64.decode(encodedChangeTypeStr),
0N/A "UTF-8");
0N/A }
0N/A catch (Exception e)
0N/A {
0N/A // The value did not have a valid base64-encoding.
868N/A if (debugEnabled())
868N/A {
868N/A debugCought(DebugLogLevel.ERROR, e);
868N/A }
0N/A
0N/A int msgID = MSGID_LDIF_COULD_NOT_BASE64_DECODE_DN;
0N/A String message = getMessage(msgID, lastEntryLineNumber, line,
0N/A String.valueOf(e));
165N/A logToRejectWriter(lines, message);
0N/A throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
0N/A }
0N/A
0N/A return changeTypeStr;
0N/A }
0N/A else
0N/A {
0N/A // The rest of the value should be the changetype.
0N/A // Skip over any spaces and
0N/A // attempt to decode the rest of the line as the changetype string.
0N/A int pos = colonPos+1;
0N/A while ((pos < length) && (line.charAt(pos) == ' '))
0N/A {
0N/A pos++;
0N/A }
0N/A
0N/A String changeTypeString = line.substring(pos);
0N/A
0N/A return changeTypeString;
0N/A }
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Decodes the provided line as an LDIF attribute and adds it to the
0N/A * appropriate hash.
0N/A *
0N/A * @param lines The full set of lines that comprise the
0N/A * entry (used for writing reject information).
0N/A * @param line The line to decode.
0N/A * @param entryDN The DN of the entry being decoded.
0N/A * @param objectClasses The set of objectclasses decoded so far for
0N/A * the current entry.
0N/A * @param userAttributes The set of user attributes decoded so far
0N/A * for the current entry.
0N/A * @param operationalAttributes The set of operational attributes decoded so
0N/A * far for the current entry.
0N/A *
0N/A * @throws LDIFException If a problem occurs while trying to decode the
0N/A * attribute contained in the provided entry.
0N/A */
0N/A private void readAttribute(LinkedList<StringBuilder> lines,
0N/A StringBuilder line, DN entryDN,
0N/A HashMap<ObjectClass,String> objectClasses,
0N/A HashMap<AttributeType,List<Attribute>> userAttributes,
0N/A HashMap<AttributeType,List<Attribute>> operationalAttributes)
0N/A throws LDIFException
0N/A {
0N/A
165N/A // Parse the attribute type description.
165N/A int colonPos = parseColonPosition(lines, line);
165N/A String attrDescr = line.substring(0, colonPos);
165N/A Attribute attribute = parseAttrDescription(attrDescr);
165N/A String attrName = attribute.getName();
165N/A String lowerName = toLowerCase(attrName);
165N/A LinkedHashSet<String> options = attribute.getOptions();
0N/A
165N/A // Now parse the attribute value.
165N/A ASN1OctetString value = parseSingleValue(lines, line, entryDN,
165N/A colonPos, attrName);
0N/A
0N/A // See if this is an objectclass or an attribute. Then get the
0N/A // corresponding definition and add the value to the appropriate hash.
0N/A if (lowerName.equals("objectclass"))
0N/A {
0N/A if (! importConfig.includeObjectClasses())
0N/A {
868N/A if (debugEnabled())
868N/A {
868N/A debugVerbose("Skipping objectclass %s for entry %s due to the " +
868N/A "import configuration.", value, entryDN);
868N/A }
0N/A return;
0N/A }
0N/A
0N/A String ocName = value.stringValue();
0N/A String lowerOCName = toLowerCase(ocName);
0N/A
0N/A ObjectClass objectClass = DirectoryServer.getObjectClass(lowerOCName);
0N/A if (objectClass == null)
0N/A {
0N/A objectClass = DirectoryServer.getDefaultObjectClass(ocName);
0N/A }
0N/A
0N/A if (objectClasses.containsKey(objectClass))
0N/A {
0N/A logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.MILD_WARNING,
0N/A MSGID_LDIF_DUPLICATE_OBJECTCLASS, String.valueOf(entryDN),
0N/A lastEntryLineNumber, ocName);
0N/A }
0N/A else
0N/A {
0N/A objectClasses.put(objectClass, ocName);
0N/A }
0N/A }
0N/A else
0N/A {
0N/A AttributeType attrType = DirectoryServer.getAttributeType(lowerName);
0N/A if (attrType == null)
0N/A {
0N/A attrType = DirectoryServer.getDefaultAttributeType(attrName);
0N/A }
0N/A
0N/A
0N/A if (! importConfig.includeAttribute(attrType))
0N/A {
868N/A if (debugEnabled())
868N/A {
868N/A debugVerbose("Skipping attribute %s for entry %s due to the import " +
868N/A "configuration.", attrName, entryDN);
868N/A }
0N/A return;
0N/A }
0N/A
0N/A
165N/A AttributeValue attributeValue = new AttributeValue(attrType, value);
0N/A List<Attribute> attrList;
0N/A if (attrType.isOperational())
0N/A {
0N/A attrList = operationalAttributes.get(attrType);
0N/A if (attrList == null)
0N/A {
0N/A LinkedHashSet<AttributeValue> valueSet =
0N/A new LinkedHashSet<AttributeValue>();
0N/A valueSet.add(attributeValue);
0N/A
0N/A attrList = new ArrayList<Attribute>();
0N/A attrList.add(new Attribute(attrType, attrName, options, valueSet));
0N/A operationalAttributes.put(attrType, attrList);
0N/A return;
0N/A }
0N/A }
0N/A else
0N/A {
0N/A attrList = userAttributes.get(attrType);
0N/A if (attrList == null)
0N/A {
0N/A LinkedHashSet<AttributeValue> valueSet =
0N/A new LinkedHashSet<AttributeValue>();
0N/A valueSet.add(attributeValue);
0N/A
0N/A attrList = new ArrayList<Attribute>();
0N/A attrList.add(new Attribute(attrType, attrName, options, valueSet));
0N/A userAttributes.put(attrType, attrList);
0N/A return;
0N/A }
0N/A }
0N/A
0N/A
0N/A // Check to see if any of the attributes in the list have the same set of
0N/A // options. If so, then try to add a value to that attribute.
0N/A for (Attribute a : attrList)
0N/A {
0N/A if (a.optionsEqual(options))
0N/A {
0N/A LinkedHashSet<AttributeValue> valueSet = a.getValues();
0N/A if (valueSet.contains(attributeValue))
0N/A {
0N/A int msgID = MSGID_LDIF_DUPLICATE_ATTR;
0N/A String message = getMessage(msgID, String.valueOf(entryDN),
0N/A lastEntryLineNumber, attrName,
0N/A value.stringValue());
165N/A logToRejectWriter(lines, message);
0N/A throw new LDIFException(msgID, message, lastEntryLineNumber, true);
0N/A }
0N/A else if (attrType.isSingleValue() && (! valueSet.isEmpty()))
0N/A {
0N/A int msgID = MSGID_LDIF_MULTIPLE_VALUES_FOR_SINGLE_VALUED_ATTR;
0N/A String message = getMessage(msgID, String.valueOf(entryDN),
0N/A lastEntryLineNumber, attrName);
165N/A logToRejectWriter(lines, message);
0N/A throw new LDIFException(msgID, message, lastEntryLineNumber, true);
0N/A }
0N/A else
0N/A {
0N/A valueSet.add(attributeValue);
0N/A return;
0N/A }
0N/A }
0N/A }
0N/A
0N/A
0N/A // No set of matching options was found, so create a new one and add it to
0N/A // the list.
0N/A LinkedHashSet<AttributeValue> valueSet =
0N/A new LinkedHashSet<AttributeValue>();
0N/A valueSet.add(attributeValue);
0N/A attrList.add(new Attribute(attrType, attrName, options, valueSet));
0N/A return;
0N/A }
0N/A }
0N/A
0N/A
0N/A
0N/A /**
0N/A * Decodes the provided line as an LDIF attribute and returns the
0N/A * Attribute (name and values) for the specified attribute name.
0N/A *
0N/A * @param lines The full set of lines that comprise the
0N/A * entry (used for writing reject information).
0N/A * @param line The line to decode.
0N/A * @param entryDN The DN of the entry being decoded.
770N/A * @param attributeName The name and options of the attribute to
770N/A * return the values for.
0N/A *
0N/A * @return The attribute in octet string form.
0N/A * @throws LDIFException If a problem occurs while trying to decode
0N/A * the attribute contained in the provided
0N/A * entry or if the parsed attribute name does
0N/A * not match the specified attribute name.
0N/A */
165N/A private Attribute readSingleValueAttribute(
0N/A LinkedList<StringBuilder> lines, StringBuilder line, DN entryDN,
0N/A String attributeName) throws LDIFException
0N/A {
0N/A
165N/A // Parse the attribute type description.
165N/A int colonPos = parseColonPosition(lines, line);
0N/A String attrDescr = line.substring(0, colonPos);
0N/A Attribute attribute = parseAttrDescription(attrDescr);
0N/A String attrName = attribute.getName();
0N/A
770N/A if (attributeName != null)
0N/A {
770N/A Attribute expectedAttr = parseAttrDescription(attributeName);
770N/A
770N/A if (!attribute.equals(expectedAttr))
770N/A {
770N/A int msgID = MSGID_LDIF_INVALID_CHANGERECORD_ATTRIBUTE;
770N/A String message = getMessage(msgID, attrDescr, attributeName);
770N/A throw new LDIFException(msgID, message, lastEntryLineNumber, false);
770N/A }
0N/A }
0N/A
165N/A // Now parse the attribute value.
165N/A ASN1OctetString value = parseSingleValue(lines, line, entryDN,
165N/A colonPos, attrName);
0N/A
0N/A AttributeType attrType = attribute.getAttributeType();
165N/A AttributeValue attributeValue = new AttributeValue(attrType, value);
0N/A attribute.getValues().add(attributeValue);
0N/A
0N/A return attribute;
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Retrieves the starting line number for the last entry read from the LDIF
0N/A * source.
0N/A *
0N/A * @return The starting line number for the last entry read from the LDIF
0N/A * source.
0N/A */
0N/A public long getLastEntryLineNumber()
0N/A {
0N/A
0N/A return lastEntryLineNumber;
0N/A }
0N/A
0N/A
0N/A
0N/A /**
0N/A * Rejects the last entry read from the LDIF. This method is intended for use
0N/A * by components that perform their own validation of entries (e.g., backends
0N/A * during import processing) in which the entry appeared valid to the LDIF
0N/A * reader but some other problem was encountered.
0N/A *
0N/A * @param message A human-readable message providing the reason that the
0N/A * last entry read was not acceptable.
0N/A */
0N/A public void rejectLastEntry(String message)
0N/A {
0N/A
0N/A entriesRejected++;
0N/A
0N/A BufferedWriter rejectWriter = importConfig.getRejectWriter();
0N/A if (rejectWriter != null)
0N/A {
0N/A try
0N/A {
402N/A if ((message != null) && (message.length() > 0))
402N/A {
402N/A rejectWriter.write("# ");
402N/A rejectWriter.write(message);
402N/A rejectWriter.newLine();
402N/A }
0N/A
0N/A for (StringBuilder sb : lastEntryHeaderLines)
0N/A {
0N/A rejectWriter.write(sb.toString());
0N/A rejectWriter.newLine();
0N/A }
0N/A
0N/A for (StringBuilder sb : lastEntryBodyLines)
0N/A {
0N/A rejectWriter.write(sb.toString());
0N/A rejectWriter.newLine();
0N/A }
0N/A
0N/A rejectWriter.newLine();
0N/A }
0N/A catch (Exception e)
0N/A {
868N/A if (debugEnabled())
868N/A {
868N/A debugCought(DebugLogLevel.ERROR, e);
868N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A
0N/A
0N/A /**
0N/A * Closes this LDIF reader and the underlying file or input stream.
0N/A */
0N/A public void close()
0N/A {
0N/A
0N/A importConfig.close();
0N/A }
0N/A
0N/A
0N/A
0N/A /**
0N/A * Parse an AttributeDescription (an attribute type name and its options).
0N/A * @param attrDescr The attribute description to be parsed.
0N/A * @return A new attribute with no values, representing the attribute type
0N/A * and its options.
0N/A */
165N/A private static Attribute parseAttrDescription(String attrDescr)
0N/A {
0N/A
0N/A String attrName;
0N/A String lowerName;
0N/A LinkedHashSet<String> options;
0N/A int semicolonPos = attrDescr.indexOf(';');
0N/A if (semicolonPos > 0)
0N/A {
0N/A attrName = attrDescr.substring(0, semicolonPos);
0N/A options = new LinkedHashSet<String>();
0N/A int nextPos = attrDescr.indexOf(';', semicolonPos+1);
0N/A while (nextPos > 0)
0N/A {
0N/A String option = attrDescr.substring(semicolonPos+1, nextPos);
0N/A if (option.length() > 0)
0N/A {
0N/A options.add(option);
0N/A semicolonPos = nextPos;
0N/A nextPos = attrDescr.indexOf(';', semicolonPos+1);
0N/A }
0N/A }
0N/A
0N/A String option = attrDescr.substring(semicolonPos+1);
0N/A if (option.length() > 0)
0N/A {
0N/A options.add(option);
0N/A }
0N/A }
0N/A else
0N/A {
0N/A attrName = attrDescr;
0N/A options = null;
0N/A }
0N/A
0N/A lowerName = toLowerCase(attrName);
0N/A AttributeType attrType = DirectoryServer.getAttributeType(lowerName);
0N/A if (attrType == null)
0N/A {
0N/A attrType = DirectoryServer.getDefaultAttributeType(attrName);
0N/A }
0N/A
0N/A return new Attribute(attrType, attrName, options, null);
0N/A }
0N/A
0N/A
0N/A
0N/A /**
0N/A * Retrieves the total number of entries read so far by this LDIF reader,
0N/A * including those that have been ignored or rejected.
0N/A *
0N/A * @return The total number of entries read so far by this LDIF reader.
0N/A */
0N/A public long getEntriesRead()
0N/A {
0N/A
0N/A return entriesRead;
0N/A }
0N/A
0N/A
0N/A
0N/A /**
0N/A * Retrieves the total number of entries that have been ignored so far by this
0N/A * LDIF reader because they did not match the import criteria.
0N/A *
0N/A * @return The total number of entries ignored so far by this LDIF reader.
0N/A */
0N/A public long getEntriesIgnored()
0N/A {
0N/A
0N/A return entriesIgnored;
0N/A }
0N/A
0N/A
0N/A
0N/A /**
0N/A * Retrieves the total number of entries rejected so far by this LDIF reader.
0N/A * This includes both entries that were rejected because of internal
0N/A * validation failure (e.g., they didn't conform to the defined server
0N/A * schema) or an external validation failure (e.g., the component using this
0N/A * LDIF reader didn't accept the entry because it didn't have a parent).
0N/A *
0N/A * @return The total number of entries rejected so far by this LDIF reader.
0N/A */
0N/A public long getEntriesRejected()
0N/A {
0N/A
0N/A return entriesRejected;
0N/A }
165N/A
165N/A
165N/A
165N/A /**
165N/A * Parse a modifyDN change record entry from LDIF.
165N/A *
165N/A * @param entryDN
165N/A * The name of the entry being modified.
165N/A * @param lines
165N/A * The lines to parse.
165N/A * @return Returns the parsed modifyDN change record entry.
165N/A * @throws LDIFException
165N/A * If there was an error when parsing the change record.
165N/A */
165N/A private ChangeRecordEntry parseModifyDNChangeRecordEntry(DN entryDN,
165N/A LinkedList<StringBuilder> lines) throws LDIFException {
165N/A
165N/A DN newSuperiorDN = null;
165N/A RDN newRDN = null;
165N/A boolean deleteOldRDN = false;
165N/A
165N/A if(lines.isEmpty())
165N/A {
165N/A int msgID = MSGID_LDIF_NO_MOD_DN_ATTRIBUTES;
165N/A String message = getMessage(msgID);
165N/A throw new LDIFException(msgID, message, lineNumber, true);
165N/A }
165N/A
165N/A StringBuilder line = lines.remove();
165N/A String rdnStr = getModifyDNAttributeValue(lines, line, entryDN, "newrdn");
165N/A
165N/A try
165N/A {
165N/A newRDN = RDN.decode(rdnStr);
165N/A } catch (DirectoryException de)
165N/A {
868N/A if (debugEnabled())
868N/A {
868N/A debugCought(DebugLogLevel.ERROR, de);
868N/A }
165N/A int msgID = MSGID_LDIF_INVALID_DN;
165N/A String message = getMessage(msgID, lineNumber, line.toString(),
165N/A de.getErrorMessage());
165N/A throw new LDIFException(msgID, message, lineNumber, true);
165N/A } catch (Exception e)
165N/A {
868N/A if (debugEnabled())
868N/A {
868N/A debugCought(DebugLogLevel.ERROR, e);
868N/A }
165N/A int msgID = MSGID_LDIF_INVALID_DN;
165N/A String message = getMessage(msgID, lineNumber, line.toString(),
165N/A e.getMessage());
165N/A throw new LDIFException(msgID, message, lineNumber, true);
165N/A }
165N/A
165N/A if(lines.isEmpty())
165N/A {
165N/A int msgID = MSGID_LDIF_NO_DELETE_OLDRDN_ATTRIBUTE;
165N/A String message = getMessage(msgID);
165N/A throw new LDIFException(msgID, message, lineNumber, true);
165N/A }
165N/A lineNumber++;
165N/A
165N/A line = lines.remove();
165N/A String delStr = getModifyDNAttributeValue(lines, line,
165N/A entryDN, "deleteoldrdn");
165N/A
165N/A if(delStr.equalsIgnoreCase("false") ||
165N/A delStr.equalsIgnoreCase("no") ||
165N/A delStr.equalsIgnoreCase("0"))
165N/A {
165N/A deleteOldRDN = false;
165N/A } else if(delStr.equalsIgnoreCase("true") ||
165N/A delStr.equalsIgnoreCase("yes") ||
165N/A delStr.equalsIgnoreCase("1"))
165N/A {
165N/A deleteOldRDN = true;
165N/A } else
165N/A {
165N/A int msgID = MSGID_LDIF_INVALID_DELETE_OLDRDN_ATTRIBUTE;
165N/A String message = getMessage(msgID, delStr);
165N/A throw new LDIFException(msgID, message, lineNumber, true);
165N/A }
165N/A
165N/A if(!lines.isEmpty())
165N/A {
165N/A lineNumber++;
165N/A
165N/A line = lines.remove();
165N/A
165N/A String dnStr = getModifyDNAttributeValue(lines, line,
165N/A entryDN, "newsuperior");
165N/A try
165N/A {
165N/A newSuperiorDN = DN.decode(dnStr);
165N/A } catch (DirectoryException de)
165N/A {
868N/A if (debugEnabled())
868N/A {
868N/A debugCought(DebugLogLevel.ERROR, de);
868N/A }
165N/A int msgID = MSGID_LDIF_INVALID_DN;
165N/A String message = getMessage(msgID, lineNumber, line.toString(),
165N/A de.getErrorMessage());
165N/A throw new LDIFException(msgID, message, lineNumber, true);
165N/A } catch (Exception e)
165N/A {
868N/A if (debugEnabled())
868N/A {
868N/A debugCought(DebugLogLevel.ERROR, e);
868N/A }
165N/A int msgID = MSGID_LDIF_INVALID_DN;
165N/A String message = getMessage(msgID, lineNumber, line.toString(),
165N/A e.getMessage());
165N/A throw new LDIFException(msgID, message, lineNumber, true);
165N/A }
165N/A }
165N/A
402N/A return new ModifyDNChangeRecordEntry(entryDN, newRDN, deleteOldRDN,
402N/A newSuperiorDN);
165N/A }
165N/A
165N/A
165N/A
165N/A /**
165N/A * Return the string value for the specified attribute name which only
165N/A * has one value.
165N/A *
165N/A * @param lines
165N/A * The set of lines for this change record entry.
165N/A * @param line
165N/A * The line currently being examined.
165N/A * @param entryDN
165N/A * The name of the entry being modified.
165N/A * @param attributeName
165N/A * The attribute name
165N/A * @return the string value for the attribute name.
165N/A * @throws LDIFException
165N/A * If a problem occurs while attempting to determine the
165N/A * attribute value.
165N/A */
165N/A
165N/A private String getModifyDNAttributeValue(LinkedList<StringBuilder> lines,
165N/A StringBuilder line,
165N/A DN entryDN,
165N/A String attributeName) throws LDIFException
165N/A {
165N/A
165N/A Attribute attr =
165N/A readSingleValueAttribute(lines, line, entryDN, attributeName);
165N/A LinkedHashSet<AttributeValue> values = attr.getValues();
165N/A
165N/A // Get the attribute value
165N/A Object[] vals = values.toArray();
165N/A return (((AttributeValue)vals[0]).getStringValue());
165N/A }
165N/A
165N/A
165N/A
165N/A /**
165N/A * Parse a modify change record entry from LDIF.
165N/A *
165N/A * @param entryDN
165N/A * The name of the entry being modified.
165N/A * @param lines
165N/A * The lines to parse.
165N/A * @return Returns the parsed modify change record entry.
165N/A * @throws LDIFException
165N/A * If there was an error when parsing the change record.
165N/A */
165N/A private ChangeRecordEntry parseModifyChangeRecordEntry(DN entryDN,
165N/A LinkedList<StringBuilder> lines) throws LDIFException {
165N/A
165N/A List<LDAPModification> modifications = new ArrayList<LDAPModification>();
165N/A while(!lines.isEmpty())
165N/A {
165N/A ModificationType modType = null;
165N/A
165N/A StringBuilder line = lines.remove();
165N/A Attribute attr =
165N/A readSingleValueAttribute(lines, line, entryDN, null);
165N/A String name = attr.getName();
165N/A LinkedHashSet<AttributeValue> values = attr.getValues();
165N/A
165N/A // Get the attribute description
165N/A String attrDescr = values.iterator().next().getStringValue();
165N/A
165N/A String lowerName = toLowerCase(name);
165N/A if(lowerName.equals("add"))
165N/A {
165N/A modType = ModificationType.ADD;
165N/A } else if(lowerName.equals("delete"))
165N/A {
165N/A modType = ModificationType.DELETE;
165N/A } else if(lowerName.equals("replace"))
165N/A {
165N/A modType = ModificationType.REPLACE;
165N/A } else if(lowerName.equals("increment"))
165N/A {
165N/A modType = ModificationType.INCREMENT;
165N/A } else
165N/A {
165N/A // Invalid attribute name.
165N/A int msgID = MSGID_LDIF_INVALID_MODIFY_ATTRIBUTE;
165N/A String message = getMessage(msgID, name,
165N/A "add, delete, replace, increment");
165N/A throw new LDIFException(msgID, message, lineNumber, true);
165N/A }
165N/A
165N/A // Now go through the rest of the attributes till the "-" line is
165N/A // reached.
165N/A Attribute modAttr = LDIFReader.parseAttrDescription(attrDescr);
165N/A while (! lines.isEmpty())
165N/A {
165N/A line = lines.remove();
165N/A if(line.toString().equals("-"))
165N/A {
165N/A break;
165N/A }
165N/A Attribute a =
165N/A readSingleValueAttribute(lines, line, entryDN, attrDescr);
165N/A modAttr.getValues().addAll(a.getValues());
165N/A }
165N/A
165N/A LDAPAttribute ldapAttr = new LDAPAttribute(modAttr);
165N/A LDAPModification mod = new LDAPModification(modType, ldapAttr);
165N/A modifications.add(mod);
165N/A }
165N/A
165N/A return new ModifyChangeRecordEntry(entryDN, modifications);
165N/A }
165N/A
165N/A
165N/A
165N/A /**
165N/A * Parse a delete change record entry from LDIF.
165N/A *
165N/A * @param entryDN
165N/A * The name of the entry being deleted.
165N/A * @param lines
165N/A * The lines to parse.
165N/A * @return Returns the parsed delete change record entry.
165N/A * @throws LDIFException
165N/A * If there was an error when parsing the change record.
165N/A */
165N/A private ChangeRecordEntry parseDeleteChangeRecordEntry(DN entryDN,
165N/A LinkedList<StringBuilder> lines) throws LDIFException {
165N/A
868N/A if (!lines.isEmpty())
165N/A {
165N/A int msgID = MSGID_LDIF_INVALID_DELETE_ATTRIBUTES;
165N/A String message = getMessage(msgID);
165N/A
165N/A throw new LDIFException(msgID, message, lineNumber, true);
165N/A }
165N/A
165N/A return new DeleteChangeRecordEntry(entryDN);
165N/A }
165N/A
165N/A
165N/A
165N/A /**
165N/A * Parse an add change record entry from LDIF.
165N/A *
165N/A * @param entryDN
165N/A * The name of the entry being added.
165N/A * @param lines
165N/A * The lines to parse.
165N/A * @return Returns the parsed add change record entry.
165N/A * @throws LDIFException
165N/A * If there was an error when parsing the change record.
165N/A */
165N/A private ChangeRecordEntry parseAddChangeRecordEntry(DN entryDN,
165N/A LinkedList<StringBuilder> lines) throws LDIFException {
165N/A
165N/A HashMap<ObjectClass,String> objectClasses =
165N/A new HashMap<ObjectClass,String>();
165N/A HashMap<AttributeType,List<Attribute>> attributes =
165N/A new HashMap<AttributeType, List<Attribute>>();
165N/A for(StringBuilder line : lines)
165N/A {
165N/A readAttribute(lines, line, entryDN, objectClasses,
165N/A attributes, attributes);
165N/A }
165N/A
165N/A // Reconstruct the object class attribute.
165N/A AttributeType ocType = DirectoryServer.getObjectClassAttributeType();
165N/A LinkedHashSet<AttributeValue> ocValues =
165N/A new LinkedHashSet<AttributeValue>(objectClasses.size());
165N/A for (String value : objectClasses.values()) {
165N/A AttributeValue av = new AttributeValue(ocType, value);
165N/A ocValues.add(av);
165N/A }
165N/A Attribute ocAttr = new Attribute(ocType, "objectClass", ocValues);
165N/A List<Attribute> ocAttrList = new ArrayList<Attribute>(1);
165N/A ocAttrList.add(ocAttr);
165N/A attributes.put(ocType, ocAttrList);
165N/A
165N/A return new AddChangeRecordEntry(entryDN, attributes);
165N/A }
165N/A
165N/A
165N/A
165N/A /**
165N/A * Parse colon position in an attribute description.
165N/A *
165N/A * @param lines
165N/A * The current set of lines.
165N/A * @param line
165N/A * The current line.
165N/A * @return The colon position.
165N/A * @throws LDIFException
165N/A * If the colon was badly placed or not found.
165N/A */
165N/A private int parseColonPosition(LinkedList<StringBuilder> lines,
165N/A StringBuilder line) throws LDIFException {
165N/A
165N/A int colonPos = line.indexOf(":");
165N/A if (colonPos <= 0)
165N/A {
165N/A int msgID = MSGID_LDIF_NO_ATTR_NAME;
165N/A String message = getMessage(msgID, lastEntryLineNumber, line.toString());
165N/A logToRejectWriter(lines, message);
165N/A throw new LDIFException(msgID, message, lastEntryLineNumber, true);
165N/A }
165N/A return colonPos;
165N/A }
165N/A
165N/A
165N/A
165N/A /**
165N/A * Parse a single attribute value from a line of LDIF.
165N/A *
165N/A * @param lines
165N/A * The current set of lines.
165N/A * @param line
165N/A * The current line.
165N/A * @param entryDN
165N/A * The DN of the entry being parsed.
165N/A * @param colonPos
165N/A * The position of the separator colon in the line.
165N/A * @param attrName
165N/A * The name of the attribute being parsed.
165N/A * @return The parsed attribute value.
165N/A * @throws LDIFException
165N/A * If an error occurred when parsing the attribute value.
165N/A */
165N/A private ASN1OctetString parseSingleValue(
165N/A LinkedList<StringBuilder> lines,
165N/A StringBuilder line,
165N/A DN entryDN,
165N/A int colonPos,
165N/A String attrName) throws LDIFException {
165N/A
165N/A // Look at the character immediately after the colon. If there is
165N/A // none, then assume an attribute with an empty value. If it is another
165N/A // colon, then the value must be base64-encoded. If it is a less-than
165N/A // sign, then assume that it is a URL. Otherwise, it is a regular value.
165N/A int length = line.length();
165N/A ASN1OctetString value;
165N/A if (colonPos == (length-1))
165N/A {
165N/A value = new ASN1OctetString();
165N/A }
165N/A else
165N/A {
165N/A char c = line.charAt(colonPos+1);
165N/A if (c == ':')
165N/A {
165N/A // The value is base64-encoded. Find the first non-blank
165N/A // character, take the rest of the line, and base64-decode it.
165N/A int pos = colonPos+2;
165N/A while ((pos < length) && (line.charAt(pos) == ' '))
165N/A {
165N/A pos++;
165N/A }
165N/A
165N/A try
165N/A {
165N/A value = new ASN1OctetString(Base64.decode(line.substring(pos)));
165N/A }
165N/A catch (Exception e)
165N/A {
165N/A // The value did not have a valid base64-encoding.
868N/A if (debugEnabled())
868N/A {
868N/A debugCought(DebugLogLevel.ERROR, e);
868N/A }
165N/A
165N/A int msgID = MSGID_LDIF_COULD_NOT_BASE64_DECODE_ATTR;
165N/A String message = getMessage(msgID, String.valueOf(entryDN),
165N/A lastEntryLineNumber, line,
165N/A String.valueOf(e));
165N/A logToRejectWriter(lines, message);
165N/A throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
165N/A }
165N/A }
165N/A else if (c == '<')
165N/A {
165N/A // Find the first non-blank character, decode the rest of the
165N/A // line as a URL, and read its contents.
165N/A int pos = colonPos+2;
165N/A while ((pos < length) && (line.charAt(pos) == ' '))
165N/A {
165N/A pos++;
165N/A }
165N/A
165N/A URL contentURL;
165N/A try
165N/A {
165N/A contentURL = new URL(line.substring(pos));
165N/A }
165N/A catch (Exception e)
165N/A {
165N/A // The URL was malformed or had an invalid protocol.
868N/A if (debugEnabled())
868N/A {
868N/A debugCought(DebugLogLevel.ERROR, e);
868N/A }
165N/A
165N/A int msgID = MSGID_LDIF_INVALID_URL;
165N/A String message = getMessage(msgID, String.valueOf(entryDN),
165N/A lastEntryLineNumber,
165N/A String.valueOf(attrName),
165N/A String.valueOf(e));
165N/A logToRejectWriter(lines, message);
165N/A throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
165N/A }
165N/A
165N/A
165N/A InputStream inputStream = null;
165N/A ByteArrayOutputStream outputStream = null;
165N/A try
165N/A {
165N/A outputStream = new ByteArrayOutputStream();
165N/A inputStream = contentURL.openConnection().getInputStream();
165N/A
165N/A int bytesRead;
165N/A while ((bytesRead = inputStream.read(buffer)) > 0)
165N/A {
165N/A outputStream.write(buffer, 0, bytesRead);
165N/A }
165N/A
165N/A value = new ASN1OctetString(outputStream.toByteArray());
165N/A }
165N/A catch (Exception e)
165N/A {
165N/A // We were unable to read the contents of that URL for some
165N/A // reason.
868N/A if (debugEnabled())
868N/A {
868N/A debugCought(DebugLogLevel.ERROR, e);
868N/A }
165N/A
165N/A int msgID = MSGID_LDIF_URL_IO_ERROR;
165N/A String message = getMessage(msgID, String.valueOf(entryDN),
165N/A lastEntryLineNumber,
165N/A String.valueOf(attrName),
165N/A String.valueOf(contentURL),
165N/A String.valueOf(e));
165N/A logToRejectWriter(lines, message);
165N/A throw new LDIFException(msgID, message, lastEntryLineNumber, true, e);
165N/A }
165N/A finally
165N/A {
165N/A if (outputStream != null)
165N/A {
165N/A try
165N/A {
165N/A outputStream.close();
165N/A } catch (Exception e) {}
165N/A }
165N/A
165N/A if (inputStream != null)
165N/A {
165N/A try
165N/A {
165N/A inputStream.close();
165N/A } catch (Exception e) {}
165N/A }
165N/A }
165N/A }
165N/A else
165N/A {
165N/A // The rest of the line should be the value. Skip over any
165N/A // spaces and take the rest of the line as the value.
165N/A int pos = colonPos+1;
165N/A while ((pos < length) && (line.charAt(pos) == ' '))
165N/A {
165N/A pos++;
165N/A }
165N/A
165N/A value = new ASN1OctetString(line.substring(pos));
165N/A }
165N/A }
165N/A return value;
165N/A }
165N/A
165N/A
165N/A
165N/A /**
165N/A * Log a message to the reject writer if one is configured.
165N/A *
165N/A * @param lines
165N/A * The set of rejected lines.
165N/A * @param message
165N/A * The associated error message.
165N/A */
165N/A private void logToRejectWriter(LinkedList<StringBuilder> lines,
165N/A String message) {
165N/A
165N/A BufferedWriter rejectWriter = importConfig.getRejectWriter();
165N/A if (rejectWriter != null)
165N/A {
165N/A try
165N/A {
165N/A rejectWriter.write("# ");
165N/A rejectWriter.write(message);
165N/A rejectWriter.newLine();
165N/A for (StringBuilder sb : lines)
165N/A {
165N/A rejectWriter.write(sb.toString());
165N/A rejectWriter.newLine();
165N/A }
165N/A
165N/A rejectWriter.newLine();
165N/A }
165N/A catch (Exception e)
165N/A {
868N/A if (debugEnabled())
868N/A {
868N/A debugCought(DebugLogLevel.ERROR, e);
868N/A }
165N/A }
165N/A }
165N/A }
0N/A}
0N/A