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