001/* 002 * Copyright 2018-2019 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2018-2019 Ping Identity Corporation 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.ldap.sdk.unboundidds.logs; 022 023 024 025import java.io.BufferedReader; 026import java.io.Closeable; 027import java.io.File; 028import java.io.FileReader; 029import java.io.InputStream; 030import java.io.IOException; 031import java.io.InputStreamReader; 032import java.io.Reader; 033import java.util.ArrayList; 034import java.util.List; 035 036import com.unboundid.ldif.LDIFAddChangeRecord; 037import com.unboundid.ldif.LDIFChangeRecord; 038import com.unboundid.ldif.LDIFDeleteChangeRecord; 039import com.unboundid.ldif.LDIFModifyChangeRecord; 040import com.unboundid.ldif.LDIFModifyDNChangeRecord; 041import com.unboundid.ldif.LDIFReader; 042import com.unboundid.util.Debug; 043import com.unboundid.util.NotMutable; 044import com.unboundid.util.StaticUtils; 045import com.unboundid.util.ThreadSafety; 046import com.unboundid.util.ThreadSafetyLevel; 047 048import static com.unboundid.ldap.sdk.unboundidds.logs.LogMessages.*; 049 050 051 052/** 053 * This class provides a mechanism for reading messages from a Directory Server 054 * audit log. 055 * <BR> 056 * <BLOCKQUOTE> 057 * <B>NOTE:</B> This class, and other classes within the 058 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 059 * supported for use against Ping Identity, UnboundID, and 060 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 061 * for proprietary functionality or for external specifications that are not 062 * considered stable or mature enough to be guaranteed to work in an 063 * interoperable way with other types of LDAP servers. 064 * </BLOCKQUOTE> 065 */ 066@NotMutable() 067@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 068public final class AuditLogReader 069 implements Closeable 070{ 071 // The reader used to read the contents of the log file. 072 private final BufferedReader reader; 073 074 075 076 /** 077 * Creates a new audit log reader that will read messages from the specified 078 * log file. 079 * 080 * @param path The path of the log file to read. 081 * 082 * @throws IOException If a problem occurs while opening the file for 083 * reading. 084 */ 085 public AuditLogReader(final String path) 086 throws IOException 087 { 088 reader = new BufferedReader(new FileReader(path)); 089 } 090 091 092 093 /** 094 * Creates a new audit log reader that will read messages from the specified 095 * log file. 096 * 097 * @param file The log file to read. 098 * 099 * @throws IOException If a problem occurs while opening the file for 100 * reading. 101 */ 102 public AuditLogReader(final File file) 103 throws IOException 104 { 105 reader = new BufferedReader(new FileReader(file)); 106 } 107 108 109 110 /** 111 * Creates a new audit log reader that will read messages using the provided 112 * {@code Reader} object. 113 * 114 * @param reader The reader to use to read log messages. 115 */ 116 public AuditLogReader(final Reader reader) 117 { 118 if (reader instanceof BufferedReader) 119 { 120 this.reader = (BufferedReader) reader; 121 } 122 else 123 { 124 this.reader = new BufferedReader(reader); 125 } 126 } 127 128 129 130 /** 131 * Creates a new audit log reader that will read messages from the provided 132 * input stream. 133 * 134 * @param inputStream The input stream from which to read log messages. 135 */ 136 public AuditLogReader(final InputStream inputStream) 137 { 138 reader = new BufferedReader(new InputStreamReader(inputStream)); 139 } 140 141 142 143 /** 144 * Reads the next audit log message from the log file. 145 * 146 * @return The audit log message read from the log file, or {@code null} if 147 * there are no more messages to be read. 148 * 149 * @throws IOException If an error occurs while trying to read from the 150 * file. 151 * 152 * @throws AuditLogException If an error occurs while trying to parse the 153 * log message. 154 */ 155 public AuditLogMessage read() 156 throws IOException, AuditLogException 157 { 158 // Read a list of lines until we find the end of the file or a blank line 159 // after a series of non-blank lines. 160 final List<String> fullMessageLines = new ArrayList<>(20); 161 final List<String> nonCommentLines = new ArrayList<>(20); 162 while (true) 163 { 164 final String line = reader.readLine(); 165 if (line == null) 166 { 167 // We hit the end of the audit log file. We obviously can't read any 168 // more. 169 break; 170 } 171 172 if (line.isEmpty()) 173 { 174 if (nonCommentLines.isEmpty()) 175 { 176 // This means that we encountered consecutive blank lines, or blank 177 // lines with only comments between them. This is okay. We'll just 178 // clear the list of full message lines and keep reading. 179 fullMessageLines.clear(); 180 continue; 181 } 182 else 183 { 184 // We found a blank line after some non-blank lines that included at 185 // least one non-comment line. Break out of the loop and process what 186 // we read as an audit log message. 187 break; 188 } 189 } 190 else 191 { 192 // We read a non-empty line. Add it to the list of full message lines, 193 // and if it's not a comment, then add it to the list of non-comment 194 // lines. 195 fullMessageLines.add(line); 196 if (! line.startsWith("#")) 197 { 198 nonCommentLines.add(line); 199 } 200 } 201 } 202 203 204 // If we've gotten here and the list of non-comment lines is empty, then 205 // that must mean that we hit the end of the audit log without finding any 206 // more messages. In that case, return null to indicate that we've hit the 207 // end of the file. 208 if (nonCommentLines.isEmpty()) 209 { 210 return null; 211 } 212 213 214 // Try to parse the set of non-comment lines as an LDIF change record. If 215 // that fails, then throw a log exception. 216 final LDIFChangeRecord changeRecord; 217 try 218 { 219 final String[] ldifLines = 220 StaticUtils.toArray(nonCommentLines, String.class); 221 changeRecord = LDIFReader.decodeChangeRecord(ldifLines); 222 } 223 catch (final Exception e) 224 { 225 Debug.debugException(e); 226 227 final String concatenatedLogLines = StaticUtils.concatenateStrings( 228 "[ ", "\"", ", ", "\"", " ]", fullMessageLines); 229 throw new AuditLogException(fullMessageLines, 230 ERR_AUDIT_LOG_READER_CANNOT_PARSE_CHANGE_RECORD.get( 231 concatenatedLogLines, StaticUtils.getExceptionMessage(e)), 232 e); 233 } 234 235 236 // Create the appropriate type of audit log message based on the change 237 // record. 238 if (changeRecord instanceof LDIFAddChangeRecord) 239 { 240 return new AddAuditLogMessage(fullMessageLines, 241 (LDIFAddChangeRecord) changeRecord); 242 } 243 else if (changeRecord instanceof LDIFDeleteChangeRecord) 244 { 245 return new DeleteAuditLogMessage(fullMessageLines, 246 (LDIFDeleteChangeRecord) changeRecord); 247 } 248 else if (changeRecord instanceof LDIFModifyChangeRecord) 249 { 250 return new ModifyAuditLogMessage(fullMessageLines, 251 (LDIFModifyChangeRecord) changeRecord); 252 } 253 else if (changeRecord instanceof LDIFModifyDNChangeRecord) 254 { 255 return new ModifyDNAuditLogMessage(fullMessageLines, 256 (LDIFModifyDNChangeRecord) changeRecord); 257 } 258 else 259 { 260 // This should never happen. 261 final String concatenatedLogLines = StaticUtils.concatenateStrings( 262 "[ ", "\"", ", ", "\"", " ]", fullMessageLines); 263 throw new AuditLogException(fullMessageLines, 264 ERR_AUDIT_LOG_READER_UNSUPPORTED_CHANGE_RECORD.get( 265 concatenatedLogLines, changeRecord.getChangeType().getName())); 266 } 267 } 268 269 270 271 /** 272 * Closes this error log reader. 273 * 274 * @throws IOException If a problem occurs while closing the reader. 275 */ 276 @Override() 277 public void close() 278 throws IOException 279 { 280 reader.close(); 281 } 282}