001/* 002 * Copyright 2009-2018 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2015-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.extensions; 022 023 024 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.Iterator; 028import java.util.List; 029 030import com.unboundid.asn1.ASN1Boolean; 031import com.unboundid.asn1.ASN1Element; 032import com.unboundid.asn1.ASN1Enumerated; 033import com.unboundid.asn1.ASN1OctetString; 034import com.unboundid.asn1.ASN1Sequence; 035import com.unboundid.asn1.ASN1Integer; 036import com.unboundid.ldap.sdk.Control; 037import com.unboundid.ldap.sdk.ExtendedRequest; 038import com.unboundid.ldap.sdk.LDAPException; 039import com.unboundid.ldap.sdk.ResultCode; 040import com.unboundid.ldap.sdk.SearchScope; 041import com.unboundid.util.NotMutable; 042import com.unboundid.util.ThreadSafety; 043import com.unboundid.util.ThreadSafetyLevel; 044 045import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*; 046import static com.unboundid.util.Debug.*; 047import static com.unboundid.util.StaticUtils.*; 048import static com.unboundid.util.Validator.*; 049 050 051 052/** 053 * This class provides an implementation of the stream directory values extended 054 * request as used in the Ping Identity, UnboundID, and Alcatel-Lucent 8661 055 * Directory Server. It may be used to obtain all entry DNs and/or all all 056 * values for one or more attributes for a specified portion of the DIT. 057 * <BR> 058 * <BLOCKQUOTE> 059 * <B>NOTE:</B> This class, and other classes within the 060 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 061 * supported for use against Ping Identity, UnboundID, and Alcatel-Lucent 8661 062 * server products. These classes provide support for proprietary 063 * functionality or for external specifications that are not considered stable 064 * or mature enough to be guaranteed to work in an interoperable way with 065 * other types of LDAP servers. 066 * </BLOCKQUOTE> 067 * <BR> 068 * This extended request has an OID of "1.3.6.1.4.1.30221.2.6.6" and the value 069 * is encoded as follows: 070 * <PRE> 071 * StreamDirectoryValuesRequest ::= SEQUENCE { 072 * baseDN [0] LDAPDN, 073 * includeDNs [1] DNSelection OPTIONAL, 074 * attributes [2] SEQUENCE OF LDAPString OPTIONAL, 075 * valuesPerResponse [3] INTEGER (1 .. 32767) OPTIONAL, 076 * ... } 077 * 078 * DNSelection ::= SEQUENCE { 079 * scope [0] ENUMERATED { 080 * baseObject (0), 081 * singleLevel (1), 082 * wholeSubtree (2), 083 * subordinateSubtree (3), 084 * ... } 085 * relative [1] BOOLEAN DEFAULT TRUE, 086 * ..... } 087 * </PRE> 088 */ 089@NotMutable() 090@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 091public final class StreamDirectoryValuesExtendedRequest 092 extends ExtendedRequest 093{ 094 /** 095 * The OID (1.3.6.1.4.1.30221.2.6.6) for the get stream directory values 096 * extended request. 097 */ 098 public static final String STREAM_DIRECTORY_VALUES_REQUEST_OID = 099 "1.3.6.1.4.1.30221.2.6.6"; 100 101 102 103 /** 104 * The BER type for the baseDN element of the stream directory values request 105 * sequence. 106 */ 107 private static final byte TYPE_BASE_DN = (byte) 0x80; 108 109 110 111 /** 112 * The BER type for the includeDNs element of the stream directory values 113 * request sequence. 114 */ 115 private static final byte TYPE_INCLUDE_DNS = (byte) 0xA1; 116 117 118 119 /** 120 * The BER type for the attributes element of the stream directory values 121 * request sequence. 122 */ 123 private static final byte TYPE_ATTRIBUTES = (byte) 0xA2; 124 125 126 127 /** 128 * The BER type for the valuesPerResponse element of the stream directory 129 * values request sequence. 130 */ 131 private static final byte TYPE_VALUES_PER_RESPONSE = (byte) 0x83; 132 133 134 135 /** 136 * The BER type for the scope element of the DNSelection sequence. 137 */ 138 private static final byte TYPE_SCOPE = (byte) 0x80; 139 140 141 142 /** 143 * The BER type for the relative element of the DNSelection sequence. 144 */ 145 private static final byte TYPE_RELATIVE = (byte) 0x81; 146 147 148 149 /** 150 * The serial version UID for this serializable class. 151 */ 152 private static final long serialVersionUID = -6365315263363449596L; 153 154 155 156 // Indicates whether to return DN values that are relative to the base DN. 157 private final boolean returnRelativeDNs; 158 159 // The maximum number of values to include per response. 160 private final int valuesPerResponse; 161 162 // The list of attribute values to be returned. 163 private final List<String> attributes; 164 165 // The search scope to use if DN values are to be included. 166 private final SearchScope dnScope; 167 168 // The base DN for this stream directory values request. 169 private final String baseDN; 170 171 172 173 /** 174 * Creates a new stream directory values extended request with the provided 175 * information. 176 * 177 * @param baseDN The base DN which indicates the portion of the 178 * DIT to target. It must not be {@code null}. 179 * @param dnScope The scope for which to return information about 180 * entry DNs in the specified portion of the DIT. 181 * This may be {@code null} if information about 182 * entry DNs should not be returned. 183 * @param returnRelativeDNs Indicates whether DNs returned should be 184 * relative to the base DN rather than full DNs. 185 * @param attributes The names of the attributes for which to 186 * retrieve the values. This may be {@code null} 187 * or empty if only entry DNs should be retrieved. 188 * @param valuesPerResponse The maximum number of values to include per 189 * response. A value less than or equal to zero 190 * indicates that the server should choose an 191 * appropriate value. 192 * @param controls The set of controls to include in the request. 193 * It may be {@code null} or empty if no controls 194 * should be included in the request. 195 */ 196 public StreamDirectoryValuesExtendedRequest(final String baseDN, 197 final SearchScope dnScope, final boolean returnRelativeDNs, 198 final List<String> attributes, final int valuesPerResponse, 199 final Control... controls) 200 { 201 super(STREAM_DIRECTORY_VALUES_REQUEST_OID, 202 encodeValue(baseDN, dnScope, returnRelativeDNs, attributes, 203 valuesPerResponse), 204 controls); 205 206 this.baseDN = baseDN; 207 this.dnScope = dnScope; 208 this.returnRelativeDNs = returnRelativeDNs; 209 210 if (attributes == null) 211 { 212 this.attributes = Collections.emptyList(); 213 } 214 else 215 { 216 this.attributes = Collections.unmodifiableList(attributes); 217 } 218 219 if (valuesPerResponse < 0) 220 { 221 this.valuesPerResponse = 0; 222 } 223 else 224 { 225 this.valuesPerResponse = valuesPerResponse; 226 } 227 } 228 229 230 231 /** 232 * Creates a new stream directory values extended request from the provided 233 * generic extended request. 234 * 235 * @param extendedRequest The generic extended request to use to create this 236 * stream directory values extended request. 237 * 238 * @throws LDAPException If a problem occurs while decoding the request. 239 */ 240 public StreamDirectoryValuesExtendedRequest( 241 final ExtendedRequest extendedRequest) 242 throws LDAPException 243 { 244 super(extendedRequest); 245 246 final ASN1OctetString value = extendedRequest.getValue(); 247 if (value == null) 248 { 249 throw new LDAPException(ResultCode.DECODING_ERROR, 250 ERR_STREAM_DIRECTORY_VALUES_REQUEST_NO_VALUE.get()); 251 } 252 253 boolean tmpRelative = true; 254 int tmpNumValues = 0; 255 final ArrayList<String> tmpAttrs = new ArrayList<String>(); 256 SearchScope tmpScope = null; 257 String tmpBaseDN = null; 258 259 try 260 { 261 final ASN1Element[] svElements = 262 ASN1Element.decode(value.getValue()).decodeAsSequence().elements(); 263 for (final ASN1Element svElement : svElements) 264 { 265 switch (svElement.getType()) 266 { 267 case TYPE_BASE_DN: 268 tmpBaseDN = svElement.decodeAsOctetString().stringValue(); 269 break; 270 271 case TYPE_INCLUDE_DNS: 272 final ASN1Element[] idElements = 273 svElement.decodeAsSequence().elements(); 274 for (final ASN1Element idElement : idElements) 275 { 276 switch (idElement.getType()) 277 { 278 case TYPE_SCOPE: 279 final int scopeValue = 280 idElement.decodeAsEnumerated().intValue(); 281 tmpScope = SearchScope.definedValueOf(scopeValue); 282 if (tmpScope == null) 283 { 284 throw new LDAPException(ResultCode.DECODING_ERROR, 285 ERR_STREAM_DIRECTORY_VALUES_REQUEST_INVALID_SCOPE.get( 286 scopeValue)); 287 } 288 break; 289 case TYPE_RELATIVE: 290 tmpRelative = 291 idElement.decodeAsBoolean().booleanValue(); 292 break; 293 default: 294 throw new LDAPException(ResultCode.DECODING_ERROR, 295 ERR_STREAM_DIRECTORY_VALUES_REQUEST_INVALID_INCLUDE_DNS_TYPE. 296 get(toHex(idElement.getType()))); 297 } 298 } 299 break; 300 301 case TYPE_ATTRIBUTES: 302 final ASN1Element[] attrElements = 303 svElement.decodeAsSequence().elements(); 304 for (final ASN1Element attrElement : attrElements) 305 { 306 tmpAttrs.add(attrElement.decodeAsOctetString().stringValue()); 307 } 308 break; 309 310 case TYPE_VALUES_PER_RESPONSE: 311 tmpNumValues = svElement.decodeAsInteger().intValue(); 312 if (tmpNumValues < 0) 313 { 314 tmpNumValues = 0; 315 } 316 break; 317 318 default: 319 throw new LDAPException(ResultCode.DECODING_ERROR, 320 ERR_STREAM_DIRECTORY_VALUES_REQUEST_INVALID_SEQUENCE_TYPE.get( 321 toHex(svElement.getType()))); 322 } 323 } 324 } 325 catch (final LDAPException le) 326 { 327 throw le; 328 } 329 catch (final Exception e) 330 { 331 debugException(e); 332 throw new LDAPException(ResultCode.DECODING_ERROR, 333 ERR_STREAM_DIRECTORY_VALUES_REQUEST_CANNOT_DECODE.get( 334 getExceptionMessage(e)), e); 335 } 336 337 if (tmpBaseDN == null) 338 { 339 throw new LDAPException(ResultCode.DECODING_ERROR, 340 ERR_STREAM_DIRECTORY_VALUES_REQUEST_NO_BASE_DN.get()); 341 } 342 343 baseDN = tmpBaseDN; 344 dnScope = tmpScope; 345 returnRelativeDNs = tmpRelative; 346 attributes = Collections.unmodifiableList(tmpAttrs); 347 valuesPerResponse = tmpNumValues; 348 } 349 350 351 352 /** 353 * Encodes the provided information into a form suitable for use as the value 354 * of this extended request. 355 * 356 * @param baseDN The base DN which indicates the portion of the 357 * DIT to target. 358 * @param scope The scope for which to return information about 359 * entry DNs in the specified portion of the DIT. 360 * This may be {@code null} if information about 361 * entry DNs should not be returned. 362 * @param relativeDNs Indicates whether DNs returned should be 363 * relative to the base DN rather than full DNs. 364 * @param attributes The names of the attributes for which to 365 * retrieve the values. This may be {@code null} 366 * or empty if only entry DNs should be retrieved. 367 * @param valuesPerResponse The maximum number of values to include per 368 * response. A value less than or equal to zero 369 * indicates that the server should choose an 370 * appropriate value. 371 * 372 * @return The ASN.1 octet string containing the encoded value to use for 373 * this extended request. 374 */ 375 private static ASN1OctetString encodeValue(final String baseDN, 376 final SearchScope scope, final boolean relativeDNs, 377 final List<String> attributes, final int valuesPerResponse) 378 { 379 ensureNotNull(baseDN); 380 381 final ArrayList<ASN1Element> svElements = new ArrayList<ASN1Element>(4); 382 svElements.add(new ASN1OctetString(TYPE_BASE_DN, baseDN)); 383 384 if (scope != null) 385 { 386 final ArrayList<ASN1Element> idElements = new ArrayList<ASN1Element>(2); 387 idElements.add(new ASN1Enumerated(TYPE_SCOPE, scope.intValue())); 388 389 if (! relativeDNs) 390 { 391 idElements.add(new ASN1Boolean(TYPE_RELATIVE, relativeDNs)); 392 } 393 394 svElements.add(new ASN1Sequence(TYPE_INCLUDE_DNS, idElements)); 395 } 396 397 if ((attributes != null) && (! attributes.isEmpty())) 398 { 399 final ArrayList<ASN1Element> attrElements = 400 new ArrayList<ASN1Element>(attributes.size()); 401 for (final String s : attributes) 402 { 403 attrElements.add(new ASN1OctetString(s)); 404 } 405 svElements.add(new ASN1Sequence(TYPE_ATTRIBUTES, attrElements)); 406 } 407 408 if (valuesPerResponse > 0) 409 { 410 svElements.add(new ASN1Integer(TYPE_VALUES_PER_RESPONSE, 411 valuesPerResponse)); 412 } 413 414 return new ASN1OctetString(new ASN1Sequence(svElements).encode()); 415 } 416 417 418 419 /** 420 * Retrieves the base DN for this request. 421 * 422 * @return The base DN for this request. 423 */ 424 public String getBaseDN() 425 { 426 return baseDN; 427 } 428 429 430 431 /** 432 * Retrieves the scope for entry DNs to be included in intermediate responses. 433 * 434 * @return The scope for entry DNs to be included in intermediate responses, 435 * or {@code null} if information about entry DNs should not be 436 * returned. 437 */ 438 public SearchScope getDNScope() 439 { 440 return dnScope; 441 } 442 443 444 445 /** 446 * Indicates whether entry DN values returned should be relative to the 447 * provided base DN. 448 * 449 * @return {@code true} if entry DN values returned should be relative to the 450 * provided base DN, or {@code false} if they should be complete DNs. 451 */ 452 public boolean returnRelativeDNs() 453 { 454 return returnRelativeDNs; 455 } 456 457 458 459 /** 460 * Retrieves the list of names of attributes whose values should be returned 461 * to the client. 462 * 463 * @return The list of names of attributes whose values should be returned to 464 * the client, or an empty list if only information about entry DNs 465 * should be returned. 466 */ 467 public List<String> getAttributes() 468 { 469 return attributes; 470 } 471 472 473 474 /** 475 * Retrieves the maximum number of values that should be included in each 476 * stream directory values intermediate response. 477 * 478 * @return The maximum number of values that should be included in each 479 * stream directory values intermediate response, or 0 if the server 480 * should choose the appropriate number of values per response. 481 */ 482 public int getValuesPerResponse() 483 { 484 return valuesPerResponse; 485 } 486 487 488 489 /** 490 * {@inheritDoc} 491 */ 492 @Override() 493 public StreamDirectoryValuesExtendedRequest duplicate() 494 { 495 return duplicate(getControls()); 496 } 497 498 499 500 /** 501 * {@inheritDoc} 502 */ 503 @Override() 504 public StreamDirectoryValuesExtendedRequest duplicate( 505 final Control[] controls) 506 { 507 final StreamDirectoryValuesExtendedRequest r = 508 new StreamDirectoryValuesExtendedRequest(baseDN, dnScope, 509 returnRelativeDNs, attributes, valuesPerResponse, controls); 510 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 511 return r; 512 } 513 514 515 516 /** 517 * {@inheritDoc} 518 */ 519 @Override() 520 public String getExtendedRequestName() 521 { 522 return INFO_EXTENDED_REQUEST_NAME_STREAM_DIRECTORY_VALUES.get(); 523 } 524 525 526 527 /** 528 * {@inheritDoc} 529 */ 530 @Override() 531 public void toString(final StringBuilder buffer) 532 { 533 buffer.append("StreamDirectoryValuesExtendedRequest(baseDN='"); 534 buffer.append(baseDN); 535 buffer.append('\''); 536 537 if (dnScope != null) 538 { 539 buffer.append(", scope='"); 540 buffer.append(dnScope.getName()); 541 buffer.append("', returnRelativeDNs="); 542 buffer.append(returnRelativeDNs); 543 } 544 545 buffer.append(", attributes={"); 546 if (! attributes.isEmpty()) 547 { 548 final Iterator<String> iterator = attributes.iterator(); 549 while (iterator.hasNext()) 550 { 551 buffer.append('\''); 552 buffer.append(iterator.next()); 553 buffer.append('\''); 554 555 if (iterator.hasNext()) 556 { 557 buffer.append(", "); 558 } 559 } 560 } 561 buffer.append('}'); 562 563 if (valuesPerResponse > 0) 564 { 565 buffer.append(", valuesPerResponse="); 566 buffer.append(valuesPerResponse); 567 } 568 569 final Control[] controls = getControls(); 570 if (controls.length > 0) 571 { 572 buffer.append(", controls={"); 573 for (int i=0; i < controls.length; i++) 574 { 575 if (i > 0) 576 { 577 buffer.append(", "); 578 } 579 580 buffer.append(controls[i]); 581 } 582 buffer.append('}'); 583 } 584 585 buffer.append(')'); 586 } 587}