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    }