001/*
002 * Copyright 2011-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2011-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2011-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk.unboundidds.controls;
037
038
039
040import java.util.ArrayList;
041import java.util.LinkedHashMap;
042import java.util.List;
043import java.util.Map;
044
045import com.unboundid.asn1.ASN1Element;
046import com.unboundid.asn1.ASN1OctetString;
047import com.unboundid.asn1.ASN1Sequence;
048import com.unboundid.ldap.sdk.Control;
049import com.unboundid.ldap.sdk.JSONControlDecodeHelper;
050import com.unboundid.ldap.sdk.LDAPException;
051import com.unboundid.ldap.sdk.ResultCode;
052import com.unboundid.util.Debug;
053import com.unboundid.util.NotMutable;
054import com.unboundid.util.NotNull;
055import com.unboundid.util.Nullable;
056import com.unboundid.util.StaticUtils;
057import com.unboundid.util.ThreadSafety;
058import com.unboundid.util.ThreadSafetyLevel;
059import com.unboundid.util.Validator;
060import com.unboundid.util.json.JSONField;
061import com.unboundid.util.json.JSONObject;
062import com.unboundid.util.json.JSONString;
063import com.unboundid.util.json.JSONValue;
064
065import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
066
067
068
069/**
070 * This class provides a request control that can be used by the client to
071 * identify the purpose of the associated operation.  It can be used in
072 * conjunction with any kind of operation, and may be used to provide
073 * information about the reason for that operation, as well as about the client
074 * application used to generate the request.  This may be very useful for
075 * debugging and auditing purposes.
076 * <BR>
077 * <BLOCKQUOTE>
078 *   <B>NOTE:</B>  This class, and other classes within the
079 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
080 *   supported for use against Ping Identity, UnboundID, and
081 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
082 *   for proprietary functionality or for external specifications that are not
083 *   considered stable or mature enough to be guaranteed to work in an
084 *   interoperable way with other types of LDAP servers.
085 * </BLOCKQUOTE>
086 * <BR>
087 * The criticality for this control may be either {@code true} or {@code false}.
088 * It must have a value with the following encoding:
089 * <PRE>
090 *   OperationPurposeRequest ::= SEQUENCE {
091 *        applicationName     [0] OCTET STRING OPTIONAL,
092 *        applicationVersion  [1] OCTET STRING OPTIONAL,
093 *        codeLocation        [2] OCTET STRING OPTIONAL,
094 *        requestPurpose      [3] OCTET STRING OPTIONAL
095 *        ... }
096 * </PRE>
097 * At least one of the elements in the value sequence must be present.
098 * <BR><BR>
099 * <H2>Example</H2>
100 * The following example demonstrates a sample authentication consisting of a
101 * search to find a user followed by a bind to verify that user's password.
102 * Both the search and bind requests will include operation purpose controls
103 * with information about the reason for the request.  Note that for the sake
104 * of brevity and clarity, error handling has been omitted from this example.
105 * <PRE>
106 * SearchRequest searchRequest = new SearchRequest("dc=example,dc=com",
107 *      SearchScope.SUB, Filter.createEqualityFilter("uid", uidValue),
108 *      "1.1");
109 * searchRequest.addControl(new OperationPurposeRequestControl(appName,
110 *      appVersion, 0,  "Retrieve the entry for a user with a given uid"));
111 * Entry userEntry = connection.searchForEntry(searchRequest);
112 *
113 * SimpleBindRequest bindRequest = new SimpleBindRequest(userEntry.getDN(),
114 *      password, new OperationPurposeRequestControl(appName, appVersion, 0,
115 *      "Bind as a user to verify the provided credentials."));
116 * BindResult bindResult = connection.bind(bindRequest);
117 * </PRE>
118 */
119@NotMutable()
120@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
121public final class OperationPurposeRequestControl
122       extends Control
123{
124  /**
125   * The OID (1.3.6.1.4.1.30221.2.5.19) for the operation purpose request
126   * control.
127   */
128  @NotNull public static final String OPERATION_PURPOSE_REQUEST_OID =
129       "1.3.6.1.4.1.30221.2.5.19";
130
131
132
133  /**
134   * The BER type for the element that specifies the application name.
135   */
136  private static final byte TYPE_APP_NAME = (byte) 0x80;
137
138
139
140  /**
141   * The BER type for the element that specifies the application version.
142   */
143  private static final byte TYPE_APP_VERSION = (byte) 0x81;
144
145
146
147  /**
148   * The BER type for the element that specifies the code location.
149   */
150  private static final byte TYPE_CODE_LOCATION = (byte) 0x82;
151
152
153
154  /**
155   * The BER type for the element that specifies the request purpose.
156   */
157  private static final byte TYPE_REQUEST_PURPOSE = (byte) 0x83;
158
159
160
161  /**
162   * The name of the field used to hold the application name in the JSON
163   * representation of this control.
164   */
165  @NotNull private static final String JSON_FIELD_APPLICATION_NAME =
166       "application-name";
167
168
169
170  /**
171   * The name of the field used to hold the application version in the JSON
172   * representation of this control.
173   */
174  @NotNull private static final String JSON_FIELD_APPLICATION_VERSION =
175       "application-version";
176
177
178
179  /**
180   * The name of the field used to hold the code location in the JSON
181   * representation of this control.
182   */
183  @NotNull private static final String JSON_FIELD_CODE_LOCATION =
184       "code-location";
185
186
187
188  /**
189   * The name of the field used to hold the request purpose in the JSON
190   * representation of this control.
191   */
192  @NotNull private static final String JSON_FIELD_REQUEST_PURPOSE =
193       "request-purpose";
194
195
196
197  /**
198   * The serial version UID for this serializable class.
199   */
200  private static final long serialVersionUID = -5552051862785419833L;
201
202
203
204  // The application name for this control, if any.
205  @Nullable private final String applicationName;
206
207  // The application version for this control, if any.
208  @Nullable private final String applicationVersion;
209
210  // The code location for this control, if any.
211  @Nullable private final String codeLocation;
212
213  // The request purpose for this control, if any.
214  @Nullable private final String requestPurpose;
215
216
217
218  /**
219   * Creates a new operation purpose request control with the provided
220   * information.  It will not be critical.  If the generateCodeLocation
221   * argument has a value of {@code false}, then at least one of the
222   * applicationName, applicationVersion, and requestPurpose arguments must
223   * be non-{@code null}.
224   *
225   * @param  applicationName     The name of the application generating the
226   *                             associated request.  It may be {@code null} if
227   *                             this should not be included in the control.
228   * @param  applicationVersion  Information about the version of the
229   *                             application generating the associated request.
230   *                             It may be {@code null} if this should not be
231   *                             included in the control.
232   * @param  codeLocationFrames  Indicates that the code location should be
233   *                             automatically generated with a condensed stack
234   *                             trace for the current thread, using the
235   *                             specified number of stack frames.  A value that
236   *                             is less than or equal to zero indicates an
237   *                             unlimited number of stack frames should be
238   *                             included.
239   * @param  requestPurpose      A string identifying the purpose of the
240   *                             associated request.  It may be {@code null} if
241   *                             this should not be included in the control.
242   */
243  public OperationPurposeRequestControl(@Nullable final String applicationName,
244              @Nullable final String applicationVersion,
245              final int codeLocationFrames,
246              @Nullable final String requestPurpose)
247  {
248    this(false, applicationName, applicationVersion,
249         generateStackTrace(codeLocationFrames), requestPurpose);
250  }
251
252
253
254  /**
255   * Creates a new operation purpose request control with the provided
256   * information.  At least one of the applicationName, applicationVersion,
257   * codeLocation, and requestPurpose arguments must be non-{@code null}.
258   *
259   * @param  isCritical          Indicates whether the control should be
260   *                             considered critical.
261   * @param  applicationName     The name of the application generating the
262   *                             associated request.  It may be {@code null} if
263   *                             this should not be included in the control.
264   * @param  applicationVersion  Information about the version of the
265   *                             application generating the associated request.
266   *                             It may be {@code null} if this should not be
267   *                             included in the control.
268   * @param  codeLocation        Information about the location in the
269   *                             application code in which the associated
270   *                             request is generated (e.g., the class and/or
271   *                             method name, or any other useful identifier).
272   *                             It may be {@code null} if this should not be
273   *                             included in the control.
274   * @param  requestPurpose      A string identifying the purpose of the
275   *                             associated request.  It may be {@code null} if
276   *                             this should not be included in the control.
277   */
278  public OperationPurposeRequestControl(final boolean isCritical,
279              @Nullable final String applicationName,
280              @Nullable final String applicationVersion,
281              @Nullable final String codeLocation,
282              @Nullable final String requestPurpose)
283  {
284    super(OPERATION_PURPOSE_REQUEST_OID, isCritical,
285         encodeValue(applicationName, applicationVersion, codeLocation,
286              requestPurpose));
287
288    this.applicationName    = applicationName;
289    this.applicationVersion = applicationVersion;
290    this.codeLocation       = codeLocation;
291    this.requestPurpose     = requestPurpose;
292  }
293
294
295
296  /**
297   * Creates a new operation purpose request control which is decoded from the
298   * provided generic control.
299   *
300   * @param  control  The generic control to be decoded as an operation purpose
301   *                  request control.
302   *
303   * @throws  LDAPException  If the provided control cannot be decoded as an
304   *                         operation purpose request control.
305   */
306  public OperationPurposeRequestControl(@NotNull final Control control)
307         throws LDAPException
308  {
309    super(control);
310
311    final ASN1OctetString value = control.getValue();
312    if (value == null)
313    {
314      throw new LDAPException(ResultCode.DECODING_ERROR,
315           ERR_OP_PURPOSE_NO_VALUE.get());
316    }
317
318    final ASN1Element[] valueElements;
319    try
320    {
321      valueElements =
322           ASN1Sequence.decodeAsSequence(value.getValue()).elements();
323    }
324    catch (final Exception e)
325    {
326      Debug.debugException(e);
327      throw new LDAPException(ResultCode.DECODING_ERROR,
328           ERR_OP_PURPOSE_VALUE_NOT_SEQUENCE.get(
329                StaticUtils.getExceptionMessage(e)),
330           e);
331    }
332
333    if (valueElements.length == 0)
334    {
335      throw new LDAPException(ResultCode.DECODING_ERROR,
336           ERR_OP_PURPOSE_VALUE_SEQUENCE_EMPTY.get());
337    }
338
339
340    String appName    = null;
341    String appVersion = null;
342    String codeLoc    = null;
343    String reqPurpose = null;
344    for (final ASN1Element e : valueElements)
345    {
346      switch (e.getType())
347      {
348        case TYPE_APP_NAME:
349          appName = ASN1OctetString.decodeAsOctetString(e).stringValue();
350          break;
351
352        case TYPE_APP_VERSION:
353          appVersion = ASN1OctetString.decodeAsOctetString(e).stringValue();
354          break;
355
356        case TYPE_CODE_LOCATION:
357          codeLoc = ASN1OctetString.decodeAsOctetString(e).stringValue();
358          break;
359
360        case TYPE_REQUEST_PURPOSE:
361          reqPurpose = ASN1OctetString.decodeAsOctetString(e).stringValue();
362          break;
363
364        default:
365          throw new LDAPException(ResultCode.DECODING_ERROR,
366               ERR_OP_PURPOSE_VALUE_UNSUPPORTED_ELEMENT.get(
367                    StaticUtils.toHex(e.getType())));
368      }
369    }
370
371    applicationName    = appName;
372    applicationVersion = appVersion;
373    codeLocation       = codeLoc;
374    requestPurpose     = reqPurpose;
375  }
376
377
378
379  /**
380   * Generates a compact stack trace for the current thread,  The stack trace
381   * elements will start with the last frame to call into this class (so that
382   * frames referencing this class, and anything called by this class in the
383   * process of getting the stack trace will be omitted).  Elements will be
384   * space-delimited and will contain the unqualified class name, a period,
385   * the method name, a colon, and the source line number.
386   *
387   * @param  numFrames  The maximum number of frames to capture in the stack
388   *                    trace.
389   *
390   * @return  The generated stack trace for the current thread.
391   */
392  @NotNull()
393  private static String generateStackTrace(final int numFrames)
394  {
395    final StringBuilder buffer = new StringBuilder();
396    final int n = (numFrames > 0) ? numFrames : Integer.MAX_VALUE;
397
398    int c = 0;
399    boolean skip = true;
400    for (final StackTraceElement e : Thread.currentThread().getStackTrace())
401    {
402      final String className = e.getClassName();
403      if (className.equals(OperationPurposeRequestControl.class.getName()))
404      {
405        skip = false;
406        continue;
407      }
408      else if (skip)
409      {
410        continue;
411      }
412
413      if (buffer.length() > 0)
414      {
415        buffer.append(' ');
416      }
417
418      final int lastPeriodPos = className.lastIndexOf('.');
419      if (lastPeriodPos > 0)
420      {
421        buffer.append(className.substring(lastPeriodPos+1));
422      }
423      else
424      {
425        buffer.append(className);
426      }
427
428      buffer.append('.');
429      buffer.append(e.getMethodName());
430      buffer.append(':');
431      buffer.append(e.getLineNumber());
432
433      c++;
434      if (c >= n)
435      {
436        break;
437      }
438    }
439
440    return buffer.toString();
441  }
442
443
444
445  /**
446   * Encodes the provided information into a form suitable for use as the value
447   * of this control.
448   *
449   * @param  applicationName     The name of the application generating the
450   *                             associated request.  It may be {@code null} if
451   *                             this should not be included in the control.
452   * @param  applicationVersion  Information about the version of the
453   *                             application generating the associated request.
454   *                             It may be {@code null} if this should not be
455   *                             included in the control.
456   * @param  codeLocation        Information about the location in the
457   *                             application code in which the associated
458   *                             request is generated (e.g., the class and/or
459   *                             method name, or any other useful identifier).
460   *                             It may be {@code null} if this should not be
461   *                             included in the control.
462   * @param  requestPurpose      A string identifying the purpose of the
463   *                             associated request.  It may be {@code null} if
464   *                             this should not be included in the control.
465   *
466   * @return  The encoded value for this control.
467   */
468  @NotNull()
469  private static ASN1OctetString encodeValue(
470               @Nullable final String applicationName,
471               @Nullable final String applicationVersion,
472               @Nullable final String codeLocation,
473               @Nullable final String requestPurpose)
474  {
475    Validator.ensureFalse((applicationName == null) &&
476         (applicationVersion == null) && (codeLocation == null) &&
477         (requestPurpose == null));
478
479    final ArrayList<ASN1Element> elements = new ArrayList<>(4);
480
481    if (applicationName != null)
482    {
483      elements.add(new ASN1OctetString(TYPE_APP_NAME, applicationName));
484    }
485
486    if (applicationVersion != null)
487    {
488      elements.add(new ASN1OctetString(TYPE_APP_VERSION, applicationVersion));
489    }
490
491    if (codeLocation != null)
492    {
493      elements.add(new ASN1OctetString(TYPE_CODE_LOCATION, codeLocation));
494    }
495
496    if (requestPurpose != null)
497    {
498      elements.add(new ASN1OctetString(TYPE_REQUEST_PURPOSE, requestPurpose));
499    }
500
501    return new ASN1OctetString(new ASN1Sequence(elements).encode());
502  }
503
504
505
506  /**
507   * Retrieves the name of the application that generated the associated
508   * request, if available.
509   *
510   * @return  The name of the application that generated the associated request,
511   *          or {@code null} if that is not available.
512   */
513  @Nullable()
514  public String getApplicationName()
515  {
516    return applicationName;
517  }
518
519
520
521  /**
522   * Retrieves information about the version of the application that generated
523   * the associated request, if available.
524   *
525   * @return  Information about the version of the application that generated
526   *          the associated request, or {@code null} if that is not available.
527   */
528  @Nullable()
529  public String getApplicationVersion()
530  {
531    return applicationVersion;
532  }
533
534
535
536  /**
537   * Retrieves information about the location in the application code in which
538   * the associated request was created, if available.
539   *
540   * @return  Information about the location in the application code in which
541   *          the associated request was created, or {@code null} if that is not
542   *          available.
543   */
544  @Nullable()
545  public String getCodeLocation()
546  {
547    return codeLocation;
548  }
549
550
551
552  /**
553   * Retrieves a message with information about the purpose of the associated
554   * request, if available.
555   *
556   * @return  A message with information about the purpose of the associated
557   *          request, or {@code null} if that is not available.
558   */
559  @Nullable()
560  public String getRequestPurpose()
561  {
562    return requestPurpose;
563  }
564
565
566
567  /**
568   * {@inheritDoc}
569   */
570  @Override()
571  @NotNull()
572  public String getControlName()
573  {
574    return INFO_CONTROL_NAME_OP_PURPOSE.get();
575  }
576
577
578
579  /**
580   * Retrieves a representation of this operation purpose request control as a
581   * JSON object.  The JSON object uses the following fields:
582   * <UL>
583   *   <LI>
584   *     {@code oid} -- A mandatory string field whose value is the object
585   *     identifier for this control.  For the operation purpose request
586   *     control, the OID is "1.3.6.1.4.1.30221.2.5.19".
587   *   </LI>
588   *   <LI>
589   *     {@code control-name} -- An optional string field whose value is a
590   *     human-readable name for this control.  This field is only intended for
591   *     descriptive purposes, and when decoding a control, the {@code oid}
592   *     field should be used to identify the type of control.
593   *   </LI>
594   *   <LI>
595   *     {@code criticality} -- A mandatory Boolean field used to indicate
596   *     whether this control is considered critical.
597   *   </LI>
598   *   <LI>
599   *     {@code value-base64} -- An optional string field whose value is a
600   *     base64-encoded representation of the raw value for this operation
601   *     purpose request control.  Exactly one of the {@code value-base64} and
602   *     {@code value-json} fields must be present.
603   *   </LI>
604   *   <LI>
605   *     {@code value-json} -- An optional JSON object field whose value is a
606   *     user-friendly representation of the value for this operation purpose
607   *     request control.  Exactly one of the {@code value-base64} and
608   *     {@code value-json} fields must be present, and if the
609   *     {@code value-json} field is used, then it will use the following
610   *     fields:
611   *     <UL>
612   *       <LI>
613   *         {@code application-name} -- An optional string field whose value is
614   *         the name of the application that generated the request.
615   *       </LI>
616   *       <LI>
617   *         {@code application-version} -- An optional string field whose value
618   *         is the version for the application that generated the request.
619   *       </LI>
620   *       <LI>
621   *         {@code code-location} -- An optional string field whose value may
622   *         help identify the location in the application codebase where the
623   *         request is generated.
624   *       </LI>
625   *       <LI>
626   *         {@code request-purpose} -- An optional string field whose value is
627   *         a message that describes the purpose for the request.
628   *       </LI>
629   *     </UL>
630   *   </LI>
631   * </UL>
632   *
633   * @return  A JSON object that contains a representation of this control.
634   */
635  @Override()
636  @NotNull()
637  public JSONObject toJSONControl()
638  {
639    final Map<String,JSONValue> valueFields = new LinkedHashMap<>();
640
641    if (applicationName != null)
642    {
643      valueFields.put(JSON_FIELD_APPLICATION_NAME,
644           new JSONString(applicationName));
645    }
646
647    if (applicationVersion != null)
648    {
649      valueFields.put(JSON_FIELD_APPLICATION_VERSION,
650           new JSONString(applicationVersion));
651    }
652
653    if (codeLocation != null)
654    {
655      valueFields.put(JSON_FIELD_CODE_LOCATION, new JSONString(codeLocation));
656    }
657
658    if (requestPurpose != null)
659    {
660      valueFields.put(JSON_FIELD_REQUEST_PURPOSE,
661           new JSONString(requestPurpose));
662    }
663
664    return new JSONObject(
665         new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID,
666              OPERATION_PURPOSE_REQUEST_OID),
667         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME,
668              INFO_CONTROL_NAME_OP_PURPOSE.get()),
669         new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY,
670              isCritical()),
671         new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON,
672              new JSONObject(valueFields)));
673  }
674
675
676
677  /**
678   * Attempts to decode the provided object as a JSON representation of an
679   * operation purpose request control.
680   *
681   * @param  controlObject  The JSON object to be decoded.  It must not be
682   *                        {@code null}.
683   * @param  strict         Indicates whether to use strict mode when decoding
684   *                        the provided JSON object.  If this is {@code true},
685   *                        then this method will throw an exception if the
686   *                        provided JSON object contains any unrecognized
687   *                        fields.  If this is {@code false}, then unrecognized
688   *                        fields will be ignored.
689   *
690   * @return  The operation purpose request control that was decoded from the
691   *          provided JSON object.
692   *
693   * @throws  LDAPException  If the provided JSON object cannot be parsed as a
694   *                         valid operation purpose request control.
695   */
696  @NotNull()
697  public static OperationPurposeRequestControl decodeJSONControl(
698              @NotNull final JSONObject controlObject,
699              final boolean strict)
700         throws LDAPException
701  {
702    final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper(
703         controlObject, strict, true, true);
704
705    final ASN1OctetString rawValue = jsonControl.getRawValue();
706    if (rawValue != null)
707    {
708      return new OperationPurposeRequestControl(new Control(
709           jsonControl.getOID(), jsonControl.getCriticality(), rawValue));
710    }
711
712
713    final JSONObject valueObject = jsonControl.getValueObject();
714
715    final String applicationName =
716         valueObject.getFieldAsString(JSON_FIELD_APPLICATION_NAME);
717    final String applicationVersion =
718         valueObject.getFieldAsString(JSON_FIELD_APPLICATION_VERSION);
719    final String codeLocation =
720         valueObject.getFieldAsString(JSON_FIELD_CODE_LOCATION);
721    final String requestPurpose =
722         valueObject.getFieldAsString(JSON_FIELD_REQUEST_PURPOSE);
723
724    if ((applicationName == null) && (applicationVersion == null) &&
725         (codeLocation == null) && (requestPurpose == null))
726    {
727      throw new LDAPException(ResultCode.DECODING_ERROR,
728           ERR_OP_PURPOSE_JSON_MISSING_FIELD.get(
729                controlObject.toSingleLineString(), JSON_FIELD_APPLICATION_NAME,
730                JSON_FIELD_APPLICATION_VERSION, JSON_FIELD_CODE_LOCATION,
731                JSON_FIELD_REQUEST_PURPOSE));
732    }
733
734
735    if (strict)
736    {
737      final List<String> unrecognizedFields =
738           JSONControlDecodeHelper.getControlObjectUnexpectedFields(
739                valueObject, JSON_FIELD_APPLICATION_NAME,
740                JSON_FIELD_APPLICATION_VERSION, JSON_FIELD_CODE_LOCATION,
741                JSON_FIELD_REQUEST_PURPOSE);
742      if (! unrecognizedFields.isEmpty())
743      {
744        throw new LDAPException(ResultCode.DECODING_ERROR,
745             ERR_OP_PURPOSE_JSON_UNRECOGNIZED_FIELD.get(
746                  controlObject.toSingleLineString(),
747                  unrecognizedFields.get(0)));
748      }
749    }
750
751
752    return new OperationPurposeRequestControl(jsonControl.getCriticality(),
753         applicationName, applicationVersion, codeLocation, requestPurpose);
754  }
755
756
757
758  /**
759   * {@inheritDoc}
760   */
761  @Override()
762  public void toString(@NotNull final StringBuilder buffer)
763  {
764    buffer.append("OperationPurposeRequestControl(isCritical=");
765    buffer.append(isCritical());
766
767    if (applicationName != null)
768    {
769      buffer.append(", appName='");
770      buffer.append(applicationName);
771      buffer.append('\'');
772    }
773
774
775    if (applicationVersion != null)
776    {
777      buffer.append(", appVersion='");
778      buffer.append(applicationVersion);
779      buffer.append('\'');
780    }
781
782
783    if (codeLocation != null)
784    {
785      buffer.append(", codeLocation='");
786      buffer.append(codeLocation);
787      buffer.append('\'');
788    }
789
790
791    if (requestPurpose != null)
792    {
793      buffer.append(", purpose='");
794      buffer.append(requestPurpose);
795      buffer.append('\'');
796    }
797
798    buffer.append(')');
799  }
800}