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}