001/* 002 * Copyright 2009-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2009-2024 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) 2009-2024 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.io.Serializable; 041import java.util.ArrayList; 042 043import com.unboundid.asn1.ASN1Boolean; 044import com.unboundid.asn1.ASN1Element; 045import com.unboundid.asn1.ASN1Enumerated; 046import com.unboundid.asn1.ASN1Integer; 047import com.unboundid.asn1.ASN1OctetString; 048import com.unboundid.asn1.ASN1Sequence; 049import com.unboundid.ldap.sdk.DereferencePolicy; 050import com.unboundid.ldap.sdk.Filter; 051import com.unboundid.ldap.sdk.LDAPException; 052import com.unboundid.ldap.sdk.ResultCode; 053import com.unboundid.ldap.sdk.SearchScope; 054import com.unboundid.util.Debug; 055import com.unboundid.util.NotMutable; 056import com.unboundid.util.NotNull; 057import com.unboundid.util.Nullable; 058import com.unboundid.util.StaticUtils; 059import com.unboundid.util.ThreadSafety; 060import com.unboundid.util.ThreadSafetyLevel; 061import com.unboundid.util.Validator; 062 063import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 064 065 066 067/** 068 * This class contains a data structure which provides information about the 069 * value of an LDAP join request control, which may or may not include a nested 070 * join. See the class-level documentation for the {@link JoinRequestControl} 071 * class for additional information and an example demonstrating its use. 072 * <BR> 073 * <BLOCKQUOTE> 074 * <B>NOTE:</B> This class, and other classes within the 075 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 076 * supported for use against Ping Identity, UnboundID, and 077 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 078 * for proprietary functionality or for external specifications that are not 079 * considered stable or mature enough to be guaranteed to work in an 080 * interoperable way with other types of LDAP servers. 081 * </BLOCKQUOTE> 082 * <BR> 083 * The value of the join request control is encoded as follows: 084 * <PRE> 085 * LDAPJoin ::= SEQUENCE { 086 * joinRule JoinRule, 087 * baseObject CHOICE { 088 * useSearchBaseDN [0] NULL, 089 * useSourceEntryDN [1] NULL, 090 * useCustomBaseDN [2] LDAPDN, 091 * ... }, 092 * scope [0] ENUMERATED { 093 * baseObject (0), 094 * singleLevel (1), 095 * wholeSubtree (2), 096 * subordinateSubtree (3), 097 * ... } OPTIONAL, 098 * derefAliases [1] ENUMERATED { 099 * neverDerefAliases (0), 100 * derefInSearching (1), 101 * derefFindingBaseObj (2), 102 * derefAlways (3), 103 * ... } OPTIONAL, 104 * sizeLimit [2] INTEGER (0 .. maxInt) OPTIONAL, 105 * filter [3] Filter OPTIONAL, 106 * attributes [4] AttributeSelection OPTIONAL, 107 * requireMatch [5] BOOLEAN DEFAULT FALSE, 108 * nestedJoin [6] LDAPJoin OPTIONAL, 109 * ... } 110 * </PRE> 111 */ 112@NotMutable() 113@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 114public final class JoinRequestValue 115 implements Serializable 116{ 117 /** 118 * The set of attributes that will be used if all user attributes should be 119 * requested. 120 */ 121 @NotNull private static final String[] NO_ATTRIBUTES = StaticUtils.NO_STRINGS; 122 123 124 125 /** 126 * The BER type to use for the scope element. 127 */ 128 private static final byte TYPE_SCOPE = (byte) 0x80; 129 130 131 132 /** 133 * The BER type to use for the dereference policy element. 134 */ 135 private static final byte TYPE_DEREF_POLICY = (byte) 0x81; 136 137 138 139 /** 140 * The BER type to use for the size limit element. 141 */ 142 private static final byte TYPE_SIZE_LIMIT = (byte) 0x82; 143 144 145 146 /** 147 * The BER type to use for the filter element. 148 */ 149 private static final byte TYPE_FILTER = (byte) 0xA3; 150 151 152 153 /** 154 * The BER type to use for the attributes element. 155 */ 156 private static final byte TYPE_ATTRIBUTES = (byte) 0xA4; 157 158 159 160 /** 161 * The BER type to use for the require match element. 162 */ 163 private static final byte TYPE_REQUIRE_MATCH = (byte) 0x85; 164 165 166 167 /** 168 * The BER type to use for the nested join element. 169 */ 170 private static final byte TYPE_NESTED_JOIN = (byte) 0xA6; 171 172 173 174 /** 175 * The serial version UID for this serializable class. 176 */ 177 private static final long serialVersionUID = 4675881185117657177L; 178 179 180 181 // Indicates whether to require at least one entry to match the join 182 // criteria for the entry to be returned. 183 private final boolean requireMatch; 184 185 // The dereference policy for this join request value. 186 @Nullable private final DereferencePolicy derefPolicy; 187 188 // The filter for this join request value. 189 @Nullable private final Filter filter; 190 191 // The client-requested size limit for this join request value. 192 @Nullable private final Integer sizeLimit; 193 194 // The base DN to use for this join request value. 195 @NotNull private final JoinBaseDN baseDN; 196 197 // The nested join criteria for this join request value. 198 @Nullable private final JoinRequestValue nestedJoin; 199 200 // The join rule for this join request value. 201 @NotNull private final JoinRule joinRule; 202 203 // The scope for this join request value. 204 @Nullable private final SearchScope scope; 205 206 // The set of attributes to include in entries matching the join criteria. 207 @NotNull private final String[] attributes; 208 209 210 211 /** 212 * Creates a new join request value with the provided information. 213 * 214 * @param joinRule The join rule for this join request value. It must 215 * not be {@code null}. 216 * @param baseDN The base DN for this join request value. It must 217 * not be {@code null}. 218 * @param scope The scope for this join request value. It may be 219 * {@code null} if the scope from the associated search 220 * request should be used. 221 * @param derefPolicy The alias dereferencing policy for this join request 222 * value. It may be {@code null} if the dereference 223 * policy from the associated search request should be 224 * used. 225 * @param sizeLimit The client-requested maximum number of entries to 226 * allow when performing the join. It may be 227 * {@code null} if the size limit from the associated 228 * search request should be used. Note that the server 229 * will impose a maximum size limit of 1000 entries, so 230 * size limit values greater than 1000 will be limited 231 * to 1000. 232 * @param filter An additional filter which must match target entries 233 * for them to be included in the join. This may be 234 * {@code null} if no additional filter is required and 235 * the join rule should be the only criteria used when 236 * performing the join. 237 * @param attributes The set of attributes that the client wishes to be 238 * included in joined entries. It may be {@code null} 239 * or empty to indicate that all user attributes should 240 * be included. It may also contain special values like 241 * "1.1" to indicate that no attributes should be 242 * included, "*" to indicate that all user attributes 243 * should be included, "+" to indicate that all 244 * operational attributes should be included, or 245 * "@ocname" to indicate that all required and optional 246 * attributes associated with the "ocname" object class 247 * should be included. 248 * @param requireMatch Indicates whether a search result entry is required 249 * to be joined with at least one entry for it to be 250 * returned to the client. 251 * @param nestedJoin A set of join criteria that should be applied to 252 * entries joined with this join request value. It may 253 * be {@code null} if no nested join is needed. 254 */ 255 public JoinRequestValue(@NotNull final JoinRule joinRule, 256 @NotNull final JoinBaseDN baseDN, 257 @Nullable final SearchScope scope, 258 @Nullable final DereferencePolicy derefPolicy, 259 @Nullable final Integer sizeLimit, 260 @Nullable final Filter filter, 261 @Nullable final String[] attributes, 262 final boolean requireMatch, 263 @Nullable final JoinRequestValue nestedJoin) 264 { 265 Validator.ensureNotNull(joinRule, baseDN); 266 267 this.joinRule = joinRule; 268 this.baseDN = baseDN; 269 this.scope = scope; 270 this.derefPolicy = derefPolicy; 271 this.sizeLimit = sizeLimit; 272 this.filter = filter; 273 this.requireMatch = requireMatch; 274 this.nestedJoin = nestedJoin; 275 276 if (attributes == null) 277 { 278 this.attributes = NO_ATTRIBUTES; 279 } 280 else 281 { 282 this.attributes = attributes; 283 } 284 } 285 286 287 288 /** 289 * Retrieves the join rule for this join request value. 290 * 291 * @return The join rule for this join request value. 292 */ 293 @NotNull() 294 public JoinRule getJoinRule() 295 { 296 return joinRule; 297 } 298 299 300 301 /** 302 * Retrieves the join base DN for this join request value. 303 * 304 * @return The join base DN for this join request value. 305 */ 306 @NotNull() 307 public JoinBaseDN getBaseDN() 308 { 309 return baseDN; 310 } 311 312 313 314 /** 315 * Retrieves the scope for this join request value. 316 * 317 * @return The scope for this join request value, or {@code null} if the 318 * scope from the associated search request should be used. 319 */ 320 @Nullable() 321 public SearchScope getScope() 322 { 323 return scope; 324 } 325 326 327 328 /** 329 * Retrieves the alias dereferencing policy for this join request value. 330 * 331 * @return The alias dereferencing policy for this join request value, or 332 * {@code null} if the policy from the associated search request 333 * should be used. 334 */ 335 @Nullable() 336 public DereferencePolicy getDerefPolicy() 337 { 338 return derefPolicy; 339 } 340 341 342 343 /** 344 * Retrieves the client-requested size limit for this join request value. 345 * Note that the server will impose a maximum size limit of 1000 entries, so 346 * if the client-requested size limit is greater than 1000, the server will 347 * limit it to 1000 entries. 348 * 349 * @return The size limit for this join request value, or {@code null} if the 350 * size limit from the associated search request should be used. 351 */ 352 @Nullable() 353 public Integer getSizeLimit() 354 { 355 return sizeLimit; 356 } 357 358 359 360 /** 361 * Retrieves a filter with additional criteria that must match a target entry 362 * for it to be joined with a search result entry. 363 * 364 * @return A filter with additional criteria that must match a target entry 365 * for it to be joined with a search result entry, or {@code null} if 366 * no additional filter is needed. 367 */ 368 @Nullable() 369 public Filter getFilter() 370 { 371 return filter; 372 } 373 374 375 376 /** 377 * Retrieves the set of requested attributes that should be included in 378 * joined entries. 379 * 380 * @return The set of requested attributes that should be included in joined 381 * entries, or an empty array if all user attributes should be 382 * requested. 383 */ 384 @NotNull() 385 public String[] getAttributes() 386 { 387 return attributes; 388 } 389 390 391 392 /** 393 * Indicates whether a search result entry will be required to be joined with 394 * at least one entry for that entry to be returned to the client. 395 * 396 * @return {@code true} if a search result entry must be joined with at least 397 * one other entry for it to be returned to the client, or 398 * {@code false} if a search result entry may be returned even if it 399 * is not joined with any other entries. 400 */ 401 public boolean requireMatch() 402 { 403 return requireMatch; 404 } 405 406 407 408 /** 409 * Retrieves the nested join for this join request value, if defined. 410 * 411 * @return The nested join for this join request value, or {@code null} if 412 * there is no nested join for this join request value. 413 */ 414 @Nullable() 415 public JoinRequestValue getNestedJoin() 416 { 417 return nestedJoin; 418 } 419 420 421 422 /** 423 * Encodes this join request value as appropriate for inclusion in the join 424 * request control. 425 * 426 * @return The ASN.1 element containing the encoded join request value. 427 */ 428 @NotNull() 429 ASN1Element encode() 430 { 431 final ArrayList<ASN1Element> elements = new ArrayList<>(9); 432 433 elements.add(joinRule.encode()); 434 elements.add(baseDN.encode()); 435 436 if (scope != null) 437 { 438 elements.add(new ASN1Enumerated(TYPE_SCOPE, scope.intValue())); 439 } 440 441 if (derefPolicy != null) 442 { 443 elements.add(new ASN1Enumerated(TYPE_DEREF_POLICY, 444 derefPolicy.intValue())); 445 } 446 447 if (sizeLimit != null) 448 { 449 elements.add(new ASN1Integer(TYPE_SIZE_LIMIT, sizeLimit)); 450 } 451 452 if (filter != null) 453 { 454 elements.add(new ASN1OctetString(TYPE_FILTER, filter.encode().encode())); 455 } 456 457 if ((attributes != null) && (attributes.length > 0)) 458 { 459 final ASN1Element[] attrElements = new ASN1Element[attributes.length]; 460 for (int i=0; i < attributes.length; i++) 461 { 462 attrElements[i] = new ASN1OctetString(attributes[i]); 463 } 464 elements.add(new ASN1Sequence(TYPE_ATTRIBUTES, attrElements)); 465 } 466 467 if (requireMatch) 468 { 469 elements.add(new ASN1Boolean(TYPE_REQUIRE_MATCH, requireMatch)); 470 } 471 472 if (nestedJoin != null) 473 { 474 elements.add(new ASN1OctetString(TYPE_NESTED_JOIN, 475 nestedJoin.encode().getValue())); 476 } 477 478 return new ASN1Sequence(elements); 479 } 480 481 482 483 /** 484 * Decodes the provided ASN.1 element as a join request value. 485 * 486 * @param element The element to be decoded. 487 * 488 * @return The decoded join request value. 489 * 490 * @throws LDAPException If the provided ASN.1 element cannot be decoded as 491 * a join request value. 492 */ 493 @NotNull() 494 static JoinRequestValue decode(@NotNull final ASN1Element element) 495 throws LDAPException 496 { 497 try 498 { 499 final ASN1Element[] elements = 500 ASN1Sequence.decodeAsSequence(element).elements(); 501 final JoinRule joinRule = JoinRule.decode(elements[0]); 502 final JoinBaseDN baseDN = JoinBaseDN.decode(elements[1]); 503 504 SearchScope scope = null; 505 DereferencePolicy derefPolicy = null; 506 Integer sizeLimit = null; 507 Filter filter = null; 508 String[] attributes = NO_ATTRIBUTES; 509 boolean requireMatch = false; 510 JoinRequestValue nestedJoin = null; 511 512 for (int i=2; i < elements.length; i++) 513 { 514 switch (elements[i].getType()) 515 { 516 case TYPE_SCOPE: 517 scope = SearchScope.valueOf( 518 ASN1Enumerated.decodeAsEnumerated(elements[i]).intValue()); 519 break; 520 521 case TYPE_DEREF_POLICY: 522 derefPolicy = DereferencePolicy.valueOf( 523 ASN1Enumerated.decodeAsEnumerated(elements[i]).intValue()); 524 break; 525 526 case TYPE_SIZE_LIMIT: 527 sizeLimit = ASN1Integer.decodeAsInteger(elements[i]).intValue(); 528 break; 529 530 case TYPE_FILTER: 531 filter = Filter.decode(ASN1Element.decode(elements[i].getValue())); 532 break; 533 534 case TYPE_ATTRIBUTES: 535 final ASN1Element[] attrElements = 536 ASN1Sequence.decodeAsSequence(elements[i]).elements(); 537 final ArrayList<String> attrList = 538 new ArrayList<>(attrElements.length); 539 for (final ASN1Element e : attrElements) 540 { 541 attrList.add( 542 ASN1OctetString.decodeAsOctetString(e).stringValue()); 543 } 544 545 attributes = new String[attrList.size()]; 546 attrList.toArray(attributes); 547 break; 548 549 case TYPE_REQUIRE_MATCH: 550 requireMatch = 551 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue(); 552 break; 553 554 case TYPE_NESTED_JOIN: 555 nestedJoin = decode(elements[i]); 556 break; 557 558 default: 559 throw new LDAPException(ResultCode.DECODING_ERROR, 560 ERR_JOIN_REQUEST_VALUE_INVALID_ELEMENT_TYPE.get( 561 elements[i].getType())); 562 } 563 } 564 565 return new JoinRequestValue(joinRule, baseDN, scope, derefPolicy, 566 sizeLimit, filter, attributes, requireMatch, nestedJoin); 567 } 568 catch (final Exception e) 569 { 570 Debug.debugException(e); 571 572 throw new LDAPException(ResultCode.DECODING_ERROR, 573 ERR_JOIN_REQUEST_VALUE_CANNOT_DECODE.get( 574 StaticUtils.getExceptionMessage(e)), 575 e); 576 } 577 } 578 579 580 581 /** 582 * Retrieves a string representation of this join request value. 583 * 584 * @return A string representation of this join request value. 585 */ 586 @Override() 587 @NotNull() 588 public String toString() 589 { 590 final StringBuilder buffer = new StringBuilder(); 591 toString(buffer); 592 return buffer.toString(); 593 } 594 595 596 597 /** 598 * Appends a string representation of this join request value to the provided 599 * buffer. 600 * 601 * @param buffer The buffer to which the information should be appended. 602 */ 603 public void toString(@NotNull final StringBuilder buffer) 604 { 605 buffer.append("JoinRequestValue(joinRule="); 606 joinRule.toString(buffer); 607 buffer.append(", baseDN="); 608 baseDN.toString(buffer); 609 buffer.append(", scope="); 610 buffer.append(String.valueOf(scope)); 611 buffer.append(", derefPolicy="); 612 buffer.append(String.valueOf(derefPolicy)); 613 buffer.append(", sizeLimit="); 614 buffer.append(sizeLimit); 615 buffer.append(", filter="); 616 617 if (filter == null) 618 { 619 buffer.append("null"); 620 } 621 else 622 { 623 buffer.append('\''); 624 filter.toString(buffer); 625 buffer.append('\''); 626 } 627 628 buffer.append(", attributes={"); 629 630 for (int i=0; i < attributes.length; i++) 631 { 632 if (i > 0) 633 { 634 buffer.append(", "); 635 } 636 buffer.append(attributes[i]); 637 } 638 639 buffer.append("}, requireMatch="); 640 buffer.append(requireMatch); 641 buffer.append(", nestedJoin="); 642 643 if (nestedJoin == null) 644 { 645 buffer.append("null"); 646 } 647 else 648 { 649 nestedJoin.toString(buffer); 650 } 651 652 buffer.append(')'); 653 } 654}