001/*
002 * Copyright 2018-2022 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2018-2022 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) 2018-2022 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.Collections;
042import java.util.Iterator;
043import java.util.LinkedHashMap;
044import java.util.Map;
045
046import com.unboundid.asn1.ASN1Element;
047import com.unboundid.asn1.ASN1OctetString;
048import com.unboundid.asn1.ASN1Sequence;
049import com.unboundid.ldap.sdk.Control;
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;
060
061import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*;
062
063
064
065/**
066 * This class provides an implementation of a control that may be included in a
067 * search request to override certain default limits that would normally be in
068 * place for the operation.  The override behavior is specified using one or
069 * more name-value pairs, with property names being case sensitive.
070 * <BR>
071 * <BLOCKQUOTE>
072 *   <B>NOTE:</B>  This class, and other classes within the
073 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
074 *   supported for use against Ping Identity, UnboundID, and
075 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
076 *   for proprietary functionality or for external specifications that are not
077 *   considered stable or mature enough to be guaranteed to work in an
078 *   interoperable way with other types of LDAP servers.
079 * </BLOCKQUOTE>
080 * <BR>
081 * The control has an OID of 1.3.6.1.4.1.30221.2.5.56, a criticality of either
082 * {@code true} or {@code false}, and a value with the provided encoding:
083 *
084 * that contains a mapping of one or
085 * more case-sensitive property-value pairs.  Property names will be treated in
086 * a case-sensitive manner.
087 * the following encoding:
088 * <PRE>
089 *   OverrideSearchLimitsRequestValue ::= SEQUENCE OF SEQUENCE {
090 *        propertyName      OCTET STRING,
091 *        propertyValue     OCTET STRING }
092 * </PRE>
093 */
094@NotMutable()
095@ThreadSafety(level= ThreadSafetyLevel.COMPLETELY_THREADSAFE)
096public final class OverrideSearchLimitsRequestControl
097       extends Control
098{
099  /**
100   * The OID (1.3.6.1.4.1.30221.2.5.56) for the override search limits request
101   * control.
102   */
103  @NotNull public static final String OVERRIDE_SEARCH_LIMITS_REQUEST_OID =
104       "1.3.6.1.4.1.30221.2.5.56";
105
106
107
108  /**
109   * The serial version UID for this serializable class.
110   */
111  private static final long serialVersionUID = 3685279915414141978L;
112
113
114
115  // The set of properties included in this control.
116  @NotNull private final Map<String,String> properties;
117
118
119
120  /**
121   * Creates a new instance of this override search limits request control with
122   * the specified property name and value.  It will not be critical.
123   *
124   * @param  propertyName   The name of the property to set.  It must not be
125   *                        {@code null} or empty.
126   * @param  propertyValue  The value for the specified property.  It must not
127   *                        be {@code null} or empty.
128   */
129  public OverrideSearchLimitsRequestControl(@NotNull final String propertyName,
130                                            @NotNull final String propertyValue)
131  {
132    this(Collections.singletonMap(propertyName, propertyValue), false);
133  }
134
135
136
137  /**
138   * Creates a new instance of this override search limits request control with
139   * the provided set of properties.
140   *
141   * @param  properties  The map of properties to set in this control.  It must
142   *                     not be {@code null} or empty, and none of the keys or
143   *                     values inside it may be {@code null} or empty.
144   * @param  isCritical  Indicates whether the control should be considered
145   *                     critical.
146   */
147  public OverrideSearchLimitsRequestControl(
148              @NotNull final Map<String,String> properties,
149              final boolean isCritical)
150  {
151    super(OVERRIDE_SEARCH_LIMITS_REQUEST_OID, isCritical,
152         encodeValue(properties));
153
154    this.properties =
155         Collections.unmodifiableMap(new LinkedHashMap<>(properties));
156  }
157
158
159
160  /**
161   * Creates a new instance of this override search limits request control that
162   * is decoded from the provided generic control.
163   *
164   * @param  control  The generic control to decode as an override search limits
165   *                  request control.  It must not be {@code null}.
166   *
167   * @throws  LDAPException  If the provided control cannot be decoded as an
168   *                         override search limits request control.
169   */
170  public OverrideSearchLimitsRequestControl(@NotNull final Control control)
171         throws LDAPException
172  {
173    super(control);
174
175    final ASN1OctetString value = control.getValue();
176    if (value == null)
177    {
178      throw new LDAPException(ResultCode.DECODING_ERROR,
179           ERR_OVERRIDE_SEARCH_LIMITS_REQUEST_NO_VALUE.get());
180    }
181
182    final LinkedHashMap<String,String> propertyMap =
183         new LinkedHashMap<>(StaticUtils.computeMapCapacity(10));
184    try
185    {
186      for (final ASN1Element valueElement :
187           ASN1Sequence.decodeAsSequence(value.getValue()).elements())
188      {
189        final ASN1Element[] propertyElements =
190             ASN1Sequence.decodeAsSequence(valueElement).elements();
191        final String propertyName = ASN1OctetString.decodeAsOctetString(
192             propertyElements[0]).stringValue();
193        final String propertyValue = ASN1OctetString.decodeAsOctetString(
194             propertyElements[1]).stringValue();
195
196        if (propertyName.isEmpty())
197        {
198          throw new LDAPException(ResultCode.DECODING_ERROR,
199               ERR_OVERRIDE_SEARCH_LIMITS_REQUEST_EMPTY_PROPERTY_NAME.get());
200        }
201
202        if (propertyValue.isEmpty())
203        {
204          throw new LDAPException(ResultCode.DECODING_ERROR,
205               ERR_OVERRIDE_SEARCH_LIMITS_REQUEST_EMPTY_PROPERTY_VALUE.get(
206                    propertyName));
207        }
208
209        if (propertyMap.containsKey(propertyName))
210        {
211          throw new LDAPException(ResultCode.DECODING_ERROR,
212               ERR_OVERRIDE_SEARCH_LIMITS_REQUEST_DUPLICATE_PROPERTY_NAME.get(
213                    propertyName));
214        }
215
216        propertyMap.put(propertyName, propertyValue);
217      }
218    }
219    catch (final LDAPException e)
220    {
221      Debug.debugException(e);
222      throw e;
223    }
224    catch (final Exception e)
225    {
226      Debug.debugException(e);
227      throw new LDAPException(ResultCode.DECODING_ERROR,
228           ERR_OVERRIDE_SEARCH_LIMITS_REQUEST_CANNOT_DECODE_VALUE.get(
229                StaticUtils.getExceptionMessage(e)),
230           e);
231    }
232
233    if (propertyMap.isEmpty())
234    {
235      throw new LDAPException(ResultCode.DECODING_ERROR,
236           ERR_OVERRIDE_SEARCH_LIMITS_REQUEST_CONTROL_NO_PROPERTIES.get());
237    }
238
239    properties = Collections.unmodifiableMap(propertyMap);
240  }
241
242
243
244  /**
245   * Encodes the provided set of properties into an ASN.1 element suitable for
246   * use as the value of this control.
247   *
248   * @param  properties  The map of properties to set in this control.  It must
249   *                     not be {@code null} or empty, and none of the keys or
250   *                     values inside it may be {@code null} or empty.
251   *
252   * @return  The ASN.1 octet string containing the encoded value.
253   */
254  @NotNull()
255  static ASN1OctetString encodeValue(
256              @NotNull final Map<String,String> properties)
257  {
258    Validator.ensureTrue(((properties != null) && (! properties.isEmpty())),
259         "OverrideSearchLimitsRequestControl.<init>properties must not be " +
260              "null or empty");
261
262    final ArrayList<ASN1Element> propertyElements =
263         new ArrayList<>(properties.size());
264    for (final Map.Entry<String,String> e : properties.entrySet())
265    {
266      final String propertyName = e.getKey();
267      final String propertyValue = e.getValue();
268      Validator.ensureTrue(
269           ((propertyName != null) && (! propertyName.isEmpty())),
270           "OverrideSearchLimitsRequestControl.<init>properties keys must " +
271                "not be null or empty");
272      Validator.ensureTrue(
273           ((propertyValue != null) && (! propertyValue.isEmpty())),
274           "OverrideSearchLimitsRequestControl.<init>properties values must " +
275                "not be null or empty");
276
277      propertyElements.add(new ASN1Sequence(
278           new ASN1OctetString(propertyName),
279           new ASN1OctetString(propertyValue)));
280    }
281
282    return new ASN1OctetString(new ASN1Sequence(propertyElements).encode());
283  }
284
285
286
287  /**
288   * Retrieves a map of the properties included in this request control.
289   *
290   * @return  A map of the properties included in this request control.
291   */
292  @NotNull()
293  public Map<String,String> getProperties()
294  {
295    return properties;
296  }
297
298
299
300  /**
301   * Retrieves the value of the specified property.
302   *
303   * @param  propertyName  The name of the property for which to retrieve the
304   *                       value.  It must not be {@code null} or empty, and it
305   *                       will be treated in a case-sensitive manner.
306   *
307   * @return  The value of the requested property, or {@code null} if the
308   *          property is not set in the control.
309   */
310  @Nullable()
311  public String getProperty(@NotNull final String propertyName)
312  {
313    Validator.ensureTrue(((propertyName != null) && (! propertyName.isEmpty())),
314         "OverrideSearchLimitsRequestControl.getProperty.propertyName must " +
315              "not be null or empty.");
316
317    return properties.get(propertyName);
318  }
319
320
321
322  /**
323   * Retrieves the value of the specified property as a {@code Boolean}.
324   *
325   * @param  propertyName  The name of the property for which to retrieve the
326   *                       value.  It must not be {@code null} or empty, and it
327   *                       will be treated in a case-sensitive manner.
328   * @param  defaultValue  The default value that will be used if the requested
329   *                       property is not set or if its value cannot be parsed
330   *                       as a {@code Boolean}.  It may be {@code null} if the
331   *                       default value should be {@code null}.
332   *
333   * @return  The Boolean value of the requested property, or the provided
334   *          default value if the property is not set or if its value cannot be
335   *          parsed as a {@code Boolean}.
336   */
337  @Nullable()
338  public Boolean getPropertyAsBoolean(@NotNull final String propertyName,
339                                      @Nullable final Boolean defaultValue)
340  {
341    final String propertyValue = getProperty(propertyName);
342    if (propertyValue == null)
343    {
344      return defaultValue;
345    }
346
347    switch (StaticUtils.toLowerCase(propertyValue))
348    {
349      case "true":
350      case "t":
351      case "yes":
352      case "y":
353      case "on":
354      case "1":
355        return Boolean.TRUE;
356      case "false":
357      case "f":
358      case "no":
359      case "n":
360      case "off":
361      case "0":
362        return Boolean.FALSE;
363      default:
364        return defaultValue;
365    }
366  }
367
368
369
370  /**
371   * Retrieves the value of the specified property as an {@code Integer}.
372   *
373   * @param  propertyName  The name of the property for which to retrieve the
374   *                       value.  It must not be {@code null} or empty, and it
375   *                       will be treated in a case-sensitive manner.
376   * @param  defaultValue  The default value that will be used if the requested
377   *                       property is not set or if its value cannot be parsed
378   *                       as an {@code Integer}.  It may be {@code null} if the
379   *                       default value should be {@code null}.
380   *
381   * @return  The integer value of the requested property, or the provided
382   *          default value if the property is not set or if its value cannot be
383   *          parsed as an {@code Integer}.
384   */
385  @Nullable()
386  public Integer getPropertyAsInteger(@NotNull final String propertyName,
387                                      @Nullable final Integer defaultValue)
388  {
389    final String propertyValue = getProperty(propertyName);
390    if (propertyValue == null)
391    {
392      return defaultValue;
393    }
394
395    try
396    {
397      return Integer.parseInt(propertyValue);
398    }
399    catch (final Exception e)
400    {
401      Debug.debugException(e);
402      return defaultValue;
403    }
404  }
405
406
407
408  /**
409   * Retrieves the value of the specified property as a {@code Long}.
410   *
411   * @param  propertyName  The name of the property for which to retrieve the
412   *                       value.  It must not be {@code null} or empty, and it
413   *                       will be treated in a case-sensitive manner.
414   * @param  defaultValue  The default value that will be used if the requested
415   *                       property is not set or if its value cannot be parsed
416   *                       as an {@code Long}.  It may be {@code null} if the
417   *                       default value should be {@code null}.
418   *
419   * @return  The long value of the requested property, or the provided default
420   *          value if the property is not set or if its value cannot be parsed
421   *          as a {@code Long}.
422   */
423  @Nullable()
424  public Long getPropertyAsLong(@NotNull final String propertyName,
425                                @Nullable final Long defaultValue)
426  {
427    final String propertyValue = getProperty(propertyName);
428    if (propertyValue == null)
429    {
430      return defaultValue;
431    }
432
433    try
434    {
435      return Long.parseLong(propertyValue);
436    }
437    catch (final Exception e)
438    {
439      Debug.debugException(e);
440      return defaultValue;
441    }
442  }
443
444
445
446  /**
447   * Retrieves the user-friendly name for this control, if available.  If no
448   * user-friendly name has been defined, then the OID will be returned.
449   *
450   * @return  The user-friendly name for this control, or the OID if no
451   *          user-friendly name is available.
452   */
453  @Override()
454  @NotNull()
455  public String getControlName()
456  {
457    return INFO_OVERRIDE_SEARCH_LIMITS_REQUEST_CONTROL_NAME.get();
458  }
459
460
461
462  /**
463   * Appends a string representation of this LDAP control to the provided
464   * buffer.
465   *
466   * @param  buffer  The buffer to which to append the string representation of
467   *                 this buffer.
468   */
469  @Override()
470  public void toString(@NotNull final StringBuilder buffer)
471  {
472    buffer.append("OverrideSearchLimitsRequestControl(oid='");
473    buffer.append(getOID());
474    buffer.append("', isCritical=");
475    buffer.append(isCritical());
476    buffer.append(", properties={");
477
478    final Iterator<Map.Entry<String,String>> iterator =
479         properties.entrySet().iterator();
480    while (iterator.hasNext())
481    {
482      final Map.Entry<String,String> e = iterator.next();
483
484      buffer.append('\'');
485      buffer.append(e.getKey());
486      buffer.append("'='");
487      buffer.append(e.getValue());
488      buffer.append('\'');
489
490      if (iterator.hasNext())
491      {
492        buffer.append(", ");
493      }
494    }
495
496    buffer.append("})");
497  }
498}