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.json;
037
038
039
040import java.io.File;
041import java.io.FileInputStream;
042import java.io.IOException;
043import java.io.InputStream;
044
045import com.unboundid.ldap.sdk.unboundidds.logs.AccessLogMessageType;
046import com.unboundid.ldap.sdk.unboundidds.logs.AccessLogOperationType;
047import com.unboundid.ldap.sdk.unboundidds.logs.LogException;
048import com.unboundid.ldap.sdk.unboundidds.logs.v2.AccessLogReader;
049import com.unboundid.util.Debug;
050import com.unboundid.util.NotNull;
051import com.unboundid.util.Nullable;
052import com.unboundid.util.StaticUtils;
053import com.unboundid.util.ThreadSafety;
054import com.unboundid.util.ThreadSafetyLevel;
055import com.unboundid.util.json.JSONException;
056import com.unboundid.util.json.JSONObject;
057import com.unboundid.util.json.JSONObjectReader;
058
059import static com.unboundid.ldap.sdk.unboundidds.logs.v2.json.JSONLogMessages.*;
060
061
062
063/**
064 * This class provides a mechanism for reading JSON-formatted access log
065 * messages.
066 * <BR>
067 * <BLOCKQUOTE>
068 *   <B>NOTE:</B>  This class, and other classes within the
069 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
070 *   supported for use against Ping Identity, UnboundID, and
071 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
072 *   for proprietary functionality or for external specifications that are not
073 *   considered stable or mature enough to be guaranteed to work in an
074 *   interoperable way with other types of LDAP servers.
075 * </BLOCKQUOTE>
076 */
077@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
078public final class JSONAccessLogReader
079       implements AccessLogReader
080{
081  // The JSON object reader that will be used to read log messages.
082  @NotNull private final JSONObjectReader jsonObjectReader;
083
084
085
086  /**
087   * Creates a new JSON access log reader that will read JSON-formatted access
088   * log messages from the specified file.
089   *
090   * @param  logFilePath  The path to the log file from which the access log
091   *                      messages will be read.  It must not be {@code null}.
092   *
093   * @throws  IOException  If a problem occurs while opening the specified file
094   *                       for reading.
095   */
096  public JSONAccessLogReader(@NotNull final String logFilePath)
097         throws IOException
098  {
099    this(new File(logFilePath));
100  }
101
102
103
104  /**
105   * Creates a new JSON access log reader that will read JSON-formatted access
106   * log messages from the specified file.
107   *
108   * @param  logFile  The log file from which the access log messages will be
109   *                  read.  It must not be {@code null}.
110   *
111   * @throws  IOException  If a problem occurs while opening the specified file
112   *                       for reading.
113   */
114  public JSONAccessLogReader(@NotNull final File logFile)
115         throws IOException
116  {
117    this(new FileInputStream(logFile));
118  }
119
120
121
122  /**
123   * Creates a new JSON access log reader that will read JSON-formatted access
124   * log messages from the provided input stream.
125   *
126   * @param  inputStream  The input stream from which the access log messages
127   *                      will be read.  It must not be {@code null}.
128   */
129  public JSONAccessLogReader(@NotNull final InputStream inputStream)
130  {
131    jsonObjectReader = new JSONObjectReader(inputStream);
132  }
133
134
135
136  /**
137   * {@inheritDoc}
138   */
139  @Override()
140  @Nullable()
141  public JSONAccessLogMessage readMessage()
142         throws IOException, LogException
143  {
144    // Read the next JSON object from the log.  If this fails, then throw an
145    // IOException to indicate that we can't continue reading.
146    final JSONObject messageObject;
147    try
148    {
149      messageObject = jsonObjectReader.readObject();
150    }
151    catch (final JSONException e)
152    {
153      Debug.debugException(e);
154      throw new IOException(
155           ERR_JSON_ACCESS_LOG_READER_NOT_VALID_JSON.get(
156                StaticUtils.getExceptionMessage(e)),
157           e);
158    }
159
160    if (messageObject == null)
161    {
162      return null;
163    }
164
165    return parseMessage(messageObject);
166  }
167
168
169
170  /**
171   * Parses the contents of the provided JSON object as a JSON-formatted access
172   * log message.
173   *
174   * @param  messageObject  The JSON object to parse as an access log message.
175   *                        It must not be {@code null}.
176   *
177   * @return  The parsed access log message.
178   *
179   * @throws  LogException  If the provided JSON object cannot be parsed as a
180   *                        valid access log message.
181   */
182  @NotNull()
183  public static JSONAccessLogMessage parseMessage(
184              @NotNull final JSONObject messageObject)
185         throws LogException
186  {
187    // Determine the message type for the log message.
188    final String messageTypeStr = messageObject.getFieldAsString(
189         JSONFormattedAccessLogFields.MESSAGE_TYPE.getFieldName());
190    if (messageTypeStr == null)
191    {
192      final String messageStr = messageObject.toSingleLineString();
193      throw new LogException(messageStr,
194           ERR_JSON_ACCESS_LOG_READER_MISSING_MESSAGE_TYPE.get(messageStr,
195                JSONFormattedAccessLogFields.MESSAGE_TYPE.getFieldName()));
196    }
197
198    final AccessLogMessageType messageType =
199         AccessLogMessageType.forName(messageTypeStr);
200    if (messageType == null)
201    {
202      final String messageStr = messageObject.toSingleLineString();
203      throw new LogException(messageStr,
204           ERR_JSON_ACCESS_LOG_READER_UNSUPPORTED_MESSAGE_TYPE.get(messageStr,
205                messageTypeStr));
206    }
207
208    switch (messageType)
209    {
210      case CONNECT:
211        return new JSONConnectAccessLogMessage(messageObject);
212      case DISCONNECT:
213        return new JSONDisconnectAccessLogMessage(messageObject);
214      case SECURITY_NEGOTIATION:
215        return new JSONSecurityNegotiationAccessLogMessage(messageObject);
216      case CLIENT_CERTIFICATE:
217        return new JSONClientCertificateAccessLogMessage(messageObject);
218      case ENTRY_REBALANCING_REQUEST:
219        return new JSONEntryRebalancingRequestAccessLogMessage(messageObject);
220      case ENTRY_REBALANCING_RESULT:
221        return new JSONEntryRebalancingResultAccessLogMessage(messageObject);
222      case ENTRY:
223        return new JSONSearchEntryAccessLogMessage(messageObject);
224      case REFERENCE:
225        return new JSONSearchReferenceAccessLogMessage(messageObject);
226      case INTERMEDIATE_RESPONSE:
227        return new JSONIntermediateResponseAccessLogMessage(messageObject);
228      case REQUEST:
229        return createRequestMessage(messageObject);
230      case RESULT:
231        return createResultMessage(messageObject);
232      case FORWARD:
233        return createForwardMessage(messageObject);
234      case FORWARD_FAILED:
235        return createForwardFailedMessage(messageObject);
236      case ASSURANCE_COMPLETE:
237        return createAssuranceCompleteMessage(messageObject);
238      default:
239        final String messageStr = messageObject.toSingleLineString();
240        throw new LogException(messageStr,
241             ERR_JSON_ACCESS_LOG_READER_UNSUPPORTED_MESSAGE_TYPE.get(messageStr,
242                  messageTypeStr));
243    }
244  }
245
246
247
248  /**
249   * Creates a new request access log message from the provided JSON object.
250   *
251   * @param  messageObject  The JSON object containing an encoded representation
252   *                        of a request access log message.  It must not be
253   *                        {@code null}.
254   *
255   * @return  The request access log message that was created.
256   *
257   * @throws  LogException  If the provided JSON object cannot be decoded as a
258   *                        valid request access log message.
259   */
260  @NotNull()
261  private static JSONAccessLogMessage createRequestMessage(
262               @NotNull final JSONObject messageObject)
263          throws LogException
264  {
265    final AccessLogOperationType operationType =
266         getOperationType(messageObject);
267    switch (operationType)
268    {
269      case ABANDON:
270        return new JSONAbandonRequestAccessLogMessage(messageObject);
271      case ADD:
272        return new JSONAddRequestAccessLogMessage(messageObject);
273      case BIND:
274        return new JSONBindRequestAccessLogMessage(messageObject);
275      case COMPARE:
276        return new JSONCompareRequestAccessLogMessage(messageObject);
277      case DELETE:
278        return new JSONDeleteRequestAccessLogMessage(messageObject);
279      case EXTENDED:
280        return new JSONExtendedRequestAccessLogMessage(messageObject);
281      case MODIFY:
282        return new JSONModifyRequestAccessLogMessage(messageObject);
283      case MODDN:
284        return new JSONModifyDNRequestAccessLogMessage(messageObject);
285      case SEARCH:
286        return new JSONSearchRequestAccessLogMessage(messageObject);
287      case UNBIND:
288        return new JSONUnbindRequestAccessLogMessage(messageObject);
289      default:
290        final String messageStr = messageObject.toSingleLineString();
291        throw new LogException(messageStr,
292             ERR_JSON_ACCESS_LOG_READER_UNSUPPORTED_REQUEST_OP_TYPE.get(
293                  messageStr, operationType.getLogIdentifier()));
294    }
295  }
296
297
298
299  /**
300   * Creates a new result access log message from the provided JSON object.
301   *
302   * @param  messageObject  The JSON object containing an encoded representation
303   *                        of a result access log message.  It must not be
304   *                        {@code null}.
305   *
306   * @return  The result access log message that was created.
307   *
308   * @throws  LogException  If the provided JSON object cannot be decoded as a
309   *                        valid result access log message.
310   */
311  @NotNull()
312  private static JSONAccessLogMessage createResultMessage(
313               @NotNull final JSONObject messageObject)
314          throws LogException
315  {
316    final AccessLogOperationType operationType =
317         getOperationType(messageObject);
318    switch (operationType)
319    {
320      case ABANDON:
321        return new JSONAbandonResultAccessLogMessage(messageObject);
322      case ADD:
323        return new JSONAddResultAccessLogMessage(messageObject);
324      case BIND:
325        return new JSONBindResultAccessLogMessage(messageObject);
326      case COMPARE:
327        return new JSONCompareResultAccessLogMessage(messageObject);
328      case DELETE:
329        return new JSONDeleteResultAccessLogMessage(messageObject);
330      case EXTENDED:
331        return new JSONExtendedResultAccessLogMessage(messageObject);
332      case MODIFY:
333        return new JSONModifyResultAccessLogMessage(messageObject);
334      case MODDN:
335        return new JSONModifyDNResultAccessLogMessage(messageObject);
336      case SEARCH:
337        return new JSONSearchResultAccessLogMessage(messageObject);
338      case UNBIND:
339      default:
340        final String messageStr = messageObject.toSingleLineString();
341        throw new LogException(messageStr,
342             ERR_JSON_ACCESS_LOG_READER_UNSUPPORTED_RESULT_OP_TYPE.get(
343                  messageStr, operationType.getLogIdentifier()));
344    }
345  }
346
347
348
349  /**
350   * Creates a new forward access log message from the provided JSON object.
351   *
352   * @param  messageObject  The JSON object containing an encoded representation
353   *                        of a forward access log message.  It must not be
354   *                        {@code null}.
355   *
356   * @return  The forward access log message that was created.
357   *
358   * @throws  LogException  If the provided JSON object cannot be decoded as a
359   *                        valid forward access log message.
360   */
361  @NotNull()
362  private static JSONAccessLogMessage createForwardMessage(
363               @NotNull final JSONObject messageObject)
364          throws LogException
365  {
366    final AccessLogOperationType operationType =
367         getOperationType(messageObject);
368    switch (operationType)
369    {
370      case ABANDON:
371        return new JSONAbandonForwardAccessLogMessage(messageObject);
372      case ADD:
373        return new JSONAddForwardAccessLogMessage(messageObject);
374      case BIND:
375        return new JSONBindForwardAccessLogMessage(messageObject);
376      case COMPARE:
377        return new JSONCompareForwardAccessLogMessage(messageObject);
378      case DELETE:
379        return new JSONDeleteForwardAccessLogMessage(messageObject);
380      case EXTENDED:
381        return new JSONExtendedForwardAccessLogMessage(messageObject);
382      case MODIFY:
383        return new JSONModifyForwardAccessLogMessage(messageObject);
384      case MODDN:
385        return new JSONModifyDNForwardAccessLogMessage(messageObject);
386      case SEARCH:
387        return new JSONSearchForwardAccessLogMessage(messageObject);
388      case UNBIND:
389      default:
390        final String messageStr = messageObject.toSingleLineString();
391        throw new LogException(messageStr,
392             ERR_JSON_ACCESS_LOG_READER_UNSUPPORTED_FORWARD_OP_TYPE.get(
393                  messageStr, operationType.getLogIdentifier()));
394    }
395  }
396
397
398
399  /**
400   * Creates a new forward failed access log message from the provided JSON
401   * object.
402   *
403   * @param  messageObject  The JSON object containing an encoded representation
404   *                        of a forward failed access log message.  It must not
405   *                        be {@code null}.
406   *
407   * @return  The forward failed access log message that was created.
408   *
409   * @throws  LogException  If the provided JSON object cannot be decoded as a
410   *                        valid forward failed access log message.
411   */
412  @NotNull()
413  private static JSONAccessLogMessage createForwardFailedMessage(
414               @NotNull final JSONObject messageObject)
415          throws LogException
416  {
417    final AccessLogOperationType operationType =
418         getOperationType(messageObject);
419    switch (operationType)
420    {
421      case ABANDON:
422        return new JSONAbandonForwardFailedAccessLogMessage(messageObject);
423      case ADD:
424        return new JSONAddForwardFailedAccessLogMessage(messageObject);
425      case BIND:
426        return new JSONBindForwardFailedAccessLogMessage(messageObject);
427      case COMPARE:
428        return new JSONCompareForwardFailedAccessLogMessage(messageObject);
429      case DELETE:
430        return new JSONDeleteForwardFailedAccessLogMessage(messageObject);
431      case EXTENDED:
432        return new JSONExtendedForwardFailedAccessLogMessage(messageObject);
433      case MODIFY:
434        return new JSONModifyForwardFailedAccessLogMessage(messageObject);
435      case MODDN:
436        return new JSONModifyDNForwardFailedAccessLogMessage(messageObject);
437      case SEARCH:
438        return new JSONSearchForwardFailedAccessLogMessage(messageObject);
439      case UNBIND:
440      default:
441        final String messageStr = messageObject.toSingleLineString();
442        throw new LogException(messageStr,
443             ERR_JSON_ACCESS_LOG_READER_UNSUPPORTED_FORWARD_FAILED_OP_TYPE.get(
444                  messageStr, operationType.getLogIdentifier()));
445    }
446  }
447
448
449
450  /**
451   * Creates a new assurance complete access log message from the provided JSON
452   * object.
453   *
454   * @param  messageObject  The JSON object containing an encoded representation
455   *                        of a assurance complete access log message.  It must
456   *                        not be {@code null}.
457   *
458   * @return  The assurance complete access log message that was created.
459   *
460   * @throws  LogException  If the provided JSON object cannot be decoded as a
461   *                        valid assurance complete access log message.
462   */
463  @NotNull()
464  private static JSONAccessLogMessage createAssuranceCompleteMessage(
465               @NotNull final JSONObject messageObject)
466          throws LogException
467  {
468    final AccessLogOperationType operationType =
469         getOperationType(messageObject);
470    switch (operationType)
471    {
472      case ADD:
473        return new JSONAddAssuranceCompletedAccessLogMessage(messageObject);
474      case DELETE:
475        return new JSONDeleteAssuranceCompletedAccessLogMessage(messageObject);
476      case MODIFY:
477        return new JSONModifyAssuranceCompletedAccessLogMessage(messageObject);
478      case MODDN:
479        return new JSONModifyDNAssuranceCompletedAccessLogMessage(
480             messageObject);
481      case ABANDON:
482      case BIND:
483      case COMPARE:
484      case EXTENDED:
485      case SEARCH:
486      case UNBIND:
487      default:
488        final String messageStr = messageObject.toSingleLineString();
489        throw new LogException(messageStr,
490             ERR_JSON_ACCESS_LOG_READER_UNSUPPORTED_ASSURANCE_COMPLETED_OP_TYPE.
491                  get(messageStr, operationType.getLogIdentifier()));
492    }
493  }
494
495
496
497  /**
498   * Determines the operation type for the JSON-formatted access log message
499   * encoded in the provided JSON object.
500   *
501   * @param  messageObject  The JSON object containing an encoded representation
502   *                        of an access log message.  It must not be
503   *                        {@code null}.
504   *
505   * @return  The operation type extracted from the provided JSON object.
506   *
507   * @throws  LogException  If it is not possible to extract a valid operation
508   *                        type from the provided JSON object.
509   */
510  @NotNull()
511  private static AccessLogOperationType getOperationType(
512               @NotNull final JSONObject messageObject)
513          throws LogException
514  {
515    final String opTypeStr = messageObject.getFieldAsString(
516         JSONFormattedAccessLogFields.OPERATION_TYPE.getFieldName());
517    if (opTypeStr == null)
518    {
519      final String messageStr = messageObject.toSingleLineString();
520      throw new LogException(messageStr,
521           ERR_JSON_ACCESS_LOG_READER_MISSING_OPERATION_TYPE.get(messageStr,
522                JSONFormattedAccessLogFields.OPERATION_TYPE.getFieldName()));
523    }
524
525    final AccessLogOperationType opType =
526         AccessLogOperationType.forName(opTypeStr);
527    if (opType == null)
528    {
529      final String messageStr = messageObject.toSingleLineString();
530      throw new LogException(messageStr,
531           ERR_JSON_ACCESS_LOG_READER_UNSUPPORTED_OPERATION_TYPE.get(messageStr,
532                opTypeStr));
533    }
534
535    return opType;
536  }
537
538
539
540  /**
541   * {@inheritDoc}
542   */
543  @Override()
544  public void close()
545         throws IOException
546  {
547    jsonObjectReader.close();
548  }
549}