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}