001/*
002 * Copyright 2016-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2016-2017 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 */
021package com.unboundid.util;
022
023
024
025import java.io.BufferedReader;
026import java.io.Closeable;
027import java.io.File;
028import java.io.FileReader;
029import java.io.IOException;
030import java.text.ParseException;
031import java.util.concurrent.atomic.AtomicLong;
032
033import com.unboundid.ldap.sdk.DN;
034import com.unboundid.ldap.sdk.LDAPException;
035import com.unboundid.ldap.sdk.ResultCode;
036
037import 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)
050public 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}