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