package com.hwlcn.ldap.ldif; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.InputStream; import java.io.InputStreamReader; import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.nio.charset.Charset; import com.hwlcn.ldap.asn1.ASN1OctetString; import com.hwlcn.ldap.ldap.matchingrules.CaseIgnoreStringMatchingRule; import com.hwlcn.ldap.ldap.matchingrules.MatchingRule; import com.hwlcn.ldap.ldap.sdk.Attribute; import com.hwlcn.ldap.ldap.sdk.Entry; import com.hwlcn.ldap.ldap.sdk.Modification; import com.hwlcn.ldap.ldap.sdk.ModificationType; import com.hwlcn.ldap.ldap.sdk.LDAPException; import com.hwlcn.ldap.ldap.sdk.schema.Schema; import com.hwlcn.ldap.util.AggregateInputStream; import com.hwlcn.ldap.util.Base64; import com.hwlcn.ldap.util.LDAPSDKThreadFactory; import com.hwlcn.core.annotation.ThreadSafety; import com.hwlcn.ldap.util.ThreadSafetyLevel; import com.hwlcn.ldap.util.parallel.AsynchronousParallelProcessor; import com.hwlcn.ldap.util.parallel.Result; import com.hwlcn.ldap.util.parallel.ParallelProcessor; import com.hwlcn.ldap.util.parallel.Processor; import static com.hwlcn.ldap.ldif.LDIFMessages.*; import static com.hwlcn.ldap.util.Debug.*; import static com.hwlcn.ldap.util.StaticUtils.*; import static com.hwlcn.ldap.util.Validator.*; /** * This class provides an LDIF reader, which can be used to read and decode * entries and change records from a data source using the LDAP Data Interchange * Format as per <A HREF="http://www.ietf.org/rfc/rfc2849.txt">RFC 2849</A>. * <BR> * This class is not synchronized. If multiple threads read from the * LDIFReader, they must be synchronized externally. * <BR><BR> * <H2>Example</H2> * The following example iterates through all entries contained in an LDIF file * and attempts to add them to a directory server: * <PRE> * LDIFReader ldifReader = new LDIFReader(pathToLDIFFile); * * while (true) * { * Entry entry; * try * { * entry = ldifReader.readEntry(); * if (entry == null) * { * System.err.println("All entries have been processed."); * break; * } * } * catch (LDIFException le) * { * if (le.mayContinueReading()) * { * System.err.println("A recoverable occurred while attempting to " + * "read an entry at or near line number " + le.getLineNumber() + * ": " + le.getMessage()); * System.err.println("The entry will be skipped."); * continue; * } * else * { * System.err.println("An unrecoverable occurred while attempting to " + * "read an entry at or near line number " + le.getLineNumber() + * ": " + le.getMessage()); * System.err.println("LDIF processing will be aborted."); * break; * } * } * catch (IOException ioe) * { * System.err.println("An I/O error occurred while attempting to read " + * "from the LDIF file: " + ioe.getMessage()); * System.err.println("LDIF processing will be aborted."); * break; * } * * try * { * connection.add(entry); * System.out.println("Successfully added entry " + entry.getDN()); * } * catch (LDAPException le) * { * System.err.println("Unable to add entry " + entry.getDN() + " -- " + * le.getMessage()); * } * } * * ldifReader.close(); * </PRE> */ @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) public final class LDIFReader { public static final int DEFAULT_BUFFER_SIZE = 128 * 1024; private static final int ASYNC_MIN_PER_PARSING_THREAD = 3; private static final int ASYNC_QUEUE_SIZE = 500; private static final Entry SKIP_ENTRY = new Entry("cn=skipped"); private static final String DEFAULT_RELATIVE_BASE_PATH; static { final File currentDir; String currentDirString = System.getProperty("user.dir"); if (currentDirString == null) { currentDir = new File("."); } else { currentDir = new File(currentDirString); } final String currentDirAbsolutePath = currentDir.getAbsolutePath(); if (currentDirAbsolutePath.endsWith(File.separator)) { DEFAULT_RELATIVE_BASE_PATH = currentDirAbsolutePath; } else { DEFAULT_RELATIVE_BASE_PATH = currentDirAbsolutePath + File.separator; } } private final BufferedReader reader; private volatile DuplicateValueBehavior duplicateValueBehavior; private long lineNumberCounter = 0; private final LDIFReaderEntryTranslator entryTranslator; private Schema schema; private volatile String relativeBasePath; private volatile TrailingSpaceBehavior trailingSpaceBehavior; private final boolean isAsync; private final AsynchronousParallelProcessor<UnparsedLDIFRecord, LDIFRecord> asyncParser; private final AtomicBoolean asyncParsingComplete; private final BlockingQueue<Result<UnparsedLDIFRecord, LDIFRecord>> asyncParsedRecords; public LDIFReader(final String path) throws IOException { this(new FileInputStream(path)); } public LDIFReader(final String path, final int numParseThreads) throws IOException { this(new FileInputStream(path), numParseThreads); } public LDIFReader(final File file) throws IOException { this(new FileInputStream(file)); } public LDIFReader(final File file, final int numParseThreads) throws IOException { this(new FileInputStream(file), numParseThreads); } public LDIFReader(final File[] files, final int numParseThreads, final LDIFReaderEntryTranslator entryTranslator) throws IOException { this(createAggregateInputStream(files), numParseThreads, entryTranslator); } private static InputStream createAggregateInputStream(final File... files) throws IOException { if (files.length == 0) { throw new IOException(ERR_READ_NO_LDIF_FILES.get()); } else if (files.length == 1) { return new FileInputStream(files[0]); } else { final File spacerFile = File.createTempFile("ldif-reader-spacer", ".ldif"); spacerFile.deleteOnExit(); final BufferedWriter spacerWriter = new BufferedWriter(new FileWriter(spacerFile)); try { spacerWriter.newLine(); spacerWriter.newLine(); } finally { spacerWriter.close(); } final File[] returnArray = new File[(files.length * 2) - 1]; returnArray[0] = files[0]; int pos = 1; for (int i=1; i < files.length; i++) { returnArray[pos++] = spacerFile; returnArray[pos++] = files[i]; } return new AggregateInputStream(returnArray); } } public LDIFReader(final InputStream inputStream) { this(inputStream, 0); } public LDIFReader(final InputStream inputStream, final int numParseThreads) { this(new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")), DEFAULT_BUFFER_SIZE), numParseThreads); } public LDIFReader(final InputStream inputStream, final int numParseThreads, final LDIFReaderEntryTranslator entryTranslator) { this(new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")), DEFAULT_BUFFER_SIZE), numParseThreads, entryTranslator); } public LDIFReader(final BufferedReader reader) { this(reader, 0); } public LDIFReader(final BufferedReader reader, final int numParseThreads) { this(reader, numParseThreads, null); } public LDIFReader(final BufferedReader reader, final int numParseThreads, final LDIFReaderEntryTranslator entryTranslator) { ensureNotNull(reader); ensureTrue(numParseThreads >= 0, "LDIFReader.numParseThreads must not be negative."); this.reader = reader; this.entryTranslator = entryTranslator; duplicateValueBehavior = DuplicateValueBehavior.STRIP; trailingSpaceBehavior = TrailingSpaceBehavior.REJECT; relativeBasePath = DEFAULT_RELATIVE_BASE_PATH; if (numParseThreads == 0) { isAsync = false; asyncParser = null; asyncParsingComplete = null; asyncParsedRecords = null; } else { isAsync = true; asyncParsingComplete = new AtomicBoolean(false); final LDAPSDKThreadFactory threadFactory = new LDAPSDKThreadFactory("LDIFReader Worker", true, null); final ParallelProcessor<UnparsedLDIFRecord, LDIFRecord> parallelParser = new ParallelProcessor<UnparsedLDIFRecord, LDIFRecord>( new RecordParser(), threadFactory, numParseThreads, ASYNC_MIN_PER_PARSING_THREAD); final BlockingQueue<UnparsedLDIFRecord> pendingQueue = new ArrayBlockingQueue<UnparsedLDIFRecord>(ASYNC_QUEUE_SIZE); asyncParsedRecords = new ArrayBlockingQueue <Result<UnparsedLDIFRecord, LDIFRecord>>(2 * ASYNC_QUEUE_SIZE + 100); asyncParser = new AsynchronousParallelProcessor <UnparsedLDIFRecord, LDIFRecord>(pendingQueue, parallelParser, asyncParsedRecords); final LineReaderThread lineReaderThread = new LineReaderThread(); lineReaderThread.start(); } } public static List<Entry> readEntries(final String path) throws IOException, LDIFException { return readEntries(new LDIFReader(path)); } public static List<Entry> readEntries(final File file) throws IOException, LDIFException { return readEntries(new LDIFReader(file)); } public static List<Entry> readEntries(final InputStream inputStream) throws IOException, LDIFException { return readEntries(new LDIFReader(inputStream)); } private static List<Entry> readEntries(final LDIFReader reader) throws IOException, LDIFException { try { final ArrayList<Entry> entries = new ArrayList<Entry>(10); while (true) { final Entry e = reader.readEntry(); if (e == null) { break; } entries.add(e); } return entries; } finally { reader.close(); } } public void close() throws IOException { reader.close(); if (isAsync()) { asyncParsedRecords.clear(); } } @Deprecated() public boolean ignoreDuplicateValues() { return (duplicateValueBehavior == DuplicateValueBehavior.STRIP); } @Deprecated() public void setIgnoreDuplicateValues(final boolean ignoreDuplicateValues) { if (ignoreDuplicateValues) { duplicateValueBehavior = DuplicateValueBehavior.STRIP; } else { duplicateValueBehavior = DuplicateValueBehavior.REJECT; } } public DuplicateValueBehavior getDuplicateValueBehavior() { return duplicateValueBehavior; } public void setDuplicateValueBehavior( final DuplicateValueBehavior duplicateValueBehavior) { this.duplicateValueBehavior = duplicateValueBehavior; } @Deprecated() public boolean stripTrailingSpaces() { return (trailingSpaceBehavior == TrailingSpaceBehavior.STRIP); } @Deprecated() public void setStripTrailingSpaces(final boolean stripTrailingSpaces) { trailingSpaceBehavior = stripTrailingSpaces ? TrailingSpaceBehavior.STRIP : TrailingSpaceBehavior.REJECT; } public TrailingSpaceBehavior getTrailingSpaceBehavior() { return trailingSpaceBehavior; } public void setTrailingSpaceBehavior( final TrailingSpaceBehavior trailingSpaceBehavior) { this.trailingSpaceBehavior = trailingSpaceBehavior; } public String getRelativeBasePath() { return relativeBasePath; } public void setRelativeBasePath(final String relativeBasePath) { setRelativeBasePath(new File(relativeBasePath)); } public void setRelativeBasePath(final File relativeBasePath) { final String path = relativeBasePath.getAbsolutePath(); if (path.endsWith(File.separator)) { this.relativeBasePath = path; } else { this.relativeBasePath = path + File.separator; } } public Schema getSchema() { return schema; } public void setSchema(final Schema schema) { this.schema = schema; } public LDIFRecord readLDIFRecord() throws IOException, LDIFException { if (isAsync()) { return readLDIFRecordAsync(); } else { return readLDIFRecordInternal(); } } public Entry readEntry() throws IOException, LDIFException { if (isAsync()) { return readEntryAsync(); } else { return readEntryInternal(); } } public LDIFChangeRecord readChangeRecord() throws IOException, LDIFException { return readChangeRecord(false); } public LDIFChangeRecord readChangeRecord(final boolean defaultAdd) throws IOException, LDIFException { if (isAsync()) { return readChangeRecordAsync(defaultAdd); } else { return readChangeRecordInternal(defaultAdd); } } private LDIFRecord readLDIFRecordAsync() throws IOException, LDIFException { final Result<UnparsedLDIFRecord, LDIFRecord> result = readLDIFRecordResultAsync(); if (result == null) { return null; } else { return result.getOutput(); } } private Entry readEntryAsync() throws IOException, LDIFException { Result<UnparsedLDIFRecord, LDIFRecord> result = null; LDIFRecord record = null; while (record == null) { result = readLDIFRecordResultAsync(); if (result == null) { return null; } record = result.getOutput(); if (record == SKIP_ENTRY) { record = null; } } if (!(record instanceof Entry)) { try { return ((LDIFChangeRecord)record).toEntry(); } catch (LDIFException e) { debugException(e); final long firstLineNumber = result.getInput().getFirstLineNumber(); throw new LDIFException(e.getExceptionMessage(), firstLineNumber, true, e); } } return (Entry) record; } private LDIFChangeRecord readChangeRecordAsync(final boolean defaultAdd) throws IOException, LDIFException { final Result<UnparsedLDIFRecord, LDIFRecord> result = readLDIFRecordResultAsync(); if (result == null) { return null; } final LDIFRecord record = result.getOutput(); if (record instanceof LDIFChangeRecord) { return (LDIFChangeRecord) record; } else if (record instanceof Entry) { if (defaultAdd) { return new LDIFAddChangeRecord((Entry) record); } else { final long firstLineNumber = result.getInput().getFirstLineNumber(); throw new LDIFException( ERR_READ_NOT_CHANGE_RECORD.get(firstLineNumber), firstLineNumber, true); } } throw new AssertionError("LDIFRecords must either be an Entry or an " + "LDIFChangeRecord"); } private Result<UnparsedLDIFRecord, LDIFRecord> readLDIFRecordResultAsync() throws IOException, LDIFException { Result<UnparsedLDIFRecord, LDIFRecord> result = null; if (asyncParsingComplete.get()) { result = asyncParsedRecords.poll(); } else { try { while ((result == null) && (!asyncParsingComplete.get())) { result = asyncParsedRecords.poll(1, TimeUnit.SECONDS); } if (result == null) { result = asyncParsedRecords.poll(); } } catch (InterruptedException e) { debugException(e); throw new IOException(getExceptionMessage(e)); } } if (result == null) { return null; } rethrow(result.getFailureCause()); final UnparsedLDIFRecord unparsedRecord = result.getInput(); if (unparsedRecord.isEOF()) { asyncParsingComplete.set(true); try { asyncParsedRecords.put(result); } catch (InterruptedException e) { debugException(e); } return null; } return result; } private boolean isAsync() { return isAsync; } static void rethrow(final Throwable t) throws IOException, LDIFException { if (t == null) { return; } if (t instanceof IOException) { throw (IOException) t; } else if (t instanceof LDIFException) { throw (LDIFException) t; } else if (t instanceof RuntimeException) { throw (RuntimeException) t; } else if (t instanceof Error) { throw (Error) t; } else { throw new IOException(getExceptionMessage(t)); } } private LDIFRecord readLDIFRecordInternal() throws IOException, LDIFException { final UnparsedLDIFRecord unparsedRecord = readUnparsedRecord(); return decodeRecord(unparsedRecord, relativeBasePath); } private Entry readEntryInternal() throws IOException, LDIFException { Entry e = null; while (e == null) { final UnparsedLDIFRecord unparsedRecord = readUnparsedRecord(); if (unparsedRecord.isEOF()) { return null; } e = decodeEntry(unparsedRecord, relativeBasePath); debugLDIFRead(e); if (entryTranslator != null) { e = entryTranslator.translate(e, unparsedRecord.getFirstLineNumber()); } } return e; } private LDIFChangeRecord readChangeRecordInternal(final boolean defaultAdd) throws IOException, LDIFException { final UnparsedLDIFRecord unparsedRecord = readUnparsedRecord(); if (unparsedRecord.isEOF()) { return null; } final LDIFChangeRecord r = decodeChangeRecord(unparsedRecord, relativeBasePath, defaultAdd); debugLDIFRead(r); return r; } private UnparsedLDIFRecord readUnparsedRecord() throws IOException, LDIFException { final ArrayList<StringBuilder> lineList = new ArrayList<StringBuilder>(20); boolean lastWasComment = false; long firstLineNumber = lineNumberCounter + 1; while (true) { final String line = reader.readLine(); lineNumberCounter++; if (line == null) { if (lineList.isEmpty()) { return new UnparsedLDIFRecord(new ArrayList<StringBuilder>(0), duplicateValueBehavior, trailingSpaceBehavior, schema, -1); } else { break; } } if (line.length() == 0) { lastWasComment = false; if (lineList.isEmpty()) { firstLineNumber++; continue; } else { break; } } if (line.charAt(0) == ' ') { if (lastWasComment) { } else if (lineList.isEmpty()) { throw new LDIFException( ERR_READ_UNEXPECTED_FIRST_SPACE.get(lineNumberCounter), lineNumberCounter, false); } else { lineList.get(lineList.size() - 1).append(line.substring(1)); lastWasComment = false; } } else if (line.charAt(0) == '#') { lastWasComment = true; } else { if (lineList.isEmpty() && line.startsWith("version:")) { lastWasComment = true; } else { lineList.add(new StringBuilder(line)); lastWasComment = false; } } } return new UnparsedLDIFRecord(lineList, duplicateValueBehavior, trailingSpaceBehavior, schema, firstLineNumber); } public static Entry decodeEntry(final String... ldifLines) throws LDIFException { final Entry e = decodeEntry(prepareRecord(DuplicateValueBehavior.STRIP, TrailingSpaceBehavior.REJECT, null, ldifLines), DEFAULT_RELATIVE_BASE_PATH); debugLDIFRead(e); return e; } public static Entry decodeEntry(final boolean ignoreDuplicateValues, final Schema schema, final String... ldifLines) throws LDIFException { final Entry e = decodeEntry(prepareRecord( (ignoreDuplicateValues ? DuplicateValueBehavior.STRIP : DuplicateValueBehavior.REJECT), TrailingSpaceBehavior.REJECT, schema, ldifLines), DEFAULT_RELATIVE_BASE_PATH); debugLDIFRead(e); return e; } public static LDIFChangeRecord decodeChangeRecord(final String... ldifLines) throws LDIFException { return decodeChangeRecord(false, ldifLines); } public static LDIFChangeRecord decodeChangeRecord(final boolean defaultAdd, final String... ldifLines) throws LDIFException { final LDIFChangeRecord r = decodeChangeRecord( prepareRecord(DuplicateValueBehavior.STRIP, TrailingSpaceBehavior.REJECT, null, ldifLines), DEFAULT_RELATIVE_BASE_PATH, defaultAdd); debugLDIFRead(r); return r; } public static LDIFChangeRecord decodeChangeRecord( final boolean ignoreDuplicateValues, final Schema schema, final boolean defaultAdd, final String... ldifLines) throws LDIFException { final LDIFChangeRecord r = decodeChangeRecord( prepareRecord( (ignoreDuplicateValues ? DuplicateValueBehavior.STRIP : DuplicateValueBehavior.REJECT), TrailingSpaceBehavior.REJECT, schema, ldifLines), DEFAULT_RELATIVE_BASE_PATH, defaultAdd); debugLDIFRead(r); return r; } private static UnparsedLDIFRecord prepareRecord( final DuplicateValueBehavior duplicateValueBehavior, final TrailingSpaceBehavior trailingSpaceBehavior, final Schema schema, final String... ldifLines) throws LDIFException { ensureNotNull(ldifLines); ensureFalse(ldifLines.length == 0, "LDIFReader.prepareRecord.ldifLines must not be empty."); boolean lastWasComment = false; final ArrayList<StringBuilder> lineList = new ArrayList<StringBuilder>(ldifLines.length); for (int i=0; i < ldifLines.length; i++) { final String line = ldifLines[i]; if (line.length() == 0) { for (int j=i+1; j < ldifLines.length; j++) { if (ldifLines[j].length() > 0) { throw new LDIFException(ERR_READ_UNEXPECTED_BLANK.get(i), i, true, ldifLines, null); } if (lineList.isEmpty()) { throw new LDIFException(ERR_READ_ONLY_BLANKS.get(), 0, true, ldifLines, null); } else { return new UnparsedLDIFRecord(lineList, duplicateValueBehavior, trailingSpaceBehavior, schema, 0); } } } if (line.charAt(0) == ' ') { if (i > 0) { if (! lastWasComment) { lineList.get(lineList.size() - 1).append(line.substring(1)); } } else { throw new LDIFException( ERR_READ_UNEXPECTED_FIRST_SPACE_NO_NUMBER.get(), 0, true, ldifLines, null); } } else if (line.charAt(0) == '#') { lastWasComment = true; } else { lineList.add(new StringBuilder(line)); lastWasComment = false; } } if (lineList.isEmpty()) { throw new LDIFException(ERR_READ_NO_DATA.get(), 0, true, ldifLines, null); } else { return new UnparsedLDIFRecord(lineList, duplicateValueBehavior, trailingSpaceBehavior, schema, 0); } } private static LDIFRecord decodeRecord( final UnparsedLDIFRecord unparsedRecord, final String relativeBasePath) throws LDIFException { final Exception readError = unparsedRecord.getFailureCause(); if (readError != null) { if (readError instanceof LDIFException) { final LDIFException ldifEx = (LDIFException) readError; throw new LDIFException(ldifEx.getMessage(), ldifEx.getLineNumber(), ldifEx.mayContinueReading(), ldifEx.getDataLines(), ldifEx.getCause()); } else { throw new LDIFException(getExceptionMessage(readError), -1, true, readError); } } if (unparsedRecord.isEOF()) { return null; } final ArrayList<StringBuilder> lineList = unparsedRecord.getLineList(); if (unparsedRecord.getLineList() == null) { return null; } final LDIFRecord r; if ((lineList.size() > 1) && toLowerCase(lineList.get(1).toString()).startsWith("changetype:")) { r = decodeChangeRecord(unparsedRecord, relativeBasePath, false); } else { r = decodeEntry(unparsedRecord, relativeBasePath); } debugLDIFRead(r); return r; } private static Entry decodeEntry(final UnparsedLDIFRecord unparsedRecord, final String relativeBasePath) throws LDIFException { final ArrayList<StringBuilder> ldifLines = unparsedRecord.getLineList(); final long firstLineNumber = unparsedRecord.getFirstLineNumber(); final Iterator<StringBuilder> iterator = ldifLines.iterator(); final StringBuilder line = iterator.next(); handleTrailingSpaces(line, null, firstLineNumber, unparsedRecord.getTrailingSpaceBehavior()); final int colonPos = line.indexOf(":"); if ((colonPos < 0) || (! line.substring(0, colonPos).equalsIgnoreCase("dn"))) { throw new LDIFException( ERR_READ_DN_LINE_DOESNT_START_WITH_DN.get(firstLineNumber), firstLineNumber, true, ldifLines, null); } final String dn; final int length = line.length(); if (length == (colonPos+1)) { dn = ""; } else if (line.charAt(colonPos+1) == ':') { int pos = colonPos+2; while ((pos < length) && (line.charAt(pos) == ' ')) { pos++; } try { final byte[] dnBytes = Base64.decode(line.substring(pos)); dn = new String(dnBytes, "UTF-8"); } catch (final ParseException pe) { debugException(pe); throw new LDIFException( ERR_READ_CANNOT_BASE64_DECODE_DN.get(firstLineNumber, pe.getMessage()), firstLineNumber, true, ldifLines, pe); } catch (final Exception e) { debugException(e); throw new LDIFException( ERR_READ_CANNOT_BASE64_DECODE_DN.get(firstLineNumber, e), firstLineNumber, true, ldifLines, e); } } else { int pos = colonPos+1; while ((pos < length) && (line.charAt(pos) == ' ')) { pos++; } dn = line.substring(pos); } if (! iterator.hasNext()) { return new Entry(dn, unparsedRecord.getSchema()); } return new Entry(dn, unparsedRecord.getSchema(), parseAttributes(dn, unparsedRecord.getDuplicateValueBehavior(), unparsedRecord.getTrailingSpaceBehavior(), unparsedRecord.getSchema(), ldifLines, iterator, relativeBasePath, firstLineNumber)); } private static LDIFChangeRecord decodeChangeRecord( final UnparsedLDIFRecord unparsedRecord, final String relativeBasePath, final boolean defaultAdd) throws LDIFException { final ArrayList<StringBuilder> ldifLines = unparsedRecord.getLineList(); final long firstLineNumber = unparsedRecord.getFirstLineNumber(); final Iterator<StringBuilder> iterator = ldifLines.iterator(); StringBuilder line = iterator.next(); handleTrailingSpaces(line, null, firstLineNumber, unparsedRecord.getTrailingSpaceBehavior()); int colonPos = line.indexOf(":"); if ((colonPos < 0) || (! line.substring(0, colonPos).equalsIgnoreCase("dn"))) { throw new LDIFException( ERR_READ_CR_DN_LINE_DOESNT_START_WITH_DN.get(firstLineNumber), firstLineNumber, true, ldifLines, null); } final String dn; int length = line.length(); if (length == (colonPos+1)) { dn = ""; } else if (line.charAt(colonPos+1) == ':') { int pos = colonPos+2; while ((pos < length) && (line.charAt(pos) == ' ')) { pos++; } try { final byte[] dnBytes = Base64.decode(line.substring(pos)); dn = new String(dnBytes, "UTF-8"); } catch (final ParseException pe) { debugException(pe); throw new LDIFException( ERR_READ_CR_CANNOT_BASE64_DECODE_DN.get(firstLineNumber, pe.getMessage()), firstLineNumber, true, ldifLines, pe); } catch (final Exception e) { debugException(e); throw new LDIFException( ERR_READ_CR_CANNOT_BASE64_DECODE_DN.get(firstLineNumber, e), firstLineNumber, true, ldifLines, e); } } else { int pos = colonPos+1; while ((pos < length) && (line.charAt(pos) == ' ')) { pos++; } dn = line.substring(pos); } if (! iterator.hasNext()) { throw new LDIFException(ERR_READ_CR_TOO_SHORT.get(firstLineNumber), firstLineNumber, true, ldifLines, null); } final String changeType; if (defaultAdd && (! toLowerCase(ldifLines.get(1).toString()).startsWith("changetype:"))) { changeType = "add"; } else { line = iterator.next(); handleTrailingSpaces(line, dn, firstLineNumber, unparsedRecord.getTrailingSpaceBehavior()); colonPos = line.indexOf(":"); if ((colonPos < 0) || (! line.substring(0, colonPos).equalsIgnoreCase("changetype"))) { throw new LDIFException( ERR_READ_CR_CT_LINE_DOESNT_START_WITH_CT.get(firstLineNumber), firstLineNumber, true, ldifLines, null); } length = line.length(); if (length == (colonPos+1)) { throw new LDIFException( ERR_READ_CT_LINE_NO_CT_VALUE.get(firstLineNumber), firstLineNumber, true, ldifLines, null); } else if (line.charAt(colonPos+1) == ':') { int pos = colonPos+2; while ((pos < length) && (line.charAt(pos) == ' ')) { pos++; } try { final byte[] changeTypeBytes = Base64.decode(line.substring(pos)); changeType = new String(changeTypeBytes, "UTF-8"); } catch (final ParseException pe) { debugException(pe); throw new LDIFException( ERR_READ_CANNOT_BASE64_DECODE_CT.get(firstLineNumber, pe.getMessage()), firstLineNumber, true, ldifLines, pe); } catch (final Exception e) { debugException(e); throw new LDIFException( ERR_READ_CANNOT_BASE64_DECODE_CT.get(firstLineNumber, e), firstLineNumber, true, ldifLines, e); } } else { int pos = colonPos+1; while ((pos < length) && (line.charAt(pos) == ' ')) { pos++; } changeType = line.substring(pos); } } final String lowerChangeType = toLowerCase(changeType); if (lowerChangeType.equals("add")) { if (iterator.hasNext()) { final Collection<Attribute> attrs = parseAttributes(dn, unparsedRecord.getDuplicateValueBehavior(), unparsedRecord.getTrailingSpaceBehavior(), unparsedRecord.getSchema(), ldifLines, iterator, relativeBasePath, firstLineNumber); final Attribute[] attributes = new Attribute[attrs.size()]; final Iterator<Attribute> attrIterator = attrs.iterator(); for (int i=0; i < attributes.length; i++) { attributes[i] = attrIterator.next(); } return new LDIFAddChangeRecord(dn, attributes); } else { throw new LDIFException(ERR_READ_CR_NO_ATTRIBUTES.get(firstLineNumber), firstLineNumber, true, ldifLines, null); } } else if (lowerChangeType.equals("delete")) { if (iterator.hasNext()) { throw new LDIFException( ERR_READ_CR_EXTRA_DELETE_DATA.get(firstLineNumber), firstLineNumber, true, ldifLines, null); } else { return new LDIFDeleteChangeRecord(dn); } } else if (lowerChangeType.equals("modify")) { if (iterator.hasNext()) { final Modification[] mods = parseModifications(dn, unparsedRecord.getTrailingSpaceBehavior(), ldifLines, iterator, firstLineNumber); return new LDIFModifyChangeRecord(dn, mods); } else { throw new LDIFException(ERR_READ_CR_NO_MODS.get(firstLineNumber), firstLineNumber, true, ldifLines, null); } } else if (lowerChangeType.equals("moddn") || lowerChangeType.equals("modrdn")) { if (iterator.hasNext()) { return parseModifyDNChangeRecord(ldifLines, iterator, dn, unparsedRecord.getTrailingSpaceBehavior(), firstLineNumber); } else { throw new LDIFException(ERR_READ_CR_NO_NEWRDN.get(firstLineNumber), firstLineNumber, true, ldifLines, null); } } else { throw new LDIFException(ERR_READ_CR_INVALID_CT.get(changeType, firstLineNumber), firstLineNumber, true, ldifLines, null); } } private static ArrayList<Attribute> parseAttributes(final String dn, final DuplicateValueBehavior duplicateValueBehavior, final TrailingSpaceBehavior trailingSpaceBehavior, final Schema schema, final ArrayList<StringBuilder> ldifLines, final Iterator<StringBuilder> iterator, final String relativeBasePath, final long firstLineNumber) throws LDIFException { final LinkedHashMap<String,Object> attributes = new LinkedHashMap<String,Object>(ldifLines.size()); while (iterator.hasNext()) { final StringBuilder line = iterator.next(); handleTrailingSpaces(line, dn, firstLineNumber, trailingSpaceBehavior); final int colonPos = line.indexOf(":"); if (colonPos <= 0) { throw new LDIFException(ERR_READ_NO_ATTR_COLON.get(firstLineNumber), firstLineNumber, true, ldifLines, null); } final String attributeName = line.substring(0, colonPos); final String lowerName = toLowerCase(attributeName); final MatchingRule matchingRule; if (schema == null) { matchingRule = CaseIgnoreStringMatchingRule.getInstance(); } else { matchingRule = MatchingRule.selectEqualityMatchingRule(attributeName, schema); } Attribute attr; final LDIFAttribute ldifAttr; final Object attrObject = attributes.get(lowerName); if (attrObject == null) { attr = null; ldifAttr = null; } else { if (attrObject instanceof Attribute) { attr = (Attribute) attrObject; ldifAttr = new LDIFAttribute(attr.getName(), matchingRule, attr.getRawValues()[0]); attributes.put(lowerName, ldifAttr); } else { attr = null; ldifAttr = (LDIFAttribute) attrObject; } } final int length = line.length(); if (length == (colonPos+1)) { if (attrObject == null) { attr = new Attribute(attributeName, ""); attributes.put(lowerName, attr); } else { try { if (! ldifAttr.addValue(new ASN1OctetString(), duplicateValueBehavior)) { if (duplicateValueBehavior != DuplicateValueBehavior.STRIP) { throw new LDIFException(ERR_READ_DUPLICATE_VALUE.get(dn, firstLineNumber, attributeName), firstLineNumber, true, ldifLines, null); } } } catch (LDAPException le) { throw new LDIFException(ERR_READ_VALUE_SYNTAX_VIOLATION.get(dn, firstLineNumber, attributeName, getExceptionMessage(le)), firstLineNumber, true, ldifLines, le); } } } else if (line.charAt(colonPos+1) == ':') { int pos = colonPos+2; while ((pos < length) && (line.charAt(pos) == ' ')) { pos++; } try { final byte[] valueBytes = Base64.decode(line.substring(pos)); if (attrObject == null) { attr = new Attribute(attributeName, valueBytes); attributes.put(lowerName, attr); } else { try { if (! ldifAttr.addValue(new ASN1OctetString(valueBytes), duplicateValueBehavior)) { if (duplicateValueBehavior != DuplicateValueBehavior.STRIP) { throw new LDIFException(ERR_READ_DUPLICATE_VALUE.get(dn, firstLineNumber, attributeName), firstLineNumber, true, ldifLines, null); } } } catch (LDAPException le) { throw new LDIFException(ERR_READ_VALUE_SYNTAX_VIOLATION.get(dn, firstLineNumber, attributeName, getExceptionMessage(le)), firstLineNumber, true, ldifLines, le); } } } catch (final ParseException pe) { debugException(pe); throw new LDIFException(ERR_READ_CANNOT_BASE64_DECODE_ATTR.get( attributeName, firstLineNumber, pe.getMessage()), firstLineNumber, true, ldifLines, pe); } } else if (line.charAt(colonPos+1) == '<') { int pos = colonPos+2; while ((pos < length) && (line.charAt(pos) == ' ')) { pos++; } final String path; final String urlString = line.substring(pos); final String lowerURLString = toLowerCase(urlString); if (lowerURLString.startsWith("file:/")) { pos = 6; while ((pos < urlString.length()) && (urlString.charAt(pos) == '/')) { pos++; } path = urlString.substring(pos-1); } else if (lowerURLString.startsWith("file:")) { path = relativeBasePath + urlString.substring(5); } else { throw new LDIFException(ERR_READ_URL_INVALID_SCHEME.get(attributeName, urlString, firstLineNumber), firstLineNumber, true, ldifLines, null); } try { final File f = new File(path); if (! f.exists()) { throw new LDIFException(ERR_READ_URL_NO_SUCH_FILE.get(attributeName, urlString, firstLineNumber, f.getAbsolutePath()), firstLineNumber, true, ldifLines, null); } final long fileSize = f.length(); if (fileSize > (10 * 1024 * 1024)) { throw new LDIFException(ERR_READ_URL_FILE_TOO_LARGE.get( attributeName, urlString, firstLineNumber, f.getAbsolutePath(), (10*1024*1024)), firstLineNumber, true, ldifLines, null); } int fileBytesRead = 0; int fileBytesRemaining = (int) fileSize; final byte[] fileData = new byte[(int) fileSize]; final FileInputStream fis = new FileInputStream(f); try { while (fileBytesRead < fileSize) { final int bytesRead = fis.read(fileData, fileBytesRead, fileBytesRemaining); if (bytesRead < 0) { throw new LDIFException(ERR_READ_URL_FILE_SIZE_CHANGED.get( attributeName, urlString, firstLineNumber, f.getAbsolutePath()), firstLineNumber, true, ldifLines, null); } fileBytesRead += bytesRead; fileBytesRemaining -= bytesRead; } if (fis.read() != -1) { throw new LDIFException(ERR_READ_URL_FILE_SIZE_CHANGED.get( attributeName, urlString, firstLineNumber, f.getAbsolutePath()), firstLineNumber, true, ldifLines, null); } } finally { fis.close(); } if (attrObject == null) { attr = new Attribute(attributeName, fileData); attributes.put(lowerName, attr); } else { if (! ldifAttr.addValue(new ASN1OctetString(fileData), duplicateValueBehavior)) { if (duplicateValueBehavior != DuplicateValueBehavior.STRIP) { throw new LDIFException(ERR_READ_DUPLICATE_VALUE.get(dn, firstLineNumber, attributeName), firstLineNumber, true, ldifLines, null); } } } } catch (LDIFException le) { debugException(le); throw le; } catch (Exception e) { debugException(e); throw new LDIFException(ERR_READ_URL_EXCEPTION.get(attributeName, urlString, firstLineNumber, e), firstLineNumber, true, ldifLines, e); } } else { int pos = colonPos+1; while ((pos < length) && (line.charAt(pos) == ' ')) { pos++; } final String valueString = line.substring(pos); if (attrObject == null) { attr = new Attribute(attributeName, valueString); attributes.put(lowerName, attr); } else { try { if (! ldifAttr.addValue(new ASN1OctetString(valueString), duplicateValueBehavior)) { if (duplicateValueBehavior != DuplicateValueBehavior.STRIP) { throw new LDIFException(ERR_READ_DUPLICATE_VALUE.get(dn, firstLineNumber, attributeName), firstLineNumber, true, ldifLines, null); } } } catch (LDAPException le) { throw new LDIFException(ERR_READ_VALUE_SYNTAX_VIOLATION.get(dn, firstLineNumber, attributeName, getExceptionMessage(le)), firstLineNumber, true, ldifLines, le); } } } } final ArrayList<Attribute> attrList = new ArrayList<Attribute>(attributes.size()); for (final Object o : attributes.values()) { if (o instanceof Attribute) { attrList.add((Attribute) o); } else { attrList.add(((LDIFAttribute) o).toAttribute()); } } return attrList; } private static Modification[] parseModifications(final String dn, final TrailingSpaceBehavior trailingSpaceBehavior, final ArrayList<StringBuilder> ldifLines, final Iterator<StringBuilder> iterator, final long firstLineNumber) throws LDIFException { final ArrayList<Modification> modList = new ArrayList<Modification>(ldifLines.size()); while (iterator.hasNext()) { StringBuilder line = iterator.next(); handleTrailingSpaces(line, dn, firstLineNumber, trailingSpaceBehavior); int colonPos = line.indexOf(":"); if (colonPos < 0) { throw new LDIFException(ERR_READ_MOD_CR_NO_MODTYPE.get(firstLineNumber), firstLineNumber, true, ldifLines, null); } final ModificationType modType; final String modTypeStr = toLowerCase(line.substring(0, colonPos)); if (modTypeStr.equals("add")) { modType = ModificationType.ADD; } else if (modTypeStr.equals("delete")) { modType = ModificationType.DELETE; } else if (modTypeStr.equals("replace")) { modType = ModificationType.REPLACE; } else if (modTypeStr.equals("increment")) { modType = ModificationType.INCREMENT; } else { throw new LDIFException(ERR_READ_MOD_CR_INVALID_MODTYPE.get(modTypeStr, firstLineNumber), firstLineNumber, true, ldifLines, null); } final String attributeName; int length = line.length(); if (length == (colonPos+1)) { throw new LDIFException(ERR_READ_MOD_CR_MODTYPE_NO_ATTR.get( firstLineNumber), firstLineNumber, true, ldifLines, null); } else if (line.charAt(colonPos+1) == ':') { int pos = colonPos+2; while ((pos < length) && (line.charAt(pos) == ' ')) { pos++; } try { final byte[] dnBytes = Base64.decode(line.substring(pos)); attributeName = new String(dnBytes, "UTF-8"); } catch (final ParseException pe) { debugException(pe); throw new LDIFException( ERR_READ_MOD_CR_MODTYPE_CANNOT_BASE64_DECODE_ATTR.get( firstLineNumber, pe.getMessage()), firstLineNumber, true, ldifLines, pe); } catch (final Exception e) { debugException(e); throw new LDIFException( ERR_READ_MOD_CR_MODTYPE_CANNOT_BASE64_DECODE_ATTR.get( firstLineNumber, e), firstLineNumber, true, ldifLines, e); } } else { int pos = colonPos+1; while ((pos < length) && (line.charAt(pos) == ' ')) { pos++; } attributeName = line.substring(pos); } if (attributeName.length() == 0) { throw new LDIFException(ERR_READ_MOD_CR_MODTYPE_NO_ATTR.get( firstLineNumber), firstLineNumber, true, ldifLines, null); } final ArrayList<ASN1OctetString> valueList = new ArrayList<ASN1OctetString>(ldifLines.size()); while (iterator.hasNext()) { line = iterator.next(); handleTrailingSpaces(line, dn, firstLineNumber, trailingSpaceBehavior); if (line.toString().equals("-")) { break; } colonPos = line.indexOf(":"); if (colonPos < 0) { throw new LDIFException(ERR_READ_NO_ATTR_COLON.get(firstLineNumber), firstLineNumber, true, ldifLines, null); } else if (! line.substring(0, colonPos).equalsIgnoreCase(attributeName)) { throw new LDIFException(ERR_READ_MOD_CR_ATTR_MISMATCH.get( firstLineNumber, line.substring(0, colonPos), attributeName), firstLineNumber, true, ldifLines, null); } final ASN1OctetString value; length = line.length(); if (length == (colonPos+1)) { value = new ASN1OctetString(); } else if (line.charAt(colonPos+1) == ':') { int pos = colonPos+2; while ((pos < length) && (line.charAt(pos) == ' ')) { pos++; } try { value = new ASN1OctetString(Base64.decode(line.substring(pos))); } catch (final ParseException pe) { debugException(pe); throw new LDIFException(ERR_READ_CANNOT_BASE64_DECODE_ATTR.get( attributeName, firstLineNumber, pe.getMessage()), firstLineNumber, true, ldifLines, pe); } catch (final Exception e) { debugException(e); throw new LDIFException(ERR_READ_CANNOT_BASE64_DECODE_ATTR.get( firstLineNumber, e), firstLineNumber, true, ldifLines, e); } } else { int pos = colonPos+1; while ((pos < length) && (line.charAt(pos) == ' ')) { pos++; } value = new ASN1OctetString(line.substring(pos)); } valueList.add(value); } final ASN1OctetString[] values = new ASN1OctetString[valueList.size()]; valueList.toArray(values); if ((modType.intValue() == ModificationType.ADD.intValue()) && (values.length == 0)) { throw new LDIFException(ERR_READ_MOD_CR_NO_ADD_VALUES.get(attributeName, firstLineNumber), firstLineNumber, true, ldifLines, null); } if ((modType.intValue() == ModificationType.INCREMENT.intValue()) && (values.length != 1)) { throw new LDIFException(ERR_READ_MOD_CR_INVALID_INCR_VALUE_COUNT.get( firstLineNumber, attributeName), firstLineNumber, true, ldifLines, null); } modList.add(new Modification(modType, attributeName, values)); } final Modification[] mods = new Modification[modList.size()]; modList.toArray(mods); return mods; } private static LDIFModifyDNChangeRecord parseModifyDNChangeRecord( final ArrayList<StringBuilder> ldifLines, final Iterator<StringBuilder> iterator, final String dn, final TrailingSpaceBehavior trailingSpaceBehavior, final long firstLineNumber) throws LDIFException { StringBuilder line = iterator.next(); handleTrailingSpaces(line, dn, firstLineNumber, trailingSpaceBehavior); int colonPos = line.indexOf(":"); if ((colonPos < 0) || (! line.substring(0, colonPos).equalsIgnoreCase("newrdn"))) { throw new LDIFException(ERR_READ_MODDN_CR_NO_NEWRDN_COLON.get( firstLineNumber), firstLineNumber, true, ldifLines, null); } final String newRDN; int length = line.length(); if (length == (colonPos+1)) { throw new LDIFException(ERR_READ_MODDN_CR_NO_NEWRDN_VALUE.get( firstLineNumber), firstLineNumber, true, ldifLines, null); } else if (line.charAt(colonPos+1) == ':') { int pos = colonPos+2; while ((pos < length) && (line.charAt(pos) == ' ')) { pos++; } try { final byte[] dnBytes = Base64.decode(line.substring(pos)); newRDN = new String(dnBytes, "UTF-8"); } catch (final ParseException pe) { debugException(pe); throw new LDIFException( ERR_READ_MODDN_CR_CANNOT_BASE64_DECODE_NEWRDN.get(firstLineNumber, pe.getMessage()), firstLineNumber, true, ldifLines, pe); } catch (final Exception e) { debugException(e); throw new LDIFException( ERR_READ_MODDN_CR_CANNOT_BASE64_DECODE_NEWRDN.get(firstLineNumber, e), firstLineNumber, true, ldifLines, e); } } else { int pos = colonPos+1; while ((pos < length) && (line.charAt(pos) == ' ')) { pos++; } newRDN = line.substring(pos); } if (newRDN.length() == 0) { throw new LDIFException(ERR_READ_MODDN_CR_NO_NEWRDN_VALUE.get( firstLineNumber), firstLineNumber, true, ldifLines, null); } if (! iterator.hasNext()) { throw new LDIFException(ERR_READ_MODDN_CR_NO_DELOLDRDN_COLON.get( firstLineNumber), firstLineNumber, true, ldifLines, null); } line = iterator.next(); handleTrailingSpaces(line, dn, firstLineNumber, trailingSpaceBehavior); colonPos = line.indexOf(":"); if ((colonPos < 0) || (! line.substring(0, colonPos).equalsIgnoreCase("deleteoldrdn"))) { throw new LDIFException(ERR_READ_MODDN_CR_NO_DELOLDRDN_COLON.get( firstLineNumber), firstLineNumber, true, ldifLines, null); } final String deleteOldRDNStr; length = line.length(); if (length == (colonPos+1)) { throw new LDIFException(ERR_READ_MODDN_CR_NO_DELOLDRDN_VALUE.get( firstLineNumber), firstLineNumber, true, ldifLines, null); } else if (line.charAt(colonPos+1) == ':') { int pos = colonPos+2; while ((pos < length) && (line.charAt(pos) == ' ')) { pos++; } try { final byte[] changeTypeBytes = Base64.decode(line.substring(pos)); deleteOldRDNStr = new String(changeTypeBytes, "UTF-8"); } catch (final ParseException pe) { debugException(pe); throw new LDIFException( ERR_READ_MODDN_CR_CANNOT_BASE64_DECODE_DELOLDRDN.get( firstLineNumber, pe.getMessage()), firstLineNumber, true, ldifLines, pe); } catch (final Exception e) { debugException(e); throw new LDIFException( ERR_READ_MODDN_CR_CANNOT_BASE64_DECODE_DELOLDRDN.get( firstLineNumber, e), firstLineNumber, true, ldifLines, e); } } else { int pos = colonPos+1; while ((pos < length) && (line.charAt(pos) == ' ')) { pos++; } deleteOldRDNStr = line.substring(pos); } final boolean deleteOldRDN; if (deleteOldRDNStr.equals("0")) { deleteOldRDN = false; } else if (deleteOldRDNStr.equals("1")) { deleteOldRDN = true; } else if (deleteOldRDNStr.equalsIgnoreCase("false") || deleteOldRDNStr.equalsIgnoreCase("no")) { deleteOldRDN = false; } else if (deleteOldRDNStr.equalsIgnoreCase("true") || deleteOldRDNStr.equalsIgnoreCase("yes")) { deleteOldRDN = false; } else { throw new LDIFException(ERR_READ_MODDN_CR_INVALID_DELOLDRDN.get( deleteOldRDNStr, firstLineNumber), firstLineNumber, true, ldifLines, null); } final String newSuperiorDN; if (iterator.hasNext()) { line = iterator.next(); handleTrailingSpaces(line, dn, firstLineNumber, trailingSpaceBehavior); colonPos = line.indexOf(":"); if ((colonPos < 0) || (! line.substring(0, colonPos).equalsIgnoreCase("newsuperior"))) { throw new LDIFException(ERR_READ_MODDN_CR_NO_NEWSUPERIOR_COLON.get( firstLineNumber), firstLineNumber, true, ldifLines, null); } length = line.length(); if (length == (colonPos+1)) { newSuperiorDN = ""; } else if (line.charAt(colonPos+1) == ':') { int pos = colonPos+2; while ((pos < length) && (line.charAt(pos) == ' ')) { pos++; } try { final byte[] dnBytes = Base64.decode(line.substring(pos)); newSuperiorDN = new String(dnBytes, "UTF-8"); } catch (final ParseException pe) { debugException(pe); throw new LDIFException( ERR_READ_MODDN_CR_CANNOT_BASE64_DECODE_NEWSUPERIOR.get( firstLineNumber, pe.getMessage()), firstLineNumber, true, ldifLines, pe); } catch (final Exception e) { debugException(e); throw new LDIFException( ERR_READ_MODDN_CR_CANNOT_BASE64_DECODE_NEWSUPERIOR.get( firstLineNumber, e), firstLineNumber, true, ldifLines, e); } } else { int pos = colonPos+1; while ((pos < length) && (line.charAt(pos) == ' ')) { pos++; } newSuperiorDN = line.substring(pos); } } else { newSuperiorDN = null; } if (iterator.hasNext()) { throw new LDIFException(ERR_READ_CR_EXTRA_MODDN_DATA.get(firstLineNumber), firstLineNumber, true, ldifLines, null); } return new LDIFModifyDNChangeRecord(dn, newRDN, deleteOldRDN, newSuperiorDN); } private static void handleTrailingSpaces(final StringBuilder buffer, final String dn, final long firstLineNumber, final TrailingSpaceBehavior trailingSpaceBehavior) throws LDIFException { int pos = buffer.length() - 1; boolean trailingFound = false; while ((pos >= 0) && (buffer.charAt(pos) == ' ')) { trailingFound = true; pos--; } if (trailingFound && (buffer.charAt(pos) != ':')) { switch (trailingSpaceBehavior) { case STRIP: buffer.setLength(pos+1); break; case REJECT: if (dn == null) { throw new LDIFException( ERR_READ_ILLEGAL_TRAILING_SPACE_WITHOUT_DN.get(firstLineNumber, buffer.toString()), firstLineNumber, true); } else { throw new LDIFException( ERR_READ_ILLEGAL_TRAILING_SPACE_WITH_DN.get(dn, firstLineNumber, buffer.toString()), firstLineNumber, true); } case RETAIN: default: break; } } } private static final class UnparsedLDIFRecord { private final ArrayList<StringBuilder> lineList; private final long firstLineNumber; private final Exception failureCause; private final boolean isEOF; private final DuplicateValueBehavior duplicateValueBehavior; private final Schema schema; private final TrailingSpaceBehavior trailingSpaceBehavior; private UnparsedLDIFRecord(final ArrayList<StringBuilder> lineList, final DuplicateValueBehavior duplicateValueBehavior, final TrailingSpaceBehavior trailingSpaceBehavior, final Schema schema, final long firstLineNumber) { this.lineList = lineList; this.firstLineNumber = firstLineNumber; this.duplicateValueBehavior = duplicateValueBehavior; this.trailingSpaceBehavior = trailingSpaceBehavior; this.schema = schema; failureCause = null; isEOF = (firstLineNumber < 0) || ((lineList != null) && lineList.isEmpty()); } private UnparsedLDIFRecord(final Exception failureCause) { this.failureCause = failureCause; lineList = null; firstLineNumber = 0; duplicateValueBehavior = DuplicateValueBehavior.REJECT; trailingSpaceBehavior = TrailingSpaceBehavior.REJECT; schema = null; isEOF = false; } private ArrayList<StringBuilder> getLineList() { return lineList; } private DuplicateValueBehavior getDuplicateValueBehavior() { return duplicateValueBehavior; } private TrailingSpaceBehavior getTrailingSpaceBehavior() { return trailingSpaceBehavior; } private Schema getSchema() { return schema; } private long getFirstLineNumber() { return firstLineNumber; } private boolean isEOF() { return isEOF; } private Exception getFailureCause() { return failureCause; } } private final class LineReaderThread extends Thread { private LineReaderThread() { super("Asynchronous LDIF line reader"); setDaemon(true); } @Override() public void run() { try { boolean stopProcessing = false; while (!stopProcessing) { UnparsedLDIFRecord unparsedRecord = null; try { unparsedRecord = readUnparsedRecord(); } catch (IOException e) { debugException(e); unparsedRecord = new UnparsedLDIFRecord(e); stopProcessing = true; } catch (Exception e) { debugException(e); unparsedRecord = new UnparsedLDIFRecord(e); } try { asyncParser.submit(unparsedRecord); } catch (InterruptedException e) { debugException(e); stopProcessing = true; } if ((unparsedRecord == null) || (unparsedRecord.isEOF())) { stopProcessing = true; } } } finally { try { asyncParser.shutdown(); } catch (InterruptedException e) { debugException(e); } finally { asyncParsingComplete.set(true); } } } } private final class RecordParser implements Processor<UnparsedLDIFRecord, LDIFRecord> { public LDIFRecord process(final UnparsedLDIFRecord input) throws LDIFException { LDIFRecord record = decodeRecord(input, relativeBasePath); if ((record instanceof Entry) && (entryTranslator != null)) { record = entryTranslator.translate((Entry) record, input.getFirstLineNumber()); if (record == null) { record = SKIP_ENTRY; } } return record; } } }