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.util.ArrayList; 041import java.util.LinkedHashMap; 042import java.util.List; 043import java.util.Map; 044 045import com.unboundid.asn1.ASN1Element; 046import com.unboundid.asn1.ASN1OctetString; 047import com.unboundid.ldap.sdk.Control; 048import com.unboundid.ldap.sdk.DereferencePolicy; 049import com.unboundid.ldap.sdk.Filter; 050import com.unboundid.ldap.sdk.JSONControlDecodeHelper; 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.StaticUtils; 058import com.unboundid.util.ThreadSafety; 059import com.unboundid.util.ThreadSafetyLevel; 060import com.unboundid.util.json.JSONArray; 061import com.unboundid.util.json.JSONBoolean; 062import com.unboundid.util.json.JSONField; 063import com.unboundid.util.json.JSONNumber; 064import com.unboundid.util.json.JSONObject; 065import com.unboundid.util.json.JSONString; 066import com.unboundid.util.json.JSONValue; 067 068import static com.unboundid.ldap.sdk.unboundidds.controls.ControlMessages.*; 069 070 071 072/** 073 * This class provides an implementation of an LDAP control which can be 074 * included in a search request to indicate that search result entries should be 075 * returned along with related entries based on a given set of criteria, much 076 * like an SQL join in a relational database. 077 * <BR> 078 * <BLOCKQUOTE> 079 * <B>NOTE:</B> This class, and other classes within the 080 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 081 * supported for use against Ping Identity, UnboundID, and 082 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 083 * for proprietary functionality or for external specifications that are not 084 * considered stable or mature enough to be guaranteed to work in an 085 * interoperable way with other types of LDAP servers. 086 * </BLOCKQUOTE> 087 * <BR> 088 * This request control has an OID of 1.3.6.1.4.1.30221.2.5.9, and the 089 * criticality is generally true. It must have a value, and the format of that 090 * value is described in the class-level documentation for the 091 * {@link JoinRequestValue} class. 092 * <BR> 093 * <H2>Example</H2> 094 * Consider the case in which user entries include an account number, but 095 * additional information about those accounts are available in separate 096 * entries. If you wish to retrieve both the user and account entries for a 097 * user given only a user ID, then you may accomplish that using the join 098 * request control as follows: 099 * <PRE> 100 * SearchRequest searchRequest = new SearchRequest( 101 * "ou=People,dc=example,dc=com", SearchScope.SUB, 102 * Filter.createEqualityFilter("uid", userID)); 103 * searchRequest.addControl(new JoinRequestControl(new JoinRequestValue( 104 * JoinRule.createEqualityJoin("accountNumber", "accountNumber", false), 105 * JoinBaseDN.createUseCustomBaseDN("ou=Accounts,dc=example,dc=com"), 106 * SearchScope.SUB, DereferencePolicy.NEVER, null, 107 * Filter.createEqualityFilter("objectClass", "accountEntry"), 108 * new String[0], false, null))); 109 * SearchResult searchResult = connection.search(searchRequest); 110 * 111 * for (SearchResultEntry userEntry : searchResult.getSearchEntries()) 112 * { 113 * JoinResultControl c = JoinResultControl.get(userEntry); 114 * for (JoinedEntry accountEntry : c.getJoinResults()) 115 * { 116 * // User userEntry was joined with account accountEntry 117 * } 118 * } 119 * </PRE> 120 */ 121@NotMutable() 122@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 123public final class JoinRequestControl 124 extends Control 125{ 126 /** 127 * The OID (1.3.6.1.4.1.30221.2.5.9) for the join request control. 128 */ 129 @NotNull public static final String JOIN_REQUEST_OID = 130 "1.3.6.1.4.1.30221.2.5.9"; 131 132 133 134 /** 135 * The name of the field used to hold the alias dereferencing behavior in the 136 * JSON representation of this control. 137 */ 138 @NotNull private static final String JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR = 139 "alias-dereferencing-behavior"; 140 141 142 143 /** 144 * The name of the field used to hold the requested attributes in the JSON 145 * representation of this control. 146 */ 147 @NotNull private static final String JSON_FIELD_ATTRIBUTES = "attributes"; 148 149 150 151 /** 152 * The name of the field used to hold the base DN type in the JSON 153 * representation of this control. 154 */ 155 @NotNull private static final String JSON_FIELD_BASE_DN_TYPE = "base-dn-type"; 156 157 158 159 /** 160 * The name of the field used to hold the base DN value in the JSON 161 * representation of this control. 162 */ 163 @NotNull private static final String JSON_FIELD_BASE_DN_VALUE = 164 "base-dn-value"; 165 166 167 168 /** 169 * The name of the field used to hold the filter in the JSON representation 170 * of this control. 171 */ 172 @NotNull private static final String JSON_FIELD_FILTER = "filter"; 173 174 175 176 /** 177 * The name of the field used to hold the join rule in the JSON representation 178 * of this control. 179 */ 180 @NotNull private static final String JSON_FIELD_JOIN_RULE = "join-rule"; 181 182 183 184 /** 185 * The name of the field used to hold a nested join value in the JSON 186 * representation of this control. 187 */ 188 @NotNull private static final String JSON_FIELD_NESTED_JOIN = "nested-join"; 189 190 191 192 /** 193 * The name of the field used to hold the require-match flag in the JSON 194 * representation of this control. 195 */ 196 @NotNull private static final String JSON_FIELD_REQUIRE_MATCH = 197 "require-match"; 198 199 200 201 /** 202 * The name of the field used to hold the scope in the JSON representation of 203 * this control. 204 */ 205 @NotNull private static final String JSON_FIELD_SCOPE = "scope"; 206 207 208 209 /** 210 * The name of the field used to hold the size limit in the JSON 211 * representation of this control. 212 */ 213 @NotNull private static final String JSON_FIELD_SIZE_LIMIT = "size-limit"; 214 215 216 217 /** 218 * The neverDerefAliases alias dereferencing behavior that will be used in the 219 * JSON representation of this control. 220 */ 221 @NotNull private static final String JSON_ALIAS_BEHAVIOR_ALWAYS = 222 "derefAlways"; 223 224 225 226 /** 227 * The neverDerefAliases alias dereferencing behavior that will be used in the 228 * JSON representation of this control. 229 */ 230 @NotNull private static final String JSON_ALIAS_BEHAVIOR_FINDING = 231 "derefInFindingBaseObj"; 232 233 234 235 /** 236 * The neverDerefAliases alias dereferencing behavior that will be used in the 237 * JSON representation of this control. 238 */ 239 @NotNull private static final String JSON_ALIAS_BEHAVIOR_NEVER = 240 "neverDerefAliases"; 241 242 243 244 /** 245 * The neverDerefAliases alias dereferencing behavior that will be used in the 246 * JSON representation of this control. 247 */ 248 @NotNull private static final String JSON_ALIAS_BEHAVIOR_SEARCHING = 249 "derefInSearching"; 250 251 252 253 /** 254 * The base DN type value that will indicate that a custom base DN should be 255 * used as the join base DN in the JSON representation of this control. 256 */ 257 @NotNull private static final String JSON_BASE_DN_TYPE_USE_CUSTOM_BASE_DN = 258 "use-custom-base-dn"; 259 260 261 262 /** 263 * The base DN type value that will indicate that the search base DN should 264 * be used as the join base DN in the JSON representation of this control. 265 */ 266 @NotNull private static final String JSON_BASE_DN_TYPE_USE_SEARCH_BASE_DN = 267 "use-search-base-dn"; 268 269 270 271 /** 272 * The base DN type value that will indicate that the source entry DN should 273 * be used as the join base DN in the JSON representation of this control. 274 */ 275 @NotNull private static final String JSON_BASE_DN_TYPE_USE_SOURCE_ENTRY_DN = 276 "use-source-entry-dn"; 277 278 279 280 /** 281 * The baseObject scope value that will be used in the JSON representation of 282 * this control. 283 */ 284 @NotNull private static final String JSON_SCOPE_BASE_OBJECT = "baseObject"; 285 286 287 288 /** 289 * The singleLevel scope value that will be used in the JSON representation of 290 * this control. 291 */ 292 @NotNull private static final String JSON_SCOPE_SINGLE_LEVEL = "singleLevel"; 293 294 295 296 /** 297 * The subordinateSubtree scope value that will be used in the JSON 298 * representation of this control. 299 */ 300 @NotNull private static final String JSON_SCOPE_SUBORDINATE_SUBTREE = 301 "subordinateSubtree"; 302 303 304 305 /** 306 * The wholeSubtree scope value that will be used in the JSON representation 307 * of this control. 308 */ 309 @NotNull private static final String JSON_SCOPE_WHOLE_SUBTREE = 310 "wholeSubtree"; 311 312 313 314 /** 315 * The serial version UID for this serializable class. 316 */ 317 private static final long serialVersionUID = -1321645105838145996L; 318 319 320 321 // The join request value for this control. 322 @NotNull private final JoinRequestValue joinRequestValue; 323 324 325 326 /** 327 * Creates a new join request control with the provided join request value. 328 * 329 * @param joinRequestValue The join request value to use for this control. 330 */ 331 public JoinRequestControl(@NotNull final JoinRequestValue joinRequestValue) 332 { 333 this(true, joinRequestValue); 334 } 335 336 337 338 /** 339 * Creates a new join request control with the provided join request value. 340 * 341 * @param isCritical Indicates whether this control should be 342 * considered critical. 343 * @param joinRequestValue The join request value to use for this control. 344 */ 345 public JoinRequestControl(final boolean isCritical, 346 @NotNull final JoinRequestValue joinRequestValue) 347 { 348 super(JOIN_REQUEST_OID, isCritical, 349 new ASN1OctetString(joinRequestValue.encode().encode())); 350 351 this.joinRequestValue = joinRequestValue; 352 } 353 354 355 356 /** 357 * Creates a new join request control which is decoded from the provided 358 * generic control. 359 * 360 * @param control The generic control to be decoded as a join request 361 * control. 362 * 363 * @throws LDAPException If the provided control cannot be decoded as a 364 * virtual attributes only request control. 365 */ 366 public JoinRequestControl(@NotNull final Control control) 367 throws LDAPException 368 { 369 super(control); 370 371 final ASN1OctetString value = control.getValue(); 372 if (value == null) 373 { 374 throw new LDAPException(ResultCode.DECODING_ERROR, 375 ERR_JOIN_REQUEST_CONTROL_NO_VALUE.get()); 376 } 377 378 final ASN1Element valueElement; 379 try 380 { 381 valueElement = ASN1Element.decode(value.getValue()); 382 } 383 catch (final Exception e) 384 { 385 Debug.debugException(e); 386 387 throw new LDAPException(ResultCode.DECODING_ERROR, 388 ERR_JOIN_REQUEST_VALUE_CANNOT_DECODE.get( 389 StaticUtils.getExceptionMessage(e)), 390 e); 391 } 392 393 joinRequestValue = JoinRequestValue.decode(valueElement); 394 } 395 396 397 398 /** 399 * Retrieves the join request value for this join request control. 400 * 401 * @return The join request value for this join request control. 402 */ 403 @NotNull() 404 public JoinRequestValue getJoinRequestValue() 405 { 406 return joinRequestValue; 407 } 408 409 410 411 /** 412 * {@inheritDoc} 413 */ 414 @Override() 415 @NotNull() 416 public String getControlName() 417 { 418 return INFO_CONTROL_NAME_JOIN_REQUEST.get(); 419 } 420 421 422 423 /** 424 * Retrieves a representation of this join request control as a JSON object. 425 * The JSON object uses the following fields: 426 * <UL> 427 * <LI> 428 * {@code oid} -- A mandatory string field whose value is the object 429 * identifier for this control. For the join request control, the OID is 430 * "1.3.6.1.4.1.30221.2.5.9". 431 * </LI> 432 * <LI> 433 * {@code control-name} -- An optional string field whose value is a 434 * human-readable name for this control. This field is only intended for 435 * descriptive purposes, and when decoding a control, the {@code oid} 436 * field should be used to identify the type of control. 437 * </LI> 438 * <LI> 439 * {@code criticality} -- A mandatory Boolean field used to indicate 440 * whether this control is considered critical. 441 * </LI> 442 * <LI> 443 * {@code value-base64} -- An optional string field whose value is a 444 * base64-encoded representation of the raw value for this join request 445 * control. Exactly one of the {@code value-base64} and 446 * {@code value-json} fields must be present. 447 * </LI> 448 * <LI> 449 * {@code value-json} -- An optional JSON object field whose value is a 450 * user-friendly representation of the value for this join request 451 * control. Exactly one of the {@code value-base64} and 452 * {@code value-json} fields must be present, and if the 453 * {@code value-json} field is used, then it will use the following 454 * fields: 455 * <UL> 456 * <LI> 457 * {@code join-rule} -- A mandatory JSON object field that provides 458 * the primary criteria to use when selecting entries to be joined 459 * with search result entries. The format for join rule objects will 460 * be described in more detail below. 461 * </LI> 462 * <LI> 463 * {@code base-dn-type} -- A mandatory string field that indicates 464 * who the server should determine the base DN to use for join 465 * processing. The value must be one of "{@code use-search-base-dn}", 466 * "{@code use-source-entry-dn}", or "{@code use-custom-base-dn}". 467 * </LI> 468 * <LI> 469 * {@code base-dn-value} -- An optional string field that provides the 470 * custom base DN value to use if the {@code base-dn-type} value was 471 * "{@code use-custom-base-dn}". This field must be present if the 472 * {@code base-dn-type} value was "{@code use-custom-base-dn}", and it 473 * must be absent for other {@code base-dn-type} values. 474 * </LI> 475 * <LI> 476 * {@code scope} -- An optional string field whose value specifies the 477 * scope to use for join processing. If present, the value must be 478 * one of "{@code baseObject}", "{@code singleLevel}", 479 * "{@code wholeSubtree}", or "{@code subordinateSubtree}". If this 480 * is not specified, the scope from the search request will be used. 481 * </LI> 482 * <LI> 483 * {@code alias-dereferencing-behavior} -- An optional string field 484 * whose value specifies the behavior to use for dereferencing any 485 * aliases encountered during join processing. If present, the value 486 * must be one of "{@code neverDerefAliases}", 487 * "{@code derefInSearching}", "{@code derefInFindingBaseObj}", or 488 * {@code derefAlways}". If this is not specified, the dereferencing 489 * behavior from the search request will be used. 490 * </LI> 491 * <LI> 492 * {@code size-limit} -- An optional integer field whose value 493 * specifies the maximum number of entries that may be joined with any 494 * single search 495 * result entry. 496 * </LI> 497 * <LI> 498 * {@code filter} -- An optional string field whose value is the 499 * string representation of a filter that will be required to match an 500 * entry for it to be joined with a search result entry. 501 * </LI> 502 * <LI> 503 * {@code attributes} -- An optional array field whose values are 504 * strings that are the names of attributes to include in joined 505 * entries. 506 * </LI> 507 * <LI> 508 * {@code require-match} -- A mandatory Boolean field that indicates 509 * whether to suppress a search result entry from the set of results 510 * to the client if it is not joined with any other entries. 511 * </LI> 512 * <LI> 513 * {@code nested-join} -- An optional JSON object field whose value 514 * represents the join criteria for a nested join operation. If 515 * present, the fields in this object are the same as the fields that 516 * may be used in the top-level {@code value-json} field (optionally 517 * including another {@code nested-jon} element if desired). 518 * </LI> 519 * </UL> 520 * </LI> 521 * </UL> 522 * <BR><BR> 523 * The following encodings may be used for different types of join rules: 524 * <UL> 525 * <LI> 526 * The fields that may be used for a DN join include: 527 * <UL> 528 * <LI> 529 * {@code type} -- A mandatory string field whose value must be 530 * "{@code dn}". 531 * </LI> 532 * <LI> 533 * {@code source-attribute} -- A mandatory string field whose value is 534 * the name of the attribute in the source entry that contains the DNs 535 * of the entries to be joined with that entry. 536 * </LI> 537 * </UL> 538 * </LI> 539 * <LI> 540 * The fields that may be used for a reverse DN join include: 541 * <UL> 542 * <LI> 543 * {@code type} -- A mandatory string field whose value must be 544 * "{@code reverse-dn}". 545 * </LI> 546 * <LI> 547 * {@code target-attribute} -- A mandatory string field whose value is 548 * the name of the attribute in joined entries that contains a value 549 * that matches the DN of the source entry. 550 * </LI> 551 * </UL> 552 * </LI> 553 * <LI> 554 * The fields that may be used for an equality join include: 555 * <UL> 556 * <LI> 557 * {@code type} -- A mandatory string field whose value must be 558 * "{@code equality}". 559 * </LI> 560 * <LI> 561 * {@code source-attribute} -- A mandatory string field whose value is 562 * the name of an attribute in the source entry whose values will be 563 * used to identify entries to be joined with that source entry. 564 * </LI> 565 * <LI> 566 * {@code target-attribute} -- A mandatory string field whose value is 567 * the name of the attribute in joined entries that must contain at 568 * least one of the values from the source attribute in the source 569 * entry. 570 * </LI> 571 * <LI> 572 * {@code match-all} -- A mandatory Boolean field that indicates 573 * whether to only join entries in which the target attribute contains 574 * all of the values of the source attribute. 575 * </LI> 576 * </UL> 577 * </LI> 578 * <LI> 579 * The fields that may be used for a contains join include: 580 * <UL> 581 * <LI> 582 * {@code type} -- A mandatory string field whose value must be 583 * "{@code contains}". 584 * </LI> 585 * <LI> 586 * {@code source-attribute} -- A mandatory string field whose value is 587 * the name of an attribute in the source entry whose values will be 588 * used to identify entries to be joined with that source entry. 589 * </LI> 590 * <LI> 591 * {@code target-attribute} -- A mandatory string field whose value is 592 * the name of the attribute in joined entries that must contain at 593 * least one value that includes the value of a source attribute as a 594 * substring. 595 * </LI> 596 * <LI> 597 * {@code match-all} -- A mandatory Boolean field that indicates 598 * whether to only join entries in which the target attribute contains 599 * values that contain all of the values of the source attribute as 600 * substrings. 601 * </LI> 602 * </UL> 603 * </LI> 604 * <LI> 605 * The fields that may be used for an AND join include: 606 * <UL> 607 * <LI> 608 * {@code type} -- A mandatory string field whose value must be 609 * "{@code and}". 610 * </LI> 611 * <LI> 612 * {@code rules} -- A mandatory, non-empty array field whose values 613 * are the JSON objects that represent the nested join rules that must 614 * all match an entry for it to be joined with the source entry. 615 * </LI> 616 * </UL> 617 * </LI> 618 * <LI> 619 * The fields that may be used for an OR join include: 620 * <UL> 621 * <LI> 622 * {@code type} -- A mandatory string field whose value must be 623 * "{@code or}". 624 * </LI> 625 * <LI> 626 * {@code rules} -- A mandatory, non-empty array field whose values 627 * are the JSON objects that represent the nested join rules of which 628 * at least one must match an entry for it to be joined with the 629 * source entry. 630 * </LI> 631 * </UL> 632 * </LI> 633 * </UL> 634 * 635 * @return A JSON object that contains a representation of this control. 636 */ 637 @Override() 638 @NotNull() 639 public JSONObject toJSONControl() 640 { 641 return new JSONObject( 642 new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID, 643 JOIN_REQUEST_OID), 644 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME, 645 INFO_CONTROL_NAME_JOIN_REQUEST.get()), 646 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY, 647 isCritical()), 648 new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON, 649 encodeValueJSON(joinRequestValue))); 650 } 651 652 653 654 /** 655 * Encodes the provided join request value to a JSON object. 656 * 657 * @param value The join request value to encode. It must not be 658 * {@code null}. 659 * 660 * @return The JSON object containing the encoded join request value. 661 */ 662 @NotNull() 663 private static JSONObject encodeValueJSON( 664 @NotNull final JoinRequestValue value) 665 { 666 final Map<String,JSONValue> fields = new LinkedHashMap<>(); 667 fields.put(JSON_FIELD_JOIN_RULE, value.getJoinRule().toJSON()); 668 669 670 final JoinBaseDN joinBaseDN = value.getBaseDN(); 671 switch (joinBaseDN.getType()) 672 { 673 case JoinBaseDN.BASE_TYPE_SEARCH_BASE: 674 fields.put(JSON_FIELD_BASE_DN_TYPE, 675 new JSONString(JSON_BASE_DN_TYPE_USE_SEARCH_BASE_DN)); 676 break; 677 678 case JoinBaseDN.BASE_TYPE_SOURCE_ENTRY_DN: 679 fields.put(JSON_FIELD_BASE_DN_TYPE, 680 new JSONString(JSON_BASE_DN_TYPE_USE_SOURCE_ENTRY_DN)); 681 break; 682 683 case JoinBaseDN.BASE_TYPE_CUSTOM: 684 fields.put(JSON_FIELD_BASE_DN_TYPE, 685 new JSONString(JSON_BASE_DN_TYPE_USE_CUSTOM_BASE_DN)); 686 fields.put(JSON_FIELD_BASE_DN_VALUE, 687 new JSONString(joinBaseDN.getCustomBaseDN())); 688 break; 689 } 690 691 692 final SearchScope scope = value.getScope(); 693 if (scope != null) 694 { 695 switch (scope.intValue()) 696 { 697 case SearchScope.BASE_INT_VALUE: 698 fields.put(JSON_FIELD_SCOPE, 699 new JSONString(JSON_SCOPE_BASE_OBJECT)); 700 break; 701 702 case SearchScope.ONE_INT_VALUE: 703 fields.put(JSON_FIELD_SCOPE, 704 new JSONString(JSON_SCOPE_SINGLE_LEVEL)); 705 break; 706 707 case SearchScope.SUB_INT_VALUE: 708 fields.put(JSON_FIELD_SCOPE, 709 new JSONString(JSON_SCOPE_WHOLE_SUBTREE)); 710 break; 711 712 case SearchScope.SUBORDINATE_SUBTREE_INT_VALUE: 713 fields.put(JSON_FIELD_SCOPE, 714 new JSONString(JSON_SCOPE_SUBORDINATE_SUBTREE)); 715 break; 716 } 717 } 718 719 720 final DereferencePolicy derefPolicy = value.getDerefPolicy(); 721 if (derefPolicy != null) 722 { 723 switch(derefPolicy.intValue()) 724 { 725 case 0: 726 fields.put(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR, 727 new JSONString(JSON_ALIAS_BEHAVIOR_NEVER)); 728 break; 729 case 1: 730 fields.put(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR, 731 new JSONString(JSON_ALIAS_BEHAVIOR_SEARCHING)); 732 break; 733 case 2: 734 fields.put(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR, 735 new JSONString(JSON_ALIAS_BEHAVIOR_FINDING)); 736 break; 737 case 3: 738 fields.put(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR, 739 new JSONString(JSON_ALIAS_BEHAVIOR_ALWAYS)); 740 break; 741 } 742 } 743 744 745 final Integer sizeLimit = value.getSizeLimit(); 746 if (sizeLimit != null) 747 { 748 fields.put(JSON_FIELD_SIZE_LIMIT, new JSONNumber(sizeLimit)); 749 } 750 751 752 final Filter filter = value.getFilter(); 753 if (filter != null) 754 { 755 fields.put(JSON_FIELD_FILTER, new JSONString(filter.toString())); 756 } 757 758 759 final String[] attributes = value.getAttributes(); 760 if ((attributes != null) && (attributes.length > 0)) 761 { 762 final List<JSONValue> attrValues = new ArrayList<>(attributes.length); 763 for (final String attr : attributes) 764 { 765 attrValues.add(new JSONString(attr)); 766 } 767 768 fields.put(JSON_FIELD_ATTRIBUTES, new JSONArray(attrValues)); 769 } 770 771 772 fields.put(JSON_FIELD_REQUIRE_MATCH, 773 new JSONBoolean(value.requireMatch())); 774 775 776 final JoinRequestValue nestedJoin = value.getNestedJoin(); 777 if (nestedJoin != null) 778 { 779 fields.put(JSON_FIELD_NESTED_JOIN, encodeValueJSON(nestedJoin)); 780 } 781 782 return new JSONObject(fields); 783 } 784 785 786 787 /** 788 * Attempts to decode the provided object as a JSON representation of a join 789 * request control. 790 * 791 * @param controlObject The JSON object to be decoded. It must not be 792 * {@code null}. 793 * @param strict Indicates whether to use strict mode when decoding 794 * the provided JSON object. If this is {@code true}, 795 * then this method will throw an exception if the 796 * provided JSON object contains any unrecognized 797 * fields. If this is {@code false}, then unrecognized 798 * fields will be ignored. 799 * 800 * @return The join request control that was decoded from the provided JSON 801 * object. 802 * 803 * @throws LDAPException If the provided JSON object cannot be parsed as a 804 * valid join request control. 805 */ 806 @NotNull() 807 public static JoinRequestControl decodeJSONControl( 808 @NotNull final JSONObject controlObject, 809 final boolean strict) 810 throws LDAPException 811 { 812 final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper( 813 controlObject, strict, true, true); 814 815 final ASN1OctetString rawValue = jsonControl.getRawValue(); 816 if (rawValue != null) 817 { 818 return new JoinRequestControl(new Control(jsonControl.getOID(), 819 jsonControl.getCriticality(), rawValue)); 820 } 821 822 823 final JoinRequestValue joinRequestValue = 824 decodeJoinRequestValueJSON(controlObject, jsonControl.getValueObject(), 825 strict); 826 return new JoinRequestControl(jsonControl.getCriticality(), 827 joinRequestValue); 828 } 829 830 831 832 /** 833 * Decodes the provided value object as a join request value. 834 * 835 * @param controlObject A JSON object that represents the join request 836 * control being decoded. It must not be {@code null}. 837 * @param valueObject A JSON object that represents the join request value 838 * being decoded. It must not be {@code null}. 839 * @param strict Indicates whether to use strict mode when decoding 840 * the provided JSON object. If this is {@code true}, 841 * then this method will throw an exception if the 842 * provided JSON object contains any unrecognized 843 * fields. If this is {@code false}, then unrecognized 844 * fields will be ignored. 845 * 846 * @return The join request value that was decoded. 847 * 848 * @throws LDAPException If the provided value object does not represent a 849 * valid join request control. 850 */ 851 @NotNull() 852 private static JoinRequestValue decodeJoinRequestValueJSON( 853 @NotNull final JSONObject controlObject, 854 @NotNull final JSONObject valueObject, 855 final boolean strict) 856 throws LDAPException 857 { 858 final JSONObject joinRuleObject = 859 valueObject.getFieldAsObject(JSON_FIELD_JOIN_RULE); 860 if (joinRuleObject == null) 861 { 862 throw new LDAPException(ResultCode.DECODING_ERROR, 863 ERR_JOIN_REQUEST_JSON_MISSING_FIELD.get( 864 controlObject.toSingleLineString(), 865 JSON_FIELD_JOIN_RULE)); 866 } 867 868 final JoinRule joinRule; 869 try 870 { 871 joinRule = JoinRule.decodeJSONJoinRule(joinRuleObject, strict); 872 } 873 catch (final LDAPException e) 874 { 875 Debug.debugException(e); 876 throw new LDAPException(ResultCode.DECODING_ERROR, 877 ERR_JOIN_REQUEST_JSON_INVALID_JOIN_RULE.get( 878 controlObject.toSingleLineString(), 879 JSON_FIELD_JOIN_RULE, e.getMessage()), 880 e); 881 } 882 883 884 final JoinBaseDN baseDN; 885 final String baseDNType = 886 valueObject.getFieldAsString(JSON_FIELD_BASE_DN_TYPE); 887 final String baseDNValue = 888 valueObject.getFieldAsString(JSON_FIELD_BASE_DN_VALUE); 889 if (baseDNType == null) 890 { 891 throw new LDAPException(ResultCode.DECODING_ERROR, 892 ERR_JOIN_REQUEST_JSON_MISSING_FIELD.get( 893 controlObject.toSingleLineString(), 894 JSON_FIELD_BASE_DN_TYPE)); 895 } 896 897 switch (baseDNType) 898 { 899 case JSON_BASE_DN_TYPE_USE_SEARCH_BASE_DN: 900 if (baseDNValue != null) 901 { 902 throw new LDAPException(ResultCode.DECODING_ERROR, 903 ERR_JOIN_REQUEST_JSON_DISALLOWED_BASE_DN_VALUE.get( 904 controlObject.toSingleLineString(), 905 JSON_FIELD_BASE_DN_VALUE, JSON_FIELD_BASE_DN_TYPE, 906 baseDNType)); 907 } 908 909 baseDN = JoinBaseDN.createUseSearchBaseDN(); 910 break; 911 912 case JSON_BASE_DN_TYPE_USE_SOURCE_ENTRY_DN: 913 if (baseDNValue != null) 914 { 915 throw new LDAPException(ResultCode.DECODING_ERROR, 916 ERR_JOIN_REQUEST_JSON_DISALLOWED_BASE_DN_VALUE.get( 917 controlObject.toSingleLineString(), 918 JSON_FIELD_BASE_DN_VALUE, JSON_FIELD_BASE_DN_TYPE, 919 baseDNType)); 920 } 921 922 baseDN = JoinBaseDN.createUseSourceEntryDN(); 923 break; 924 925 case JSON_BASE_DN_TYPE_USE_CUSTOM_BASE_DN: 926 if (baseDNValue == null) 927 { 928 throw new LDAPException(ResultCode.DECODING_ERROR, 929 ERR_JOIN_REQUEST_JSON_MISSING_BASE_DN_VALUE.get( 930 controlObject.toSingleLineString(), 931 JSON_FIELD_BASE_DN_VALUE, JSON_FIELD_BASE_DN_TYPE, 932 baseDNType)); 933 } 934 935 baseDN = JoinBaseDN.createUseCustomBaseDN(baseDNValue); 936 break; 937 938 default: 939 throw new LDAPException(ResultCode.DECODING_ERROR, 940 ERR_JOIN_REQUEST_JSON_INVALID_BASE_DN_TYPE.get( 941 controlObject.toSingleLineString(), baseDNType, 942 JSON_FIELD_BASE_DN_TYPE, JSON_BASE_DN_TYPE_USE_SEARCH_BASE_DN, 943 JSON_BASE_DN_TYPE_USE_SOURCE_ENTRY_DN, 944 JSON_BASE_DN_TYPE_USE_CUSTOM_BASE_DN)); 945 } 946 947 948 final SearchScope scope; 949 final String scopeStr = 950 valueObject.getFieldAsString(JSON_FIELD_SCOPE); 951 if (scopeStr == null) 952 { 953 scope = null; 954 } 955 else 956 { 957 switch (scopeStr) 958 { 959 case JSON_SCOPE_BASE_OBJECT: 960 scope = SearchScope.BASE; 961 break; 962 case JSON_SCOPE_SINGLE_LEVEL: 963 scope = SearchScope.ONE; 964 break; 965 case JSON_SCOPE_WHOLE_SUBTREE: 966 scope = SearchScope.SUB; 967 break; 968 case JSON_SCOPE_SUBORDINATE_SUBTREE: 969 scope = SearchScope.SUBORDINATE_SUBTREE; 970 break; 971 default: 972 throw new LDAPException(ResultCode.DECODING_ERROR, 973 ERR_JOIN_REQUEST_JSON_INVALID_SCOPE.get( 974 controlObject.toSingleLineString(), scopeStr, 975 JSON_FIELD_SCOPE, JSON_SCOPE_BASE_OBJECT, 976 JSON_SCOPE_SINGLE_LEVEL, JSON_SCOPE_WHOLE_SUBTREE, 977 JSON_SCOPE_SUBORDINATE_SUBTREE)); 978 } 979 } 980 981 982 final DereferencePolicy derefPolicy; 983 final String derefStr = 984 valueObject.getFieldAsString(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR); 985 if (derefStr == null) 986 { 987 derefPolicy = null; 988 } 989 else 990 { 991 switch (derefStr) 992 { 993 case JSON_ALIAS_BEHAVIOR_NEVER: 994 derefPolicy = DereferencePolicy.NEVER; 995 break; 996 case JSON_ALIAS_BEHAVIOR_SEARCHING: 997 derefPolicy = DereferencePolicy.SEARCHING; 998 break; 999 case JSON_ALIAS_BEHAVIOR_FINDING: 1000 derefPolicy = DereferencePolicy.FINDING; 1001 break; 1002 case JSON_ALIAS_BEHAVIOR_ALWAYS: 1003 derefPolicy = DereferencePolicy.ALWAYS; 1004 break; 1005 default: 1006 throw new LDAPException(ResultCode.DECODING_ERROR, 1007 ERR_JOIN_REQUEST_JSON_INVALID_DEREF.get( 1008 controlObject.toSingleLineString(), derefStr, 1009 JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR, 1010 JSON_ALIAS_BEHAVIOR_NEVER, JSON_ALIAS_BEHAVIOR_SEARCHING, 1011 JSON_ALIAS_BEHAVIOR_FINDING, JSON_ALIAS_BEHAVIOR_ALWAYS)); 1012 } 1013 } 1014 1015 1016 final Integer sizeLimit = 1017 valueObject.getFieldAsInteger(JSON_FIELD_SIZE_LIMIT); 1018 1019 1020 final Filter filter; 1021 final String filterStr = valueObject.getFieldAsString(JSON_FIELD_FILTER); 1022 if (filterStr == null) 1023 { 1024 filter = null; 1025 } 1026 else 1027 { 1028 try 1029 { 1030 filter = Filter.create(filterStr); 1031 } 1032 catch (final Exception e) 1033 { 1034 Debug.debugException(e); 1035 throw new LDAPException(ResultCode.DECODING_ERROR, 1036 ERR_JOIN_REQUEST_JSON_INVALID_FILTER.get( 1037 controlObject.toSingleLineString(), filterStr, 1038 JSON_FIELD_FILTER), 1039 e); 1040 } 1041 } 1042 1043 1044 final String[] attributes; 1045 final List<JSONValue> attrValues = 1046 valueObject.getFieldAsArray(JSON_FIELD_ATTRIBUTES); 1047 if (attrValues == null) 1048 { 1049 attributes = null; 1050 } 1051 else 1052 { 1053 attributes = new String[attrValues.size()]; 1054 for (int i=0; i < attributes.length; i++) 1055 { 1056 final JSONValue v = attrValues.get(i); 1057 if (v instanceof JSONString) 1058 { 1059 attributes[i] = ((JSONString) v).stringValue(); 1060 } 1061 else 1062 { 1063 throw new LDAPException(ResultCode.DECODING_ERROR, 1064 ERR_JOIN_REQUEST_JSON_ATTR_NOT_STRING.get( 1065 controlObject.toSingleLineString(), 1066 JSON_FIELD_ATTRIBUTES)); 1067 } 1068 } 1069 } 1070 1071 1072 final Boolean requireMatch = 1073 valueObject.getFieldAsBoolean(JSON_FIELD_REQUIRE_MATCH); 1074 if (requireMatch == null) 1075 { 1076 throw new LDAPException(ResultCode.DECODING_ERROR, 1077 ERR_JOIN_REQUEST_JSON_MISSING_FIELD.get( 1078 controlObject.toSingleLineString(), 1079 JSON_FIELD_REQUIRE_MATCH)); 1080 } 1081 1082 1083 final JoinRequestValue nestedJoin; 1084 final JSONObject nestedJoinObject = 1085 valueObject.getFieldAsObject(JSON_FIELD_NESTED_JOIN); 1086 if (nestedJoinObject == null) 1087 { 1088 nestedJoin = null; 1089 } 1090 else 1091 { 1092 nestedJoin = 1093 decodeJoinRequestValueJSON(controlObject, nestedJoinObject, strict); 1094 } 1095 1096 1097 if (strict) 1098 { 1099 final List<String> unrecognizedFields = 1100 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 1101 valueObject, JSON_FIELD_JOIN_RULE, JSON_FIELD_BASE_DN_TYPE, 1102 JSON_FIELD_BASE_DN_VALUE, JSON_FIELD_SCOPE, 1103 JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR, JSON_FIELD_SIZE_LIMIT, 1104 JSON_FIELD_FILTER, JSON_FIELD_ATTRIBUTES, 1105 JSON_FIELD_REQUIRE_MATCH, JSON_FIELD_NESTED_JOIN); 1106 if (! unrecognizedFields.isEmpty()) 1107 { 1108 throw new LDAPException(ResultCode.DECODING_ERROR, 1109 ERR_JOIN_REQUEST_JSON_UNRECOGNIZED_FIELD.get( 1110 controlObject.toSingleLineString(), 1111 unrecognizedFields.get(0))); 1112 } 1113 } 1114 1115 1116 return new JoinRequestValue(joinRule, baseDN, scope, derefPolicy, sizeLimit, 1117 filter, attributes, requireMatch, nestedJoin); 1118 } 1119 1120 1121 1122 /** 1123 * {@inheritDoc} 1124 */ 1125 @Override() 1126 public void toString(@NotNull final StringBuilder buffer) 1127 { 1128 buffer.append("JoinRequestControl(value="); 1129 joinRequestValue.toString(buffer); 1130 buffer.append(')'); 1131 } 1132}