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