001/* 002 * Copyright 2007-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-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) 2007-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.controls; 037 038 039 040import java.util.LinkedHashMap; 041import java.util.List; 042import java.util.Map; 043 044import com.unboundid.asn1.ASN1Element; 045import com.unboundid.asn1.ASN1Enumerated; 046import com.unboundid.asn1.ASN1Exception; 047import com.unboundid.asn1.ASN1OctetString; 048import com.unboundid.asn1.ASN1Sequence; 049import com.unboundid.ldap.sdk.Control; 050import com.unboundid.ldap.sdk.DecodeableControl; 051import com.unboundid.ldap.sdk.JSONControlDecodeHelper; 052import com.unboundid.ldap.sdk.LDAPException; 053import com.unboundid.ldap.sdk.ResultCode; 054import com.unboundid.ldap.sdk.SearchResult; 055import com.unboundid.util.Debug; 056import com.unboundid.util.NotMutable; 057import com.unboundid.util.NotNull; 058import com.unboundid.util.Nullable; 059import com.unboundid.util.ThreadSafety; 060import com.unboundid.util.ThreadSafetyLevel; 061import com.unboundid.util.json.JSONField; 062import com.unboundid.util.json.JSONNumber; 063import com.unboundid.util.json.JSONObject; 064import com.unboundid.util.json.JSONString; 065import com.unboundid.util.json.JSONValue; 066 067import static com.unboundid.ldap.sdk.controls.ControlMessages.*; 068 069 070 071/** 072 * This class provides an implementation of the server-side sort response 073 * control, as defined in 074 * <A HREF="http://www.ietf.org/rfc/rfc2891.txt">RFC 2891</A>. It may be used 075 * to provide information about the result of server-side sort processing. If 076 * the corresponding search request included the 077 * {@link ServerSideSortRequestControl}, then the search result done message 078 * may include this response control to provide information about the state of 079 * the sorting. 080 */ 081@NotMutable() 082@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 083public final class ServerSideSortResponseControl 084 extends Control 085 implements DecodeableControl 086{ 087 /** 088 * The OID (1.2.840.113556.1.4.474) for the server-side sort response control. 089 */ 090 @NotNull public static final String SERVER_SIDE_SORT_RESPONSE_OID = 091 "1.2.840.113556.1.4.474"; 092 093 094 095 /** 096 * The BER type to use for the element that holds the attribute type. 097 */ 098 private static final byte TYPE_ATTRIBUTE_TYPE = (byte) 0x80; 099 100 101 102 /** 103 * The name of the field used to hold the attribute name in the JSON 104 * representation of this control. 105 */ 106 @NotNull private static final String JSON_FIELD_ATTRIBUTE_NAME = 107 "attribute-name"; 108 109 110 111 /** 112 * The name of the field used to hold the result code in the JSON 113 * representation of this control. 114 */ 115 @NotNull private static final String JSON_FIELD_RESULT_CODE = 116 "result-code"; 117 118 119 120 /** 121 * The serial version UID for this serializable class. 122 */ 123 private static final long serialVersionUID = -8707533262822875822L; 124 125 126 127 // The result code for this server-side sort response control. 128 @NotNull private final ResultCode resultCode; 129 130 // The name of the attribute associated with this result, if available. 131 @Nullable private final String attributeName; 132 133 134 135 /** 136 * Creates a new empty control instance that is intended to be used only for 137 * decoding controls via the {@code DecodeableControl} interface. 138 */ 139 ServerSideSortResponseControl() 140 { 141 resultCode = null; 142 attributeName = null; 143 } 144 145 146 147 /** 148 * Creates a new server-side sort response control with the provided 149 * information. 150 * 151 * @param resultCode The result code for this server-side sort response. 152 * @param attributeName The name of the attribute associated with this 153 * result. It may be {@code null} if there is no 154 * associated attribute name. 155 */ 156 public ServerSideSortResponseControl(@NotNull final ResultCode resultCode, 157 @Nullable final String attributeName) 158 { 159 this(resultCode, attributeName, false); 160 } 161 162 163 164 /** 165 * Creates a new server-side sort response control with the provided 166 * information. 167 * 168 * @param resultCode The result code for this server-side sort response. 169 * @param attributeName The name of the attribute associated with this 170 * result. It may be {@code null} if there is no 171 * associated attribute name. 172 * @param isCritical Indicates whether this control should be marked 173 * critical. Response controls should generally not be 174 * critical. 175 */ 176 public ServerSideSortResponseControl(@NotNull final ResultCode resultCode, 177 @Nullable final String attributeName, 178 final boolean isCritical) 179 { 180 super(SERVER_SIDE_SORT_RESPONSE_OID, isCritical, 181 encodeValue(resultCode, attributeName)); 182 183 this.resultCode = resultCode; 184 this.attributeName = attributeName; 185 } 186 187 188 189 /** 190 * Creates a new server-side sort response control from the information 191 * contained in the provided control. 192 * 193 * @param oid The OID for the control. 194 * @param isCritical Indicates whether the control should be marked 195 * critical. 196 * @param value The encoded value for the control. This may be 197 * {@code null} if no value was provided. 198 * 199 * @throws LDAPException If a problem occurs while attempting to decode the 200 * provided control as a server-side sort response 201 * control. 202 */ 203 public ServerSideSortResponseControl(@NotNull final String oid, 204 final boolean isCritical, 205 @Nullable final ASN1OctetString value) 206 throws LDAPException 207 { 208 super(oid, isCritical, value); 209 210 if (value == null) 211 { 212 throw new LDAPException(ResultCode.DECODING_ERROR, 213 ERR_SORT_RESPONSE_NO_VALUE.get()); 214 } 215 216 final ASN1Sequence valueSequence; 217 try 218 { 219 final ASN1Element valueElement = 220 ASN1Element.decode(value.getValue()); 221 valueSequence = ASN1Sequence.decodeAsSequence(valueElement); 222 } 223 catch (final ASN1Exception ae) 224 { 225 Debug.debugException(ae); 226 throw new LDAPException(ResultCode.DECODING_ERROR, 227 ERR_SORT_RESPONSE_VALUE_NOT_SEQUENCE.get(ae), ae); 228 } 229 230 final ASN1Element[] valueElements = valueSequence.elements(); 231 if ((valueElements.length < 1) || (valueElements.length > 2)) 232 { 233 throw new LDAPException(ResultCode.DECODING_ERROR, 234 ERR_SORT_RESPONSE_INVALID_ELEMENT_COUNT.get( 235 valueElements.length)); 236 } 237 238 try 239 { 240 final int rc = 241 ASN1Enumerated.decodeAsEnumerated(valueElements[0]).intValue(); 242 resultCode = ResultCode.valueOf(rc); 243 } 244 catch (final ASN1Exception ae) 245 { 246 Debug.debugException(ae); 247 throw new LDAPException(ResultCode.DECODING_ERROR, 248 ERR_SORT_RESPONSE_FIRST_NOT_ENUM.get(ae), ae); 249 } 250 251 if (valueElements.length == 2) 252 { 253 attributeName = 254 ASN1OctetString.decodeAsOctetString(valueElements[1]).stringValue(); 255 } 256 else 257 { 258 attributeName = null; 259 } 260 } 261 262 263 264 /** 265 * {@inheritDoc} 266 */ 267 @Override() 268 @NotNull() 269 public ServerSideSortResponseControl decodeControl(@NotNull final String oid, 270 final boolean isCritical, 271 @Nullable final ASN1OctetString value) 272 throws LDAPException 273 { 274 return new ServerSideSortResponseControl(oid, isCritical, value); 275 } 276 277 278 279 /** 280 * Extracts a server-side sort response control from the provided result. 281 * 282 * @param result The result from which to retrieve the server-side sort 283 * response control. 284 * 285 * @return The server-side sort response control contained in the provided 286 * result, or {@code null} if the result did not contain a 287 * server-side sort response control. 288 * 289 * @throws LDAPException If a problem is encountered while attempting to 290 * decode the server-side sort response control 291 * contained in the provided result. 292 */ 293 @Nullable() 294 public static ServerSideSortResponseControl get( 295 @NotNull final SearchResult result) 296 throws LDAPException 297 { 298 final Control c = result.getResponseControl(SERVER_SIDE_SORT_RESPONSE_OID); 299 if (c == null) 300 { 301 return null; 302 } 303 304 if (c instanceof ServerSideSortResponseControl) 305 { 306 return (ServerSideSortResponseControl) c; 307 } 308 else 309 { 310 return new ServerSideSortResponseControl(c.getOID(), c.isCritical(), 311 c.getValue()); 312 } 313 } 314 315 316 317 /** 318 * Encodes the provided information into an octet string that can be used as 319 * the value for this control. 320 * 321 * @param resultCode The result code for this server-side sort response 322 * control. 323 * @param attributeName The attribute name to include in the control, or 324 * {@code null} if it should not be provided. 325 * 326 * @return An ASN.1 octet string that can be used as the value for this 327 * control. 328 */ 329 @NotNull() 330 private static ASN1OctetString encodeValue( 331 @NotNull final ResultCode resultCode, 332 @Nullable final String attributeName) 333 { 334 final ASN1Element[] valueElements; 335 if (attributeName == null) 336 { 337 valueElements = new ASN1Element[] 338 { 339 new ASN1Enumerated(resultCode.intValue()) 340 }; 341 } 342 else 343 { 344 valueElements = new ASN1Element[] 345 { 346 new ASN1Enumerated(resultCode.intValue()), 347 new ASN1OctetString(TYPE_ATTRIBUTE_TYPE, attributeName) 348 }; 349 } 350 351 return new ASN1OctetString(new ASN1Sequence(valueElements).encode()); 352 } 353 354 355 356 /** 357 * Retrieves the result code for this server-side sort response control. 358 * 359 * @return The result code for this server-side sort response control. 360 */ 361 @NotNull() 362 public ResultCode getResultCode() 363 { 364 return resultCode; 365 } 366 367 368 369 /** 370 * Retrieves the attribute name for this server-side sort response control, if 371 * available. 372 * 373 * @return The attribute name for this server-side sort response control, or 374 * {@code null} if none was provided. 375 */ 376 @Nullable() 377 public String getAttributeName() 378 { 379 return attributeName; 380 } 381 382 383 384 /** 385 * {@inheritDoc} 386 */ 387 @Override() 388 @NotNull() 389 public String getControlName() 390 { 391 return INFO_CONTROL_NAME_SORT_RESPONSE.get(); 392 } 393 394 395 396 /** 397 * Retrieves a representation of this server-side sort response control as a 398 * JSON object. The JSON object uses the following fields: 399 * <UL> 400 * <LI> 401 * {@code oid} -- A mandatory string field whose value is the object 402 * identifier for this control. For the server-side sort response 403 * control, the OID is "1.2.840.113556.1.4.474". 404 * </LI> 405 * <LI> 406 * {@code control-name} -- An optional string field whose value is a 407 * human-readable name for this control. This field is only intended for 408 * descriptive purposes, and when decoding a control, the {@code oid} 409 * field should be used to identify the type of control. 410 * </LI> 411 * <LI> 412 * {@code criticality} -- A mandatory Boolean field used to indicate 413 * whether this control is considered critical. 414 * </LI> 415 * <LI> 416 * {@code value-base64} -- An optional string field whose value is a 417 * base64-encoded representation of the raw value for this server-side 418 * sort response control. Exactly one of the {@code value-base64} and 419 * {@code value-json} fields must be present. 420 * </LI> 421 * <LI> 422 * {@code value-json} -- An optional JSON object field whose value is a 423 * user-friendly representation of the value for this server-side sort 424 * response control. Exactly one of the {@code value-base64} and 425 * {@code value-json} fields must be present, and if the 426 * {@code value-json} field is used, then it will use the following 427 * fields: 428 * <UL> 429 * <LI> 430 * {@code result-code} -- An integer field whose value is the numeric 431 * representation of the result code for the sort processing. 432 * </LI> 433 * <LI> 434 * {@code attribute-name} -- An optional string field whose value is 435 * the name of the attribute with which the result code is most 436 * closely associated. 437 * </LI> 438 * </UL> 439 * </LI> 440 * </UL> 441 * 442 * @return A JSON object that contains a representation of this control. 443 */ 444 @Override() 445 @NotNull() 446 public JSONObject toJSONControl() 447 { 448 final Map<String,JSONValue> valueFields = new LinkedHashMap<>(); 449 valueFields.put(JSON_FIELD_RESULT_CODE, 450 new JSONNumber(resultCode.intValue())); 451 452 if (attributeName != null) 453 { 454 valueFields.put(JSON_FIELD_ATTRIBUTE_NAME, new JSONString(attributeName)); 455 } 456 457 return new JSONObject( 458 new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID, 459 SERVER_SIDE_SORT_RESPONSE_OID), 460 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME, 461 INFO_CONTROL_NAME_SORT_RESPONSE.get()), 462 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY, 463 isCritical()), 464 new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON, 465 new JSONObject(valueFields))); 466 } 467 468 469 470 /** 471 * Attempts to decode the provided object as a JSON representation of a 472 * server-side sort response control. 473 * 474 * @param controlObject The JSON object to be decoded. It must not be 475 * {@code null}. 476 * @param strict Indicates whether to use strict mode when decoding 477 * the provided JSON object. If this is {@code true}, 478 * then this method will throw an exception if the 479 * provided JSON object contains any unrecognized 480 * fields. If this is {@code false}, then unrecognized 481 * fields will be ignored. 482 * 483 * @return The server=side sort response control that was decoded from 484 * the provided JSON object. 485 * 486 * @throws LDAPException If the provided JSON object cannot be parsed as a 487 * valid server-side sort response control. 488 */ 489 @NotNull() 490 public static ServerSideSortResponseControl decodeJSONControl( 491 @NotNull final JSONObject controlObject, 492 final boolean strict) 493 throws LDAPException 494 { 495 final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper( 496 controlObject, strict, true, true); 497 498 final ASN1OctetString rawValue = jsonControl.getRawValue(); 499 if (rawValue != null) 500 { 501 return new ServerSideSortResponseControl(jsonControl.getOID(), 502 jsonControl.getCriticality(), rawValue); 503 } 504 505 506 final JSONObject valueObject = jsonControl.getValueObject(); 507 508 final Integer resultCodeValue = 509 valueObject.getFieldAsInteger(JSON_FIELD_RESULT_CODE); 510 if (resultCodeValue == null) 511 { 512 throw new LDAPException(ResultCode.DECODING_ERROR, 513 ERR_SORT_RESPONSE_JSON_MISSING_RESULT_CODE.get( 514 controlObject.toSingleLineString(), JSON_FIELD_RESULT_CODE)); 515 } 516 517 final ResultCode resultCode = ResultCode.valueOf(resultCodeValue); 518 519 520 final String attributeName = 521 valueObject.getFieldAsString(JSON_FIELD_ATTRIBUTE_NAME); 522 523 524 if (strict) 525 { 526 final List<String> unrecognizedFields = 527 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 528 valueObject, JSON_FIELD_RESULT_CODE, 529 JSON_FIELD_ATTRIBUTE_NAME); 530 if (! unrecognizedFields.isEmpty()) 531 { 532 throw new LDAPException(ResultCode.DECODING_ERROR, 533 ERR_SORT_RESPONSE_JSON_UNRECOGNIZED_FIELD.get( 534 controlObject.toSingleLineString(), 535 unrecognizedFields.get(0))); 536 } 537 } 538 539 540 return new ServerSideSortResponseControl(resultCode, attributeName, 541 jsonControl.getCriticality()); 542 } 543 544 545 546 /** 547 * {@inheritDoc} 548 */ 549 @Override() 550 public void toString(@NotNull final StringBuilder buffer) 551 { 552 buffer.append("ServerSideSortResponseControl(resultCode="); 553 buffer.append(resultCode); 554 555 if (attributeName != null) 556 { 557 buffer.append(", attributeName='"); 558 buffer.append(attributeName); 559 buffer.append('\''); 560 } 561 562 buffer.append(')'); 563 } 564}