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 }