001    /*
002     * Copyright 2016 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2016 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     */
021    package com.unboundid.ldap.sdk.experimental;
022    
023    
024    
025    import java.util.ArrayList;
026    import java.util.Collections;
027    import java.util.Date;
028    import java.util.List;
029    
030    import com.unboundid.asn1.ASN1Sequence;
031    import com.unboundid.ldap.sdk.Control;
032    import com.unboundid.ldap.sdk.Entry;
033    import com.unboundid.ldap.sdk.LDAPException;
034    import com.unboundid.ldap.sdk.LDAPResult;
035    import com.unboundid.ldap.sdk.OperationType;
036    import com.unboundid.ldap.sdk.ReadOnlyEntry;
037    import com.unboundid.ldap.sdk.ResultCode;
038    import com.unboundid.util.Debug;
039    import com.unboundid.util.NotExtensible;
040    import com.unboundid.util.StaticUtils;
041    import com.unboundid.util.ThreadSafety;
042    import com.unboundid.util.ThreadSafetyLevel;
043    
044    import 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)
056    public 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    }