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}