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.io.Serializable; 041import java.util.ArrayList; 042 043import com.unboundid.asn1.ASN1Boolean; 044import com.unboundid.asn1.ASN1Element; 045import com.unboundid.asn1.ASN1OctetString; 046import com.unboundid.asn1.ASN1Sequence; 047import com.unboundid.ldap.sdk.LDAPException; 048import com.unboundid.ldap.sdk.ResultCode; 049import com.unboundid.util.Debug; 050import com.unboundid.util.NotMutable; 051import com.unboundid.util.NotNull; 052import com.unboundid.util.Nullable; 053import com.unboundid.util.StaticUtils; 054import com.unboundid.util.ThreadSafety; 055import com.unboundid.util.ThreadSafetyLevel; 056import com.unboundid.util.Validator; 057 058import static com.unboundid.ldap.sdk.controls.ControlMessages.*; 059 060 061 062/** 063 * This class provides a data structure for representing a sort key that is to 064 * be used in conjunction with the {@link ServerSideSortRequestControl} for 065 * requesting that the server sort the results before returning them to the 066 * client. 067 * <BR><BR> 068 * A sort key includes the following elements: 069 * <UL> 070 * <LI>The name of the attribute for which sorting is to be performed.</LI> 071 * <LI>A {@code reverseOrder} flag that indicates whether the results should 072 * be sorted in ascending order (if the value is {@code false}) or 073 * descending order (if the value is {@code true}).</LI> 074 * <LI>An optional matching rule ID, which specifies the ordering matching 075 * rule that should be used to perform the sorting. If this is not 076 * provided, then the default ordering matching rule for the specified 077 * attribute will be used.</LI> 078 * </UL> 079 */ 080@NotMutable() 081@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 082public final class SortKey 083 implements Serializable 084{ 085 /** 086 * The BER type that should be used for the matching rule ID element. 087 */ 088 private static final byte TYPE_MATCHING_RULE_ID = (byte) 0x80; 089 090 091 092 /** 093 * The BER type that should be used for the reverse order element. 094 */ 095 private static final byte TYPE_REVERSE_ORDER = (byte) 0x81; 096 097 098 099 /** 100 * The serial version UID for this serializable class. 101 */ 102 private static final long serialVersionUID = -8631224188301402858L; 103 104 105 106 // Indicates whether the sort should be performed in reverse order. 107 private final boolean reverseOrder; 108 109 // The attribute name for this sort key. 110 @NotNull private final String attributeName; 111 112 // The matching rule ID for this sort key. 113 @Nullable private final String matchingRuleID; 114 115 116 117 /** 118 * Creates a new sort key with the specified attribute name. It will use the 119 * default ordering matching rule associated with that attribute, and it will 120 * not use reverse order. 121 * 122 * @param attributeName The attribute name for this sort key. It must not 123 * be {@code null}. 124 */ 125 public SortKey(@NotNull final String attributeName) 126 { 127 this(attributeName, null, false); 128 } 129 130 131 132 /** 133 * Creates a new sort key with the specified attribute name. It will use the 134 * default ordering matching rule associated with that attribute. 135 * 136 * @param attributeName The attribute name for this sort key. It must not 137 * be {@code null}. 138 * @param reverseOrder Indicates whether the sort should be performed in 139 * reverse order. 140 */ 141 public SortKey(@NotNull final String attributeName, 142 final boolean reverseOrder) 143 { 144 this(attributeName, null, reverseOrder); 145 } 146 147 148 149 /** 150 * Creates a new sort key with the provided information. 151 * 152 * @param attributeName The attribute name for this sort key. It must not 153 * be {@code null}. 154 * @param matchingRuleID The name or OID of the ordering matching rule that 155 * should be used to perform the sort. It may be 156 * {@code null} if the default ordering matching rule 157 * for the specified attribute is to be used. 158 * @param reverseOrder Indicates whether the sort should be performed in 159 * reverse order. 160 */ 161 public SortKey(@NotNull final String attributeName, 162 @Nullable final String matchingRuleID, 163 final boolean reverseOrder) 164 { 165 Validator.ensureNotNull(attributeName); 166 167 this.attributeName = attributeName; 168 this.matchingRuleID = matchingRuleID; 169 this.reverseOrder = reverseOrder; 170 } 171 172 173 174 /** 175 * Retrieves the attribute name for this sort key. 176 * 177 * @return The attribute name for this sort key. 178 */ 179 @NotNull() 180 public String getAttributeName() 181 { 182 return attributeName; 183 } 184 185 186 187 /** 188 * Retrieves the name or OID of the ordering matching rule that should be used 189 * to perform the sort, if defined. 190 * 191 * @return The name or OID of the ordering matching rule that should be used 192 * to perform the sort, or {@code null} if the sort should use the 193 * default ordering matching rule associated with the specified 194 * attribute. 195 */ 196 @Nullable() 197 public String getMatchingRuleID() 198 { 199 return matchingRuleID; 200 } 201 202 203 204 /** 205 * Indicates whether the sort should be performed in reverse order. 206 * 207 * @return {@code true} if the sort should be performed in reverse order, or 208 * {@code false} if it should be performed in the standard order for 209 * the associated ordering matching rule. 210 */ 211 public boolean reverseOrder() 212 { 213 return reverseOrder; 214 } 215 216 217 218 /** 219 * Encodes this sort key into an ASN.1 sequence suitable for use in the 220 * server-side sort control. 221 * 222 * @return An ASN.1 sequence containing the encoded representation of this 223 * sort key. 224 */ 225 @NotNull() 226 ASN1Sequence encode() 227 { 228 final ArrayList<ASN1Element> elements = new ArrayList<>(3); 229 elements.add(new ASN1OctetString(attributeName)); 230 231 if (matchingRuleID != null) 232 { 233 elements.add(new ASN1OctetString(TYPE_MATCHING_RULE_ID, matchingRuleID)); 234 } 235 236 if (reverseOrder) 237 { 238 elements.add(new ASN1Boolean(TYPE_REVERSE_ORDER, reverseOrder)); 239 } 240 241 return new ASN1Sequence(elements); 242 } 243 244 245 246 /** 247 * Decodes the provided ASN.1 element as a sort key. 248 * 249 * @param element The ASN.1 element to decode as a sort key. 250 * 251 * @return The decoded sort key. 252 * 253 * @throws LDAPException If the provided ASN.1 element cannot be decoded as 254 * a sort key. 255 */ 256 @NotNull() 257 public static SortKey decode(@NotNull final ASN1Element element) 258 throws LDAPException 259 { 260 final ASN1Element[] elements; 261 try 262 { 263 elements = ASN1Sequence.decodeAsSequence(element).elements(); 264 } 265 catch (final Exception e) 266 { 267 Debug.debugException(e); 268 throw new LDAPException(ResultCode.DECODING_ERROR, 269 ERR_SORT_KEY_NOT_SEQUENCE.get(e), e); 270 } 271 272 if ((elements.length < 1) || (elements.length > 3)) 273 { 274 throw new LDAPException(ResultCode.DECODING_ERROR, 275 ERR_SORT_KEY_INVALID_ELEMENT_COUNT.get(elements.length)); 276 } 277 278 boolean reverseOrder = false; 279 String matchingRuleID = null; 280 final String attributeName = 281 ASN1OctetString.decodeAsOctetString(elements[0]).stringValue(); 282 for (int i=1; i < elements.length; i++) 283 { 284 switch (elements[i].getType()) 285 { 286 case TYPE_MATCHING_RULE_ID: 287 matchingRuleID = 288 ASN1OctetString.decodeAsOctetString(elements[i]).stringValue(); 289 break; 290 291 case TYPE_REVERSE_ORDER: 292 try 293 { 294 reverseOrder = 295 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue(); 296 } 297 catch (final Exception e) 298 { 299 Debug.debugException(e); 300 throw new LDAPException(ResultCode.DECODING_ERROR, 301 ERR_SORT_KEY_REVERSE_NOT_BOOLEAN.get(e), e); 302 } 303 break; 304 305 default: 306 throw new LDAPException(ResultCode.DECODING_ERROR, 307 ERR_SORT_KEY_ELEMENT_INVALID_TYPE.get( 308 StaticUtils.toHex(elements[i].getType()))); 309 } 310 } 311 312 return new SortKey(attributeName, matchingRuleID, reverseOrder); 313 } 314 315 316 317 /** 318 * Retrieves a string representation of this sort key. 319 * 320 * @return A string representation of this sort key. 321 */ 322 @Override() 323 @NotNull() 324 public String toString() 325 { 326 final StringBuilder buffer = new StringBuilder(); 327 toString(buffer); 328 return buffer.toString(); 329 } 330 331 332 333 /** 334 * Appends a string representation of this sort key to the provided buffer. 335 * 336 * @param buffer The buffer to which to append a string representation of 337 * this sort key. 338 */ 339 public void toString(@NotNull final StringBuilder buffer) 340 { 341 buffer.append("SortKey(attributeName="); 342 buffer.append(attributeName); 343 344 if (matchingRuleID != null) 345 { 346 buffer.append(", matchingRuleID="); 347 buffer.append(matchingRuleID); 348 } 349 350 buffer.append(", reverseOrder="); 351 buffer.append(reverseOrder); 352 buffer.append(')'); 353 } 354}