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