001 /*
002 * Copyright 2011-2015 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2015 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.unboundidds.controls;
022
023
024
025 import java.util.ArrayList;
026
027 import com.unboundid.asn1.ASN1Element;
028 import com.unboundid.asn1.ASN1OctetString;
029 import com.unboundid.asn1.ASN1Sequence;
030 import com.unboundid.ldap.sdk.Control;
031 import com.unboundid.ldap.sdk.LDAPException;
032 import com.unboundid.ldap.sdk.ResultCode;
033 import com.unboundid.util.Debug;
034 import com.unboundid.util.NotMutable;
035 import com.unboundid.util.StaticUtils;
036 import com.unboundid.util.ThreadSafety;
037 import com.unboundid.util.ThreadSafetyLevel;
038 import com.unboundid.util.Validator;
039
040 import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
041
042
043
044 /**
045 * <BLOCKQUOTE>
046 * <B>NOTE:</B> This class is part of the Commercial Edition of the UnboundID
047 * LDAP SDK for Java. It is not available for use in applications that
048 * include only the Standard Edition of the LDAP SDK, and is not supported for
049 * use in conjunction with non-UnboundID products.
050 * </BLOCKQUOTE>
051 * This class provides a request control that can be used by the client to
052 * identify the purpose of the associated operation. It can be used in
053 * conjunction with any kind of operation, and may be used to provide
054 * information about the reason for that operation, as well as about the client
055 * application used to generate the request. This may be very useful for
056 * debugging and auditing purposes.
057 * <BR><BR>
058 * The criticality for this control may be either {@code true} or {@code false}.
059 * It must have a value with the following encoding:
060 * <PRE>
061 * OperationPurposeRequest ::= SEQUENCE {
062 * applicationName [0] OCTET STRING OPTIONAL,
063 * applicationVersion [1] OCTET STRING OPTIONAL,
064 * codeLocation [2] OCTET STRING OPTIONAL,
065 * requestPurpose [3] OCTET STRING OPTIONAL
066 * ... }
067 * </PRE>
068 * At least one of the elements in the value sequence must be present.
069 * <BR><BR>
070 * <H2>Example</H2>
071 * The following example demonstrates a sample authentication consisting of a
072 * search to find a user followed by a bind to verify that user's password.
073 * Both the search and bind requests will include operation purpose controls
074 * with information about the reason for the request. Note that for the sake
075 * of brevity and clarity, error handling has been omitted from this example.
076 * <PRE>
077 * SearchRequest searchRequest = new SearchRequest("dc=example,dc=com",
078 * SearchScope.SUB, Filter.createEqualityFilter("uid", uidValue),
079 * "1.1");
080 * searchRequest.addControl(new OperationPurposeRequestControl(appName,
081 * appVersion, 0, "Retrieve the entry for a user with a given uid"));
082 * Entry userEntry = connection.searchForEntry(searchRequest);
083 *
084 * SimpleBindRequest bindRequest = new SimpleBindRequest(userEntry.getDN(),
085 * password, new OperationPurposeRequestControl(appName, appVersion, 0,
086 * "Bind as a user to verify the provided credentials."));
087 * BindResult bindResult = connection.bind(bindRequest);
088 * </PRE>
089 */
090 @NotMutable()
091 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
092 public final class OperationPurposeRequestControl
093 extends Control
094 {
095 /**
096 * The OID (1.3.6.1.4.1.30221.2.5.19) for the operation purpose request
097 * control.
098 */
099 public static final String OPERATION_PURPOSE_REQUEST_OID =
100 "1.3.6.1.4.1.30221.2.5.19";
101
102
103
104 /**
105 * The BER type for the element that specifies the application name.
106 */
107 private static final byte TYPE_APP_NAME = (byte) 0x80;
108
109
110
111 /**
112 * The BER type for the element that specifies the application version.
113 */
114 private static final byte TYPE_APP_VERSION = (byte) 0x81;
115
116
117
118 /**
119 * The BER type for the element that specifies the code location.
120 */
121 private static final byte TYPE_CODE_LOCATION = (byte) 0x82;
122
123
124
125 /**
126 * The BER type for the element that specifies the request purpose.
127 */
128 private static final byte TYPE_REQUEST_PURPOSE = (byte) 0x83;
129
130
131
132 /**
133 * The serial version UID for this serializable class.
134 */
135 private static final long serialVersionUID = -5552051862785419833L;
136
137
138
139 // The application name for this control, if any.
140 private final String applicationName;
141
142 // The application version for this control, if any.
143 private final String applicationVersion;
144
145 // The code location for this control, if any.
146 private final String codeLocation;
147
148 // The request purpose for this control, if any.
149 private final String requestPurpose;
150
151
152
153 /**
154 * Creates a new operation purpose request control with the provided
155 * information. It will not be critical. If the generateCodeLocation
156 * argument has a value of {@code false}, then at least one of the
157 * applicationName, applicationVersion, and requestPurpose arguments must
158 * be non-{@code null}.
159 *
160 * @param applicationName The name of the application generating the
161 * associated request. It may be {@code null} if
162 * this should not be included in the control.
163 * @param applicationVersion Information about the version of the
164 * application generating the associated request.
165 * It may be {@code null} if this should not be
166 * included in the control.
167 * @param codeLocationFrames Indicates that the code location should be
168 * automatically generated with a condensed stack
169 * trace for the current thread, using the
170 * specified number of stack frames. A value that
171 * is less than or equal to zero indicates an
172 * unlimited number of stack frames should be
173 * included.
174 * @param requestPurpose A string identifying the purpose of the
175 * associated request. It may be {@code null} if
176 * this should not be included in the control.
177 */
178 public OperationPurposeRequestControl(final String applicationName,
179 final String applicationVersion,
180 final int codeLocationFrames,
181 final String requestPurpose)
182 {
183 this(false, applicationName, applicationVersion,
184 generateStackTrace(codeLocationFrames), requestPurpose);
185 }
186
187
188
189 /**
190 * Creates a new operation purpose request control with the provided
191 * information. At least one of the applicationName, applicationVersion,
192 * codeLocation, and requestPurpose arguments must be non-{@code null}.
193 *
194 * @param isCritical Indicates whether the control should be
195 * considered critical.
196 * @param applicationName The name of the application generating the
197 * associated request. It may be {@code null} if
198 * this should not be included in the control.
199 * @param applicationVersion Information about the version of the
200 * application generating the associated request.
201 * It may be {@code null} if this should not be
202 * included in the control.
203 * @param codeLocation Information about the location in the
204 * application code in which the associated
205 * request is generated (e.g., the class and/or
206 * method name, or any other useful identifier).
207 * It may be {@code null} if this should not be
208 * included in the control.
209 * @param requestPurpose A string identifying the purpose of the
210 * associated request. It may be {@code null} if
211 * this should not be included in the control.
212 */
213 public OperationPurposeRequestControl(final boolean isCritical,
214 final String applicationName,
215 final String applicationVersion,
216 final String codeLocation,
217 final String requestPurpose)
218 {
219 super(OPERATION_PURPOSE_REQUEST_OID, isCritical,
220 encodeValue(applicationName, applicationVersion, codeLocation,
221 requestPurpose));
222
223 this.applicationName = applicationName;
224 this.applicationVersion = applicationVersion;
225 this.codeLocation = codeLocation;
226 this.requestPurpose = requestPurpose;
227 }
228
229
230
231 /**
232 * Creates a new operation purpose request control which is decoded from the
233 * provided generic control.
234 *
235 * @param control The generic control to be decoded as an operation purpose
236 * request control.
237 *
238 * @throws LDAPException If the provided control cannot be decoded as an
239 * operation purpose request control.
240 */
241 public OperationPurposeRequestControl(final Control control)
242 throws LDAPException
243 {
244 super(control);
245
246 final ASN1OctetString value = control.getValue();
247 if (value == null)
248 {
249 throw new LDAPException(ResultCode.DECODING_ERROR,
250 ERR_OP_PURPOSE_NO_VALUE.get());
251 }
252
253 final ASN1Element[] valueElements;
254 try
255 {
256 valueElements =
257 ASN1Sequence.decodeAsSequence(value.getValue()).elements();
258 }
259 catch (final Exception e)
260 {
261 Debug.debugException(e);
262 throw new LDAPException(ResultCode.DECODING_ERROR,
263 ERR_OP_PURPOSE_VALUE_NOT_SEQUENCE.get(
264 StaticUtils.getExceptionMessage(e)),
265 e);
266 }
267
268 if (valueElements.length == 0)
269 {
270 throw new LDAPException(ResultCode.DECODING_ERROR,
271 ERR_OP_PURPOSE_VALUE_SEQUENCE_EMPTY.get());
272 }
273
274
275 String appName = null;
276 String appVersion = null;
277 String codeLoc = null;
278 String reqPurpose = null;
279 for (final ASN1Element e : valueElements)
280 {
281 switch (e.getType())
282 {
283 case TYPE_APP_NAME:
284 appName = ASN1OctetString.decodeAsOctetString(e).stringValue();
285 break;
286
287 case TYPE_APP_VERSION:
288 appVersion = ASN1OctetString.decodeAsOctetString(e).stringValue();
289 break;
290
291 case TYPE_CODE_LOCATION:
292 codeLoc = ASN1OctetString.decodeAsOctetString(e).stringValue();
293 break;
294
295 case TYPE_REQUEST_PURPOSE:
296 reqPurpose = ASN1OctetString.decodeAsOctetString(e).stringValue();
297 break;
298
299 default:
300 throw new LDAPException(ResultCode.DECODING_ERROR,
301 ERR_OP_PURPOSE_VALUE_UNSUPPORTED_ELEMENT.get(
302 StaticUtils.toHex(e.getType())));
303 }
304 }
305
306 applicationName = appName;
307 applicationVersion = appVersion;
308 codeLocation = codeLoc;
309 requestPurpose = reqPurpose;
310 }
311
312
313
314 /**
315 * Generates a compact stack trace for the current thread, The stack trace
316 * elements will start with the last frame to call into this class (so that
317 * frames referencing this class, and anything called by this class in the
318 * process of getting the stack trace will be omitted). Elements will be
319 * space-delimited and will contain the unqualified class name, a period,
320 * the method name, a colon, and the source line number.
321 *
322 * @param numFrames The maximum number of frames to capture in the stack
323 * trace.
324 *
325 * @return The generated stack trace for the current thread.
326 */
327 private static String generateStackTrace(final int numFrames)
328 {
329 final StringBuilder buffer = new StringBuilder();
330 final int n = (numFrames > 0) ? numFrames : Integer.MAX_VALUE;
331
332 int c = 0;
333 boolean skip = true;
334 for (final StackTraceElement e : Thread.currentThread().getStackTrace())
335 {
336 final String className = e.getClassName();
337 if (className.equals(OperationPurposeRequestControl.class.getName()))
338 {
339 skip = false;
340 continue;
341 }
342 else if (skip)
343 {
344 continue;
345 }
346
347 if (buffer.length() > 0)
348 {
349 buffer.append(' ');
350 }
351
352 final int lastPeriodPos = className.lastIndexOf('.');
353 if (lastPeriodPos > 0)
354 {
355 buffer.append(className.substring(lastPeriodPos+1));
356 }
357 else
358 {
359 buffer.append(className);
360 }
361
362 buffer.append('.');
363 buffer.append(e.getMethodName());
364 buffer.append(':');
365 buffer.append(e.getLineNumber());
366
367 c++;
368 if (c >= n)
369 {
370 break;
371 }
372 }
373
374 return buffer.toString();
375 }
376
377
378
379 /**
380 * Encodes the provided information into a form suitable for use as the value
381 * of this control.
382 *
383 * @param applicationName The name of the application generating the
384 * associated request. It may be {@code null} if
385 * this should not be included in the control.
386 * @param applicationVersion Information about the version of the
387 * application generating the associated request.
388 * It may be {@code null} if this should not be
389 * included in the control.
390 * @param codeLocation Information about the location in the
391 * application code in which the associated
392 * request is generated (e.g., the class and/or
393 * method name, or any other useful identifier).
394 * It may be {@code null} if this should not be
395 * included in the control.
396 * @param requestPurpose A string identifying the purpose of the
397 * associated request. It may be {@code null} if
398 * this should not be included in the control.
399 *
400 * @return The encoded value for this control.
401 */
402 private static ASN1OctetString encodeValue(final String applicationName,
403 final String applicationVersion,
404 final String codeLocation,
405 final String requestPurpose)
406 {
407 Validator.ensureFalse((applicationName == null) &&
408 (applicationVersion == null) && (codeLocation == null) &&
409 (requestPurpose == null));
410
411 final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(4);
412
413 if (applicationName != null)
414 {
415 elements.add(new ASN1OctetString(TYPE_APP_NAME, applicationName));
416 }
417
418 if (applicationVersion != null)
419 {
420 elements.add(new ASN1OctetString(TYPE_APP_VERSION, applicationVersion));
421 }
422
423 if (codeLocation != null)
424 {
425 elements.add(new ASN1OctetString(TYPE_CODE_LOCATION, codeLocation));
426 }
427
428 if (requestPurpose != null)
429 {
430 elements.add(new ASN1OctetString(TYPE_REQUEST_PURPOSE, requestPurpose));
431 }
432
433 return new ASN1OctetString(new ASN1Sequence(elements).encode());
434 }
435
436
437
438 /**
439 * Retrieves the name of the application that generated the associated
440 * request, if available.
441 *
442 * @return The name of the application that generated the associated request,
443 * or {@code null} if that is not available.
444 */
445 public String getApplicationName()
446 {
447 return applicationName;
448 }
449
450
451
452 /**
453 * Retrieves information about the version of the application that generated
454 * the associated request, if available.
455 *
456 * @return Information about the version of the application that generated
457 * the associated request, or {@code null} if that is not available.
458 */
459 public String getApplicationVersion()
460 {
461 return applicationVersion;
462 }
463
464
465
466 /**
467 * Retrieves information about the location in the application code in which
468 * the associated request was created, if available.
469 *
470 * @return Information about the location in the application code in which
471 * the associated request was created, or {@code null} if that is not
472 * available.
473 */
474 public String getCodeLocation()
475 {
476 return codeLocation;
477 }
478
479
480
481 /**
482 * Retrieves a message with information about the purpose of the associated
483 * request, if available.
484 *
485 * @return A message with information about the purpose of the associated
486 * request, or {@code null} if that is not available.
487 */
488 public String getRequestPurpose()
489 {
490 return requestPurpose;
491 }
492
493
494
495 /**
496 * {@inheritDoc}
497 */
498 @Override()
499 public String getControlName()
500 {
501 return INFO_CONTROL_NAME_OP_PURPOSE.get();
502 }
503
504
505
506 /**
507 * {@inheritDoc}
508 */
509 @Override()
510 public void toString(final StringBuilder buffer)
511 {
512 buffer.append("OperationPurposeRequestControl(isCritical=");
513 buffer.append(isCritical());
514
515 if (applicationName != null)
516 {
517 buffer.append(", appName='");
518 buffer.append(applicationName);
519 buffer.append('\'');
520 }
521
522
523 if (applicationVersion != null)
524 {
525 buffer.append(", appVersion='");
526 buffer.append(applicationVersion);
527 buffer.append('\'');
528 }
529
530
531 if (codeLocation != null)
532 {
533 buffer.append(", codeLocation='");
534 buffer.append(codeLocation);
535 buffer.append('\'');
536 }
537
538
539 if (requestPurpose != null)
540 {
541 buffer.append(", purpose='");
542 buffer.append(requestPurpose);
543 buffer.append('\'');
544 }
545
546 buffer.append(')');
547 }
548 }