001/* 002 * Copyright 2022-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2022-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2022-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.unboundidds.logs.v2.text; 037 038 039 040import java.io.BufferedReader; 041import java.io.File; 042import java.io.FileReader; 043import java.io.IOException; 044import java.io.InputStream; 045import java.io.InputStreamReader; 046import java.util.List; 047 048import com.unboundid.ldap.sdk.unboundidds.logs.AccessLogMessageType; 049import com.unboundid.ldap.sdk.unboundidds.logs.AccessLogOperationType; 050import com.unboundid.ldap.sdk.unboundidds.logs.LogException; 051import com.unboundid.ldap.sdk.unboundidds.logs.v2.AccessLogReader; 052import com.unboundid.util.NotNull; 053import com.unboundid.util.Nullable; 054import com.unboundid.util.ThreadSafety; 055import com.unboundid.util.ThreadSafetyLevel; 056 057import static com.unboundid.ldap.sdk.unboundidds.logs.v2.text.TextLogMessages.*; 058 059 060 061/** 062 * This class provides a mechanism for reading text-formatted access log 063 * messages. 064 * <BR> 065 * <BLOCKQUOTE> 066 * <B>NOTE:</B> This class, and other classes within the 067 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 068 * supported for use against Ping Identity, UnboundID, and 069 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 070 * for proprietary functionality or for external specifications that are not 071 * considered stable or mature enough to be guaranteed to work in an 072 * interoperable way with other types of LDAP servers. 073 * </BLOCKQUOTE> 074 */ 075@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 076public final class TextFormattedAccessLogReader 077 implements AccessLogReader 078{ 079 // The buffered reader that will be used to read log messages. 080 @NotNull private final BufferedReader logReader; 081 082 083 084 /** 085 * Creates a new text-formatted access log reader that will read log messages 086 * from the specified file. 087 * 088 * @param logFilePath The path to the log file from which the access log 089 * messages will be read. It must not be {@code null}. 090 * 091 * @throws IOException If a problem occurs while opening the specified file 092 * for reading. 093 */ 094 public TextFormattedAccessLogReader(@NotNull final String logFilePath) 095 throws IOException 096 { 097 this(new File(logFilePath)); 098 } 099 100 101 102 /** 103 * Creates a new text-formatted access log reader that will read log messages 104 * messages from the specified file. 105 * 106 * @param logFile The log file from which the access log messages will be 107 * read. It must not be {@code null}. 108 * 109 * @throws IOException If a problem occurs while opening the specified file 110 * for reading. 111 */ 112 public TextFormattedAccessLogReader(@NotNull final File logFile) 113 throws IOException 114 { 115 logReader = new BufferedReader(new FileReader(logFile)); 116 } 117 118 119 120 /** 121 * Creates a new text-formatted access log reader that will read log messages 122 * from the provided input stream. 123 * 124 * @param inputStream The input stream from which the access log messages 125 * will be read. It must not be {@code null}. 126 */ 127 public TextFormattedAccessLogReader(@NotNull final InputStream inputStream) 128 { 129 logReader = new BufferedReader(new InputStreamReader(inputStream)); 130 } 131 132 133 134 /** 135 * {@inheritDoc} 136 */ 137 @Override() 138 @Nullable() 139 public TextFormattedAccessLogMessage readMessage() 140 throws IOException, LogException 141 { 142 // Read the next line from the log. If this fails, then throw an 143 // IOException to indicate that we can't continue reading. If the line is 144 // blank or starts with an octothorpe (indicating that it's a comment), then 145 // skip it and read the next one. 146 String messageString; 147 while (true) 148 { 149 messageString = logReader.readLine(); 150 if (messageString == null) 151 { 152 return null; 153 } 154 155 if (messageString.isEmpty() || messageString.startsWith("#")) 156 { 157 continue; 158 } 159 160 break; 161 } 162 163 return parseMessage(messageString); 164 } 165 166 167 168 /** 169 * Parses the contents of the provided string as a JSON-formatted access log 170 * message. 171 * 172 * @param messageString The string to parse as an access log message. It 173 * must not be {@code null}. 174 * 175 * @return The parsed access log message. 176 * 177 * @throws LogException If the provided JSON object cannot be parsed as a 178 * valid access log message. 179 */ 180 @NotNull() 181 public static TextFormattedAccessLogMessage parseMessage( 182 @NotNull final String messageString) 183 throws LogException 184 { 185 // Parse the line as a generic log message, which will give us access to 186 // all of its fields. 187 final TextFormattedLogMessage m = 188 new TextFormattedLogMessage(messageString); 189 190 191 // Make sure that the message has at least one field without a name. 192 final List<String> unnamedFields = m.getFields().get( 193 TextFormattedLogMessage.NO_FIELD_NAME); 194 if ((unnamedFields == null) || unnamedFields.isEmpty()) 195 { 196 throw new LogException(messageString, 197 ERR_TEXT_ACCESS_READER_CANNOT_DETERMINE_MESSAGE_TYPE.get( 198 messageString)); 199 } 200 201 202 // Look at the first unnamed field. For messages that are not associated 203 // with an operation, then it will be the message type. For messages that 204 // are associated with an operation, then the operation type will come 205 // first, so it's okay if we can't parse the first unnamed field value as a 206 // message type. 207 final String messageOrOpType = unnamedFields.get(0); 208 AccessLogMessageType messageType = 209 AccessLogMessageType.forName(messageOrOpType); 210 if (messageType != null) 211 { 212 switch (messageType) 213 { 214 case CONNECT: 215 return new TextFormattedConnectAccessLogMessage(m); 216 case DISCONNECT: 217 return new TextFormattedDisconnectAccessLogMessage(m); 218 case SECURITY_NEGOTIATION: 219 return new TextFormattedSecurityNegotiationAccessLogMessage(m); 220 case CLIENT_CERTIFICATE: 221 return new TextFormattedClientCertificateAccessLogMessage(m); 222 case ENTRY_REBALANCING_REQUEST: 223 return new TextFormattedEntryRebalancingRequestAccessLogMessage(m); 224 case ENTRY_REBALANCING_RESULT: 225 return new TextFormattedEntryRebalancingResultAccessLogMessage(m); 226 default: 227 throw new LogException(messageString, 228 ERR_TEXT_ACCESS_READER_CANNOT_DETERMINE_MESSAGE_TYPE.get( 229 messageString)); 230 } 231 } 232 233 234 // If we've gotten here, then we expect the first unnamed field to be the 235 // operation type, and the second field will be the message type. 236 final AccessLogOperationType opType = 237 AccessLogOperationType.forName(messageOrOpType); 238 if (opType == null) 239 { 240 throw new LogException(messageString, 241 ERR_TEXT_ACCESS_READER_CANNOT_DETERMINE_MESSAGE_TYPE.get( 242 messageOrOpType)); 243 } 244 245 if (unnamedFields.size() < 2) 246 { 247 throw new LogException(messageString, 248 ERR_TEXT_ACCESS_READER_CANNOT_DETERMINE_MESSAGE_TYPE.get( 249 messageString)); 250 } 251 252 messageType = AccessLogMessageType.forName(unnamedFields.get(1)); 253 if (messageType == null) 254 { 255 throw new LogException(messageString, 256 ERR_TEXT_ACCESS_READER_CANNOT_DETERMINE_MESSAGE_TYPE.get( 257 messageString)); 258 } 259 260 switch (messageType) 261 { 262 case REQUEST: 263 switch (opType) 264 { 265 case ABANDON: 266 return new TextFormattedAbandonRequestAccessLogMessage(m); 267 case ADD: 268 return new TextFormattedAddRequestAccessLogMessage(m); 269 case BIND: 270 return new TextFormattedBindRequestAccessLogMessage(m); 271 case COMPARE: 272 return new TextFormattedCompareRequestAccessLogMessage(m); 273 case DELETE: 274 return new TextFormattedDeleteRequestAccessLogMessage(m); 275 case EXTENDED: 276 return new TextFormattedExtendedRequestAccessLogMessage(m); 277 case MODIFY: 278 return new TextFormattedModifyRequestAccessLogMessage(m); 279 case MODDN: 280 return new TextFormattedModifyDNRequestAccessLogMessage(m); 281 case SEARCH: 282 return new TextFormattedSearchRequestAccessLogMessage(m); 283 case UNBIND: 284 return new TextFormattedUnbindRequestAccessLogMessage(m); 285 default: 286 throw new LogException(messageString, 287 ERR_TEXT_ACCESS_READER_UNSUPPORTED_REQUEST_OP_TYPE.get( 288 messageString)); 289 } 290 291 case FORWARD: 292 switch (opType) 293 { 294 case ABANDON: 295 return new TextFormattedAbandonForwardAccessLogMessage(m); 296 case ADD: 297 return new TextFormattedAddForwardAccessLogMessage(m); 298 case BIND: 299 return new TextFormattedBindForwardAccessLogMessage(m); 300 case COMPARE: 301 return new TextFormattedCompareForwardAccessLogMessage(m); 302 case DELETE: 303 return new TextFormattedDeleteForwardAccessLogMessage(m); 304 case EXTENDED: 305 return new TextFormattedExtendedForwardAccessLogMessage(m); 306 case MODIFY: 307 return new TextFormattedModifyForwardAccessLogMessage(m); 308 case MODDN: 309 return new TextFormattedModifyDNForwardAccessLogMessage(m); 310 case SEARCH: 311 return new TextFormattedSearchForwardAccessLogMessage(m); 312 case UNBIND: 313 default: 314 throw new LogException(messageString, 315 ERR_TEXT_ACCESS_READER_UNSUPPORTED_FORWARD_OP_TYPE.get( 316 messageString)); 317 } 318 319 case FORWARD_FAILED: 320 switch (opType) 321 { 322 case ABANDON: 323 return new TextFormattedAbandonForwardFailedAccessLogMessage(m); 324 case ADD: 325 return new TextFormattedAddForwardFailedAccessLogMessage(m); 326 case BIND: 327 return new TextFormattedBindForwardFailedAccessLogMessage(m); 328 case COMPARE: 329 return new TextFormattedCompareForwardFailedAccessLogMessage(m); 330 case DELETE: 331 return new TextFormattedDeleteForwardFailedAccessLogMessage(m); 332 case EXTENDED: 333 return new TextFormattedExtendedForwardFailedAccessLogMessage(m); 334 case MODIFY: 335 return new TextFormattedModifyForwardFailedAccessLogMessage(m); 336 case MODDN: 337 return new TextFormattedModifyDNForwardFailedAccessLogMessage(m); 338 case SEARCH: 339 return new TextFormattedSearchForwardFailedAccessLogMessage(m); 340 case UNBIND: 341 default: 342 throw new LogException(messageString, 343 ERR_TEXT_ACCESS_READER_UNSUPPORTED_FORWARD_FAILED_OP_TYPE.get( 344 messageString)); 345 } 346 347 case RESULT: 348 switch (opType) 349 { 350 case ABANDON: 351 return new TextFormattedAbandonResultAccessLogMessage(m); 352 case ADD: 353 return new TextFormattedAddResultAccessLogMessage(m); 354 case BIND: 355 return new TextFormattedBindResultAccessLogMessage(m); 356 case COMPARE: 357 return new TextFormattedCompareResultAccessLogMessage(m); 358 case DELETE: 359 return new TextFormattedDeleteResultAccessLogMessage(m); 360 case EXTENDED: 361 return new TextFormattedExtendedResultAccessLogMessage(m); 362 case MODIFY: 363 return new TextFormattedModifyResultAccessLogMessage(m); 364 case MODDN: 365 return new TextFormattedModifyDNResultAccessLogMessage(m); 366 case SEARCH: 367 return new TextFormattedSearchResultAccessLogMessage(m); 368 case UNBIND: 369 default: 370 throw new LogException(messageString, 371 ERR_TEXT_ACCESS_READER_UNSUPPORTED_RESULT_OP_TYPE.get( 372 messageString)); 373 } 374 375 case ASSURANCE_COMPLETE: 376 switch (opType) 377 { 378 case ADD: 379 return new TextFormattedAddAssuranceCompletedAccessLogMessage(m); 380 case DELETE: 381 return new TextFormattedDeleteAssuranceCompletedAccessLogMessage(m); 382 case MODIFY: 383 return new TextFormattedModifyAssuranceCompletedAccessLogMessage(m); 384 case MODDN: 385 return new TextFormattedModifyDNAssuranceCompletedAccessLogMessage( 386 m); 387 case ABANDON: 388 case BIND: 389 case COMPARE: 390 case EXTENDED: 391 case SEARCH: 392 case UNBIND: 393 default: 394 throw new LogException(messageString, 395 ERR_TEXT_ACCESS_READER_UNSUPPORTED_ASSURANCE_OP_TYPE.get( 396 messageString)); 397 } 398 399 case ENTRY: 400 return new TextFormattedSearchEntryAccessLogMessage(m); 401 402 case REFERENCE: 403 return new TextFormattedSearchReferenceAccessLogMessage(m); 404 405 case INTERMEDIATE_RESPONSE: 406 return new TextFormattedIntermediateResponseAccessLogMessage(m, opType); 407 408 default: 409 throw new LogException(messageString, 410 ERR_TEXT_ACCESS_READER_CANNOT_DETERMINE_MESSAGE_TYPE.get( 411 messageString)); 412 } 413 } 414 415 416 417 /** 418 * {@inheritDoc} 419 */ 420 @Override() 421 public void close() 422 throws IOException 423 { 424 logReader.close(); 425 } 426}