001/* 002 * Copyright 2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2018 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 = new LinkedHashMap<>(10); 165 try 166 { 167 for (final ASN1Element valueElement : 168 ASN1Sequence.decodeAsSequence(value.getValue()).elements()) 169 { 170 final ASN1Element[] propertyElements = 171 ASN1Sequence.decodeAsSequence(valueElement).elements(); 172 final String propertyName = ASN1OctetString.decodeAsOctetString( 173 propertyElements[0]).stringValue(); 174 final String propertyValue = ASN1OctetString.decodeAsOctetString( 175 propertyElements[1]).stringValue(); 176 177 if (propertyName.isEmpty()) 178 { 179 throw new LDAPException(ResultCode.DECODING_ERROR, 180 ERR_OVERRIDE_SEARCH_LIMITS_REQUEST_EMPTY_PROPERTY_NAME.get()); 181 } 182 183 if (propertyValue.isEmpty()) 184 { 185 throw new LDAPException(ResultCode.DECODING_ERROR, 186 ERR_OVERRIDE_SEARCH_LIMITS_REQUEST_EMPTY_PROPERTY_VALUE.get( 187 propertyName)); 188 } 189 190 if (propertyMap.containsKey(propertyName)) 191 { 192 throw new LDAPException(ResultCode.DECODING_ERROR, 193 ERR_OVERRIDE_SEARCH_LIMITS_REQUEST_DUPLICATE_PROPERTY_NAME.get( 194 propertyName)); 195 } 196 197 propertyMap.put(propertyName, propertyValue); 198 } 199 } 200 catch (final LDAPException e) 201 { 202 Debug.debugException(e); 203 throw e; 204 } 205 catch (final Exception e) 206 { 207 Debug.debugException(e); 208 throw new LDAPException(ResultCode.DECODING_ERROR, 209 ERR_OVERRIDE_SEARCH_LIMITS_REQUEST_CANNOT_DECODE_VALUE.get( 210 StaticUtils.getExceptionMessage(e)), 211 e); 212 } 213 214 if (propertyMap.isEmpty()) 215 { 216 throw new LDAPException(ResultCode.DECODING_ERROR, 217 ERR_OVERRIDE_SEARCH_LIMITS_REQUEST_CONTROL_NO_PROPERTIES.get()); 218 } 219 220 properties = Collections.unmodifiableMap(propertyMap); 221 } 222 223 224 225 /** 226 * Encodes the provided set of properties into an ASN.1 element suitable for 227 * use as the value of this control. 228 * 229 * @param properties The map of properties to set in this control. It must 230 * not be {@code null} or empty, and none of the keys or 231 * values inside it may be {@code null} or empty. 232 * 233 * @return The ASN.1 octet string containing the encoded value. 234 */ 235 static ASN1OctetString encodeValue(final Map<String,String> properties) 236 { 237 Validator.ensureTrue(((properties != null) && (! properties.isEmpty())), 238 "OverrideSearchLimitsRequestControl.<init>properties must not be " + 239 "null or empty"); 240 241 final ArrayList<ASN1Element> propertyElements = 242 new ArrayList<>(properties.size()); 243 for (final Map.Entry<String,String> e : properties.entrySet()) 244 { 245 final String propertyName = e.getKey(); 246 final String propertyValue = e.getValue(); 247 Validator.ensureTrue( 248 ((propertyName != null) && (! propertyName.isEmpty())), 249 "OverrideSearchLimitsRequestControl.<init>properties keys must " + 250 "not be null or empty"); 251 Validator.ensureTrue( 252 ((propertyValue != null) && (! propertyValue.isEmpty())), 253 "OverrideSearchLimitsRequestControl.<init>properties values must " + 254 "not be null or empty"); 255 256 propertyElements.add(new ASN1Sequence( 257 new ASN1OctetString(propertyName), 258 new ASN1OctetString(propertyValue))); 259 } 260 261 return new ASN1OctetString(new ASN1Sequence(propertyElements).encode()); 262 } 263 264 265 266 /** 267 * Retrieves a map of the properties included in this request control. 268 * 269 * @return A map of the properties included in this request control. 270 */ 271 public Map<String,String> getProperties() 272 { 273 return properties; 274 } 275 276 277 278 /** 279 * Retrieves the value of the specified property. 280 * 281 * @param propertyName The name of the property for which to retrieve the 282 * value. It must not be {@code null} or empty, and it 283 * will be treated in a case-sensitive manner. 284 * 285 * @return The value of the requested property, or {@code null} if the 286 * property is not set in the control. 287 */ 288 public String getProperty(final String propertyName) 289 { 290 Validator.ensureTrue(((propertyName != null) && (! propertyName.isEmpty())), 291 "OverrideSearchLimitsRequestControl.getProperty.propertyName must " + 292 "not be null or empty."); 293 294 return properties.get(propertyName); 295 } 296 297 298 299 /** 300 * Retrieves the value of the specified property as a {@code Boolean}. 301 * 302 * @param propertyName The name of the property for which to retrieve the 303 * value. It must not be {@code null} or empty, and it 304 * will be treated in a case-sensitive manner. 305 * @param defaultValue The default value that will be used if the requested 306 * property is not set or if its value cannot be parsed 307 * as a {@code Boolean}. It may be {@code null} if the 308 * default value should be {@code null}. 309 * 310 * @return The Boolean value of the requested property, or the provided 311 * default value if the property is not set or if its value cannot be 312 * parsed as a {@code Boolean}. 313 */ 314 public Boolean getPropertyAsBoolean(final String propertyName, 315 final Boolean defaultValue) 316 { 317 final String propertyValue = getProperty(propertyName); 318 if (propertyValue == null) 319 { 320 return defaultValue; 321 } 322 323 switch (StaticUtils.toLowerCase(propertyValue)) 324 { 325 case "true": 326 case "t": 327 case "yes": 328 case "y": 329 case "on": 330 case "1": 331 return Boolean.TRUE; 332 case "false": 333 case "f": 334 case "no": 335 case "n": 336 case "off": 337 case "0": 338 return Boolean.FALSE; 339 default: 340 return defaultValue; 341 } 342 } 343 344 345 346 /** 347 * Retrieves the value of the specified property as an {@code Integer}. 348 * 349 * @param propertyName The name of the property for which to retrieve the 350 * value. It must not be {@code null} or empty, and it 351 * will be treated in a case-sensitive manner. 352 * @param defaultValue The default value that will be used if the requested 353 * property is not set or if its value cannot be parsed 354 * as an {@code Integer}. It may be {@code null} if the 355 * default value should be {@code null}. 356 * 357 * @return The integer value of the requested property, or the provided 358 * default value if the property is not set or if its value cannot be 359 * parsed as an {@code Integer}. 360 */ 361 public Integer getPropertyAsInteger(final String propertyName, 362 final Integer defaultValue) 363 { 364 final String propertyValue = getProperty(propertyName); 365 if (propertyValue == null) 366 { 367 return defaultValue; 368 } 369 370 try 371 { 372 return Integer.parseInt(propertyValue); 373 } 374 catch (final Exception e) 375 { 376 Debug.debugException(e); 377 return defaultValue; 378 } 379 } 380 381 382 383 /** 384 * Retrieves the value of the specified property as a {@code Long}. 385 * 386 * @param propertyName The name of the property for which to retrieve the 387 * value. It must not be {@code null} or empty, and it 388 * will be treated in a case-sensitive manner. 389 * @param defaultValue The default value that will be used if the requested 390 * property is not set or if its value cannot be parsed 391 * as an {@code Long}. It may be {@code null} if the 392 * default value should be {@code null}. 393 * 394 * @return The long value of the requested property, or the provided default 395 * value if the property is not set or if its value cannot be parsed 396 * as a {@code Long}. 397 */ 398 public Long getPropertyAsLong(final String propertyName, 399 final Long defaultValue) 400 { 401 final String propertyValue = getProperty(propertyName); 402 if (propertyValue == null) 403 { 404 return defaultValue; 405 } 406 407 try 408 { 409 return Long.parseLong(propertyValue); 410 } 411 catch (final Exception e) 412 { 413 Debug.debugException(e); 414 return defaultValue; 415 } 416 } 417 418 419 420 /** 421 * Retrieves the user-friendly name for this control, if available. If no 422 * user-friendly name has been defined, then the OID will be returned. 423 * 424 * @return The user-friendly name for this control, or the OID if no 425 * user-friendly name is available. 426 */ 427 @Override() 428 public String getControlName() 429 { 430 return INFO_OVERRIDE_SEARCH_LIMITS_REQUEST_CONTROL_NAME.get(); 431 } 432 433 434 435 /** 436 * Appends a string representation of this LDAP control to the provided 437 * buffer. 438 * 439 * @param buffer The buffer to which to append the string representation of 440 * this buffer. 441 */ 442 @Override() 443 public void toString(final StringBuilder buffer) 444 { 445 buffer.append("OverrideSearchLimitsRequestControl(oid='"); 446 buffer.append(getOID()); 447 buffer.append("', isCritical="); 448 buffer.append(isCritical()); 449 buffer.append(", properties={"); 450 451 final Iterator<Map.Entry<String,String>> iterator = 452 properties.entrySet().iterator(); 453 while (iterator.hasNext()) 454 { 455 final Map.Entry<String,String> e = iterator.next(); 456 457 buffer.append('\''); 458 buffer.append(e.getKey()); 459 buffer.append("'='"); 460 buffer.append(e.getValue()); 461 buffer.append('\''); 462 463 if (iterator.hasNext()) 464 { 465 buffer.append(", "); 466 } 467 } 468 469 buffer.append("})"); 470 } 471}