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.ldap.sdk.experimental;
022
023
024
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.Date;
028import java.util.List;
029
030import com.unboundid.asn1.ASN1Sequence;
031import com.unboundid.ldap.sdk.Control;
032import com.unboundid.ldap.sdk.Entry;
033import com.unboundid.ldap.sdk.LDAPException;
034import com.unboundid.ldap.sdk.LDAPResult;
035import com.unboundid.ldap.sdk.OperationType;
036import com.unboundid.ldap.sdk.ReadOnlyEntry;
037import com.unboundid.ldap.sdk.ResultCode;
038import com.unboundid.util.Debug;
039import com.unboundid.util.NotExtensible;
040import com.unboundid.util.StaticUtils;
041import com.unboundid.util.ThreadSafety;
042import com.unboundid.util.ThreadSafetyLevel;
043
044import static com.unboundid.ldap.sdk.experimental.ExperimentalMessages.*;
045
046
047
048/**
049 * This class serves as the base class for entries that hold information about
050 * operations processed by an LDAP server, much like LDAP-accessible access log
051 * messages.  The format for the entries used in this implementation is
052 * described in draft-chu-ldap-logschema-00.
053 */
054@NotExtensible()
055@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
056public abstract class DraftChuLDAPLogSchema00Entry
057       extends ReadOnlyEntry
058{
059  /**
060   * The name of the attribute used to hold the DN of the authorization identity
061   * for the operation.
062   */
063  public static final String ATTR_AUTHORIZATION_IDENTITY_DN = "reqAuthzID";
064
065
066
067  /**
068   * The name of the attribute used to hold the diagnostic message the server
069   * included in the response to the client.
070   */
071  public static final String ATTR_DIAGNOSTIC_MESSAGE = "reqMessage";
072
073
074
075  /**
076   * The name of the attribute used to hold the type of operation that was
077   * processed.  For extended operation, the value will be
078   * "extended" followed by the OID of the extended request (e.g.,
079   * "extended1.3.6.1.4.1.1466.20037" to indicate the StartTLS extended
080   * request).  For all other operation types, this will be simply the name of
081   * the operation:  abandon, add, bind, compare, delete, modify, modrdn,
082   * search, or unbind.
083   */
084  public static final String ATTR_OPERATION_TYPE = "reqType";
085
086
087
088  /**
089   * The name of the attribute used to hold the time the server completed
090   * processing the operation.  Values will be in generalized time format, but
091   * may be of a very high precision to ensure that each log entry has a
092   * unique end time.
093   */
094  public static final String ATTR_PROCESSING_END_TIME = "reqEnd";
095
096
097
098  /**
099   * The name of the attribute used to hold the time the server started
100   * processing the operation.  Values will be in generalized time format, but
101   * may be of a very high precision to ensure that each log entry has a
102   * unique start time.
103   */
104  public static final String ATTR_PROCESSING_START_TIME = "reqStart";
105
106
107
108  /**
109   * The name of the attribute used to hold a referral URL the server included
110   * in the response to the client.
111   */
112  public static final String ATTR_REFERRAL_URL = "reqReferral";
113
114
115
116  /**
117   * The name of the attribute used to hold information about a request control
118   * included in the request received from the client.
119   */
120  public static final String ATTR_REQUEST_CONTROL = "reqControls";
121
122
123
124  /**
125   * The name of the attribute used to hold information about a response control
126   * included in the result returned to the client.
127   */
128  public static final String ATTR_RESPONSE_CONTROL = "reqRespControls";
129
130
131
132  /**
133   * The name of the attribute used to hold the integer value of the result code
134   * the server included in the response to the client.
135   */
136  public static final String ATTR_RESULT_CODE = "reqResult";
137
138
139
140  /**
141   * The name of the attribute used to hold a session identifier for a sequence
142   * of operations received on the same connection.
143   */
144  public static final String ATTR_SESSION_ID = "reqSession";
145
146
147
148  /**
149   * The name of the attribute used to hold the DN of the entry targeted by the
150   * operation.  For a search operation, this will be the search base DN.
151   */
152  public static final String ATTR_TARGET_ENTRY_DN = "reqDN";
153
154
155
156  /**
157   * The serial version UID for this serializable class.
158   */
159  private static final long serialVersionUID = -7279669732772403236L;
160
161
162
163  // The parsed processing end time for the operation.
164  private final Date processingEndTimeDate;
165
166  // The parsed processing start time for the operation.
167  private final Date processingStartTimeDate;
168
169  // A list of controls included in the request from the client.
170  private final List<Control> requestControls;
171
172  // A list of controls included in the request from the client.
173  private final List<Control> responseControls;
174
175  // A list of referral URLs returned to the client.
176  private final List<String> referralURLs;
177
178  // The operation type for the log entry.
179  private final OperationType operationType;
180
181  // The result code returned to the client.
182  private final ResultCode resultCode;
183
184  // The DN of the account used as the authorization identity for the operation.
185  private final String authorizationIdentityDN;
186
187  // The diagnostic message returned to the client.
188  private final String diagnosticMessage;
189
190  // The string representation of the processing end time for the operation.
191  private final String processingEndTimeString;
192
193  // The string representation of the processing start time for the operation.
194  private final String processingStartTimeString;
195
196  // The session ID for the sequence of operations received on the same
197  // connection.
198  private final String sessionID;
199
200  // The DN of the entry targeted by the client.
201  private final String targetEntryDN;
202
203
204
205  /**
206   * Creates a new instance of this access log entry from the provided entry.
207   *
208   * @param  entry          The entry used to create this access log entry.
209   * @param  operationType  The associated operation type.
210   *
211   * @throws  LDAPException  If the provided entry cannot be decoded as a valid
212   *                         access log entry as per the specification contained
213   *                         in draft-chu-ldap-logschema-00.
214   */
215  DraftChuLDAPLogSchema00Entry(final Entry entry,
216                               final OperationType operationType)
217       throws LDAPException
218  {
219    super(entry);
220
221    this.operationType = operationType;
222
223
224    // Get the processing start time.
225    processingStartTimeString =
226         entry.getAttributeValue(ATTR_PROCESSING_START_TIME);
227    if (processingStartTimeString == null)
228    {
229      throw new LDAPException(ResultCode.DECODING_ERROR,
230           ERR_LOGSCHEMA_DECODE_MISSING_REQUIRED_ATTR.get(entry.getDN(),
231                ATTR_PROCESSING_START_TIME));
232    }
233    else
234    {
235      try
236      {
237        processingStartTimeDate =
238             StaticUtils.decodeGeneralizedTime(processingStartTimeString);
239      }
240      catch (final Exception e)
241      {
242        Debug.debugException(e);
243        throw new LDAPException(ResultCode.DECODING_ERROR,
244             ERR_LOGSCHEMA_DECODE_CANNOT_DECODE_TIME.get(entry.getDN(),
245                  ATTR_PROCESSING_START_TIME, processingStartTimeString),
246             e);
247      }
248    }
249
250
251    // Get the processing end time.
252    processingEndTimeString =
253         entry.getAttributeValue(ATTR_PROCESSING_END_TIME);
254    if (processingEndTimeString == null)
255    {
256      processingEndTimeDate = null;
257    }
258    else
259    {
260      try
261      {
262        processingEndTimeDate =
263             StaticUtils.decodeGeneralizedTime(processingEndTimeString);
264      }
265      catch (final Exception e)
266      {
267        Debug.debugException(e);
268        throw new LDAPException(ResultCode.DECODING_ERROR,
269             ERR_LOGSCHEMA_DECODE_CANNOT_DECODE_TIME.get(entry.getDN(),
270                  ATTR_PROCESSING_END_TIME, processingEndTimeString),
271             e);
272      }
273    }
274
275
276    // Get the session ID.
277    sessionID = entry.getAttributeValue(ATTR_SESSION_ID);
278    if (sessionID == null)
279    {
280      throw new LDAPException(ResultCode.DECODING_ERROR,
281           ERR_LOGSCHEMA_DECODE_MISSING_REQUIRED_ATTR.get(entry.getDN(),
282                ATTR_SESSION_ID));
283    }
284
285
286    // Get the target DN.  It can only be null for abandon, extended, and unbind
287    // operation types.
288    targetEntryDN = entry.getAttributeValue(ATTR_TARGET_ENTRY_DN);
289    if (targetEntryDN == null)
290    {
291      if (! ((operationType == OperationType.ABANDON) ||
292             (operationType == OperationType.EXTENDED) ||
293             (operationType == OperationType.UNBIND)))
294      {
295        throw new LDAPException(ResultCode.DECODING_ERROR,
296             ERR_LOGSCHEMA_DECODE_MISSING_REQUIRED_ATTR.get(entry.getDN(),
297                  ATTR_TARGET_ENTRY_DN));
298      }
299    }
300
301
302    // Get the authorization identity.
303    authorizationIdentityDN =
304         entry.getAttributeValue(ATTR_AUTHORIZATION_IDENTITY_DN);
305
306
307    // Get the set of request controls, if any.
308    requestControls = decodeControls(entry, ATTR_REQUEST_CONTROL);
309
310
311    // Get the set of response controls, if any.
312    responseControls = decodeControls(entry, ATTR_RESPONSE_CONTROL);
313
314
315    // Get the result code, if any.
316    final String resultCodeString = entry.getAttributeValue(ATTR_RESULT_CODE);
317    if (resultCodeString == null)
318    {
319      resultCode = null;
320    }
321    else
322    {
323      try
324      {
325        resultCode = ResultCode.valueOf(Integer.parseInt(resultCodeString));
326      }
327      catch (final Exception e)
328      {
329        Debug.debugException(e);
330        throw new LDAPException(ResultCode.DECODING_ERROR,
331             ERR_LOGSCHEMA_DECODE_RESULT_CODE_ERROR.get(entry.getDN(),
332                  resultCodeString, ATTR_RESULT_CODE),
333             e);
334      }
335    }
336
337
338    // Get the diagnostic message, if any.
339    diagnosticMessage = entry.getAttributeValue(ATTR_DIAGNOSTIC_MESSAGE);
340
341
342    // Get the referral URLs, if any.
343    final String[] referralArray = entry.getAttributeValues(ATTR_REFERRAL_URL);
344    if (referralArray == null)
345    {
346      referralURLs = Collections.emptyList();
347    }
348    else
349    {
350      referralURLs =
351           Collections.unmodifiableList(StaticUtils.toList(referralArray));
352    }
353  }
354
355
356
357  /**
358   * Decodes a set of controls contained in the specified attribute of the
359   * provided entry.
360   *
361   * @param  entry          The entry containing the controls to decode.
362   * @param  attributeName  The name of the attribute expected to hold the set
363   *                        of controls to decode.
364   *
365   * @return  The decoded controls, or an empty list if the provided entry did
366   *          not include any controls in the specified attribute.
367   *
368   * @throws  LDAPException  If a problem is encountered while trying to decode
369   *                         the controls.
370   */
371  private static List<Control> decodeControls(final Entry entry,
372                                              final String attributeName)
373          throws LDAPException
374  {
375    final byte[][] values = entry.getAttributeValueByteArrays(attributeName);
376    if ((values == null) || (values.length == 0))
377    {
378      return Collections.emptyList();
379    }
380
381    final ArrayList<Control> controls = new ArrayList<Control>(values.length);
382    for (final byte[] controlBytes : values)
383    {
384      try
385      {
386        controls.add(Control.decode(ASN1Sequence.decodeAsSequence(
387             controlBytes)));
388      }
389      catch (final Exception e)
390      {
391        Debug.debugException(e);
392        throw new LDAPException(ResultCode.DECODING_ERROR,
393             ERR_LOGSCHEMA_DECODE_CONTROL_ERROR.get(entry.getDN(),
394                  attributeName, StaticUtils.getExceptionMessage(e)),
395             e);
396      }
397    }
398
399    return Collections.unmodifiableList(controls);
400  }
401
402
403
404  /**
405   * Retrieves the type of operation represented by this access log entry.
406   *
407   * @return  The type of operation represented by this access log entry.
408   */
409  public final OperationType getOperationType()
410  {
411    return operationType;
412  }
413
414
415
416  /**
417   * Retrieves the DN of the entry targeted by by the operation represented by
418   * this access log entry, if available.  Some types of operations, like
419   * abandon and extended operations, will not have a target entry DN.  For a
420   * search operation, this will be the base DN for the search request.  For a
421   * modify DN operation, this will be the DN of the entry before any processing
422   * was performed.
423   *
424   * @return  The DN of the entry targeted by the operation represented by this
425   *          access log entry, or {@code null} if no DN is available.
426   */
427  public final String getTargetEntryDN()
428  {
429    return targetEntryDN;
430  }
431
432
433
434  /**
435   * Retrieves the string representation of the time that the server started
436   * processing the operation represented by this access log entry.  Note that
437   * the string representation of this start time may have a different precision
438   * than the parsed start time returned by the
439   * {@link #getProcessingStartTimeDate()} method.
440   *
441   * @return  The string representation of the time that the server started
442   *          processing the operation represented by this access log entry.
443   */
444  public final String getProcessingStartTimeString()
445  {
446    return processingStartTimeString;
447  }
448
449
450
451  /**
452   * Retrieves a parsed representation of the time that the server started
453   * processing the operation represented by this access log entry.  Note that
454   * this parsed representation may have a different precision than the start
455   * time string returned by the {@link #getProcessingStartTimeString()} method.
456   *
457   * @return  A parsed representation of the time that the server started
458   *          processing the operation represented by this access log entry.
459   */
460  public final Date getProcessingStartTimeDate()
461  {
462    return processingStartTimeDate;
463  }
464
465
466
467  /**
468   * Retrieves the string representation of the time that the server completed
469   * processing the operation represented by this access log entry, if
470   * available.  Note that the string representation of this end time may have a
471   * different precision than the parsed end time returned by the
472   * {@link #getProcessingEndTimeDate()} method.
473   *
474   * @return  The string representation of the time that the server completed
475   *          processing the operation represented by this access log entry, or
476   *          {@code null} if no end time is available.
477   */
478  public final String getProcessingEndTimeString()
479  {
480    return processingEndTimeString;
481  }
482
483
484
485  /**
486   * Retrieves a parsed representation of the time that the server completed
487   * processing the operation represented by this access log entry, if
488   * available.  Note that this parsed representation may have a different
489   * precision than the end time string returned by the
490   * {@link #getProcessingEndTimeString()} method.
491   *
492   * @return  A parsed representation of the time that the server completed
493   *          processing the operation represented by this access log entry.
494   */
495  public final Date getProcessingEndTimeDate()
496  {
497    return processingEndTimeDate;
498  }
499
500
501
502  /**
503   * Retrieves the session identifier that the server assigned to the operation
504   * represented by this access log entry and can be used to correlate that
505   * operation with other operations requested on the same client connection.
506   * The server will assign a unique session identifier to each client
507   * connection, and all requests received on that connection will share the
508   * same session ID.
509   *
510   * @return  The session identifier that the server assigned to the operation
511   *          represented by this access log entry.
512   */
513  public final String getSessionID()
514  {
515    return sessionID;
516  }
517
518
519
520  /**
521   * Retrieves a list of the request controls for the operation represented by
522   * this access log entry, if any.
523   *
524   * @return  A list of the request controls for the operation represented by
525   *          this access log entry, or an empty list if there were no request
526   *          controls included in the access log entry.
527   */
528  public final List<Control> getRequestControls()
529  {
530    return requestControls;
531  }
532
533
534
535  /**
536   * Retrieves the set of request controls as an array rather than a list.  This
537   * is a convenience method for subclasses that need to create LDAP requests
538   * whose constructors need an array of controls rather than a list.
539   *
540   * @return  The set of request controls as an array rather than a list.
541   */
542  final Control[] getRequestControlArray()
543  {
544    return requestControls.toArray(StaticUtils.NO_CONTROLS);
545  }
546
547
548
549  /**
550   * Retrieves the result code for the operation represented by this access log
551   * entry, if any.
552   *
553   * @return  The result code for the operation represented by this access log
554   *          entry, or {@code null} if no result code was included in the
555   *          access log entry.
556   */
557  public final ResultCode getResultCode()
558  {
559    return resultCode;
560  }
561
562
563
564  /**
565   * Retrieves the diagnostic message for the operation represented by this
566   * access log entry, if any.
567   *
568   * @return  The diagnostic message for the operation represented by this
569   *          access log entry, or {@code null} if no result code was included
570   *          in the access log entry.
571   */
572  public final String getDiagnosticMessage()
573  {
574    return diagnosticMessage;
575  }
576
577
578
579  /**
580   * Retrieves the list of referral URLs for the operation represented by this
581   * access log entry, if any.
582   *
583   * @return  The list of referral URLs for the operation represented by this
584   *          access log entry, or an empty list if no referral URLs were
585   *          included in the access log entry.
586   */
587  public final List<String> getReferralURLs()
588  {
589    return referralURLs;
590  }
591
592
593
594  /**
595   * Retrieves a list of the response controls for the operation represented by
596   * this access log entry, if any.
597   *
598   * @return  A list of the response controls for the operation represented by
599   *          this access log entry, or an empty list if there were no response
600   *          controls included in the access log entry.
601   */
602  public final List<Control> getResponseControls()
603  {
604    return responseControls;
605  }
606
607
608
609  /**
610   * Retrieves the DN of the account that served as the authorization identity
611   * for the operation represented by this access log entry, if any.
612   *
613   * @return  The DN of the account that served as the authorization identity
614   *          for the operation represented by this access log entry, or
615   *          {@code null} if the authorization identity is not available.
616   */
617  public final String getAuthorizationIdentityDN()
618  {
619    return authorizationIdentityDN;
620  }
621
622
623
624  /**
625   * Retrieves an {@code LDAPResult} object that represents the server response
626   * described by this access log entry, if any.  Note that for some types of
627   * operations, like abandon and unbind operations, the server will not return
628   * a result to the client.
629   *
630   * @return  An {@code LDAPResult} object that represents the server response
631   *          described by this access log entry, or {@code null} if no response
632   *          information is available.
633   */
634  public final LDAPResult toLDAPResult()
635  {
636    if (resultCode == null)
637    {
638      return null;
639    }
640
641    return new LDAPResult(-1, resultCode, diagnosticMessage, null, referralURLs,
642         responseControls);
643  }
644
645
646
647  /**
648   * Decodes the provided entry as an access log entry of the appropriate type.
649   *
650   * @param  entry  The entry to decode as an access log entry.  It must not be
651   *                {@code null}.
652   *
653   * @return  The decoded access log entry.
654   *
655   * @throws  LDAPException  If the provided entry cannot be decoded as a valid
656   *                         access log entry as per the specification contained
657   *                         in draft-chu-ldap-logschema-00.
658   */
659  public static DraftChuLDAPLogSchema00Entry decode(final Entry entry)
660         throws LDAPException
661  {
662    final String opType = entry.getAttributeValue(ATTR_OPERATION_TYPE);
663    if (opType == null)
664    {
665      throw new LDAPException(ResultCode.DECODING_ERROR,
666           ERR_LOGSCHEMA_DECODE_NO_OP_TYPE.get(entry.getDN(),
667                ATTR_OPERATION_TYPE));
668    }
669
670    final String lowerOpType = StaticUtils.toLowerCase(opType);
671    if (lowerOpType.equals("abandon"))
672    {
673      return new DraftChuLDAPLogSchema00AbandonEntry(entry);
674    }
675    else if (lowerOpType.equals("add"))
676    {
677      return new DraftChuLDAPLogSchema00AddEntry(entry);
678    }
679    else if (lowerOpType.equals("bind"))
680    {
681      return new DraftChuLDAPLogSchema00BindEntry(entry);
682    }
683    else if (lowerOpType.equals("compare"))
684    {
685      return new DraftChuLDAPLogSchema00CompareEntry(entry);
686    }
687    else if (lowerOpType.equals("delete"))
688    {
689      return new DraftChuLDAPLogSchema00DeleteEntry(entry);
690    }
691    else if (lowerOpType.startsWith("extended"))
692    {
693      return new DraftChuLDAPLogSchema00ExtendedEntry(entry);
694    }
695    else if (lowerOpType.equals("modify"))
696    {
697      return new DraftChuLDAPLogSchema00ModifyEntry(entry);
698    }
699    else if (lowerOpType.equals("modrdn"))
700    {
701      return new DraftChuLDAPLogSchema00ModifyDNEntry(entry);
702    }
703    else if (lowerOpType.equals("search"))
704    {
705      return new DraftChuLDAPLogSchema00SearchEntry(entry);
706    }
707    else if (lowerOpType.equals("unbind"))
708    {
709      return new DraftChuLDAPLogSchema00UnbindEntry(entry);
710    }
711    else
712    {
713      throw new LDAPException(ResultCode.DECODING_ERROR,
714           ERR_LOGSCHEMA_DECODE_UNRECOGNIZED_OP_TYPE.get(
715                entry.getDN(), ATTR_OPERATION_TYPE, opType));
716    }
717  }
718}