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}