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}