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    }