001 /*
002 * Copyright 2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2016 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021 package com.unboundid.util;
022
023
024
025 import java.io.BufferedReader;
026 import java.io.Closeable;
027 import java.io.File;
028 import java.io.FileReader;
029 import java.io.IOException;
030 import java.text.ParseException;
031 import java.util.concurrent.atomic.AtomicLong;
032
033 import com.unboundid.ldap.sdk.DN;
034 import com.unboundid.ldap.sdk.LDAPException;
035 import com.unboundid.ldap.sdk.ResultCode;
036
037 import static com.unboundid.util.UtilityMessages.*;
038
039
040
041 /**
042 * This class provides a mechanism for reading DNs from a file. The file is
043 * expected to have one DN per line. Blank lines and lines beginning with the
044 * octothorpe (#) character will be ignored. Lines may contain just the raw DN,
045 * or they may start with "dn:" followed by an optional space and the DN, or
046 * "dn::" followed by an optional space and the base64-encoded representation of
047 * the DN.
048 */
049 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
050 public final class DNFileReader
051 implements Closeable
052 {
053 // A counter used to keep track of the line number for information read from
054 // the file.
055 private final AtomicLong lineNumberCounter;
056
057 // The reader to use to read the DNs.
058 private final BufferedReader reader;
059
060 // The file from which the DNs are being read.
061 private final File dnFile;
062
063
064
065 /**
066 * Creates a new DN file reader that will read from the file with the
067 * specified path.
068 *
069 * @param path The path to the file to be read. It must not be {@code null}
070 * and the file must exist.
071 *
072 * @throws IOException If a problem is encountered while opening the file
073 * for reading.
074 */
075 public DNFileReader(final String path)
076 throws IOException
077 {
078 this(new File(path));
079 }
080
081
082
083 /**
084 * Creates a new DN file reader that will read from the specified file.
085 *
086 * @param dnFile The file to be read. It must not be {@code null} and the
087 * file must exist.
088 *
089 * @throws IOException If a problem is encountered while opening the file
090 * for reading.
091 */
092 public DNFileReader(final File dnFile)
093 throws IOException
094 {
095 this.dnFile = dnFile;
096
097 reader = new BufferedReader(new FileReader(dnFile));
098 lineNumberCounter = new AtomicLong(0L);
099 }
100
101
102
103 /**
104 * Reads the next DN from the file.
105 *
106 * @return The DN read from the file, or {@code null} if there are no more
107 * DNs to be read.
108 *
109 * @throws IOException If a problem is encountered while trying to read from
110 * the file.
111 *
112 * @throws LDAPException If data read from the file can't be parsed as a DN.
113 */
114 public DN readDN()
115 throws IOException, LDAPException
116 {
117 while (true)
118 {
119 final long lineNumber;
120 final String line;
121 synchronized (this)
122 {
123 line = reader.readLine();
124 lineNumber = lineNumberCounter.incrementAndGet();
125 }
126
127 if (line == null)
128 {
129 return null;
130 }
131
132 final String trimmedLine = line.trim();
133 if ((trimmedLine.length() == 0) || trimmedLine.startsWith("#"))
134 {
135 continue;
136 }
137
138 String dnString = trimmedLine;
139 if (trimmedLine.charAt(2) == ':')
140 {
141 final String lowerLine = StaticUtils.toLowerCase(trimmedLine);
142 if (lowerLine.startsWith("dn::"))
143 {
144 final String base64String = line.substring(4).trim();
145
146 try
147 {
148 dnString = Base64.decodeToString(base64String);
149 }
150 catch (final ParseException pe)
151 {
152 Debug.debugException(pe);
153 throw new LDAPException(ResultCode.DECODING_ERROR,
154 ERR_DN_FILE_READER_CANNOT_BASE64_DECODE.get(base64String,
155 lineNumber, dnFile.getAbsolutePath(), pe.getMessage()),
156 pe);
157 }
158 }
159 else if (lowerLine.startsWith("dn:"))
160 {
161 dnString = line.substring(3).trim();
162 }
163 }
164
165 try
166 {
167 return new DN(dnString);
168 }
169 catch (final LDAPException le)
170 {
171 Debug.debugException(le);
172 throw new LDAPException(ResultCode.INVALID_DN_SYNTAX,
173 ERR_DN_FILE_READER_CANNOT_PARSE_DN.get(dnString, lineNumber,
174 dnFile.getAbsolutePath(), le.getMessage()),
175 le);
176 }
177 }
178 }
179
180
181
182 /**
183 * Closes this DN file reader.
184 *
185 * @throws IOException If a problem is encountered while closing the reader.
186 */
187 public void close()
188 throws IOException
189 {
190 reader.close();
191 }
192 }