001/* 002 * Copyright 2009-2022 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2009-2022 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-2022 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 super(JOIN_REQUEST_OID, true, 334 new ASN1OctetString(joinRequestValue.encode().encode())); 335 336 this.joinRequestValue = joinRequestValue; 337 } 338 339 340 341 /** 342 * Creates a new join request control which is decoded from the provided 343 * generic control. 344 * 345 * @param control The generic control to be decoded as a join request 346 * control. 347 * 348 * @throws LDAPException If the provided control cannot be decoded as a 349 * virtual attributes only request control. 350 */ 351 public JoinRequestControl(@NotNull final Control control) 352 throws LDAPException 353 { 354 super(control); 355 356 final ASN1OctetString value = control.getValue(); 357 if (value == null) 358 { 359 throw new LDAPException(ResultCode.DECODING_ERROR, 360 ERR_JOIN_REQUEST_CONTROL_NO_VALUE.get()); 361 } 362 363 final ASN1Element valueElement; 364 try 365 { 366 valueElement = ASN1Element.decode(value.getValue()); 367 } 368 catch (final Exception e) 369 { 370 Debug.debugException(e); 371 372 throw new LDAPException(ResultCode.DECODING_ERROR, 373 ERR_JOIN_REQUEST_VALUE_CANNOT_DECODE.get( 374 StaticUtils.getExceptionMessage(e)), 375 e); 376 } 377 378 joinRequestValue = JoinRequestValue.decode(valueElement); 379 } 380 381 382 383 /** 384 * Retrieves the join request value for this join request control. 385 * 386 * @return The join request value for this join request control. 387 */ 388 @NotNull() 389 public JoinRequestValue getJoinRequestValue() 390 { 391 return joinRequestValue; 392 } 393 394 395 396 /** 397 * {@inheritDoc} 398 */ 399 @Override() 400 @NotNull() 401 public String getControlName() 402 { 403 return INFO_CONTROL_NAME_JOIN_REQUEST.get(); 404 } 405 406 407 408 /** 409 * Retrieves a representation of this join request control as a JSON object. 410 * The JSON object uses the following fields: 411 * <UL> 412 * <LI> 413 * {@code oid} -- A mandatory string field whose value is the object 414 * identifier for this control. For the join request control, the OID is 415 * "1.3.6.1.4.1.30221.2.5.9". 416 * </LI> 417 * <LI> 418 * {@code control-name} -- An optional string field whose value is a 419 * human-readable name for this control. This field is only intended for 420 * descriptive purposes, and when decoding a control, the {@code oid} 421 * field should be used to identify the type of control. 422 * </LI> 423 * <LI> 424 * {@code criticality} -- A mandatory Boolean field used to indicate 425 * whether this control is considered critical. 426 * </LI> 427 * <LI> 428 * {@code value-base64} -- An optional string field whose value is a 429 * base64-encoded representation of the raw value for this join request 430 * control. Exactly one of the {@code value-base64} and 431 * {@code value-json} fields must be present. 432 * </LI> 433 * <LI> 434 * {@code value-json} -- An optional JSON object field whose value is a 435 * user-friendly representation of the value for this join request 436 * control. Exactly one of the {@code value-base64} and 437 * {@code value-json} fields must be present, and if the 438 * {@code value-json} field is used, then it will use the following 439 * fields: 440 * <UL> 441 * <LI> 442 * {@code join-rule} -- A mandatory JSON object field that provides 443 * the primary criteria to use when selecting entries to be joined 444 * with search result entries. The format for join rule objects will 445 * be described in more detail below. 446 * </LI> 447 * <LI> 448 * {@code base-dn-type} -- A mandatory string field that indicates 449 * who the server should determine the base DN to use for join 450 * processing. The value must be one of "{@code use-search-base-dn}", 451 * "{@code use-source-entry-dn}", or "{@code use-custom-base-dn}". 452 * </LI> 453 * <LI> 454 * {@code base-dn-value} -- An optional string field that provides the 455 * custom base DN value to use if the {@code base-dn-type} value was 456 * "{@code use-custom-base-dn}". This field must be present if the 457 * {@code base-dn-type} value was "{@code use-custom-base-dn}", and it 458 * must be absent for other {@code base-dn-type} values. 459 * </LI> 460 * <LI> 461 * {@code scope} -- An optional string field whose value specifies the 462 * scope to use for join processing. If present, the value must be 463 * one of "{@code baseObject}", "{@code singleLevel}", 464 * "{@code wholeSubtree}", or "{@code subordinateSubtree}". If this 465 * is not specified, the scope from the search request will be used. 466 * </LI> 467 * <LI> 468 * {@code alias-dereferencing-behavior} -- An optional string field 469 * whose value specifies the behavior to use for dereferencing any 470 * aliases encountered during join processing. If present, the value 471 * must be one of "{@code neverDerefAliases}", 472 * "{@code derefInSearching}", "{@code derefInFindingBaseObj}", or 473 * {@code derefAlways}". If this is not specified, the dereferencing 474 * behavior from the search request will be used. 475 * </LI> 476 * <LI> 477 * {@code size-limit} -- An optional integer field whose value 478 * specifies the maximum number of entries that may be joined with any 479 * single search 480 * result entry. 481 * </LI> 482 * <LI> 483 * {@code filter} -- An optional string field whose value is the 484 * string representation of a filter that will be required to match an 485 * entry for it to be joined with a search result entry. 486 * </LI> 487 * <LI> 488 * {@code attributes} -- An optional array field whose values are 489 * strings that are the names of attributes to include in joined 490 * entries. 491 * </LI> 492 * <LI> 493 * {@code require-match} -- A mandatory Boolean field that indicates 494 * whether to suppress a search result entry from the set of results 495 * to the client if it is not joined with any other entries. 496 * </LI> 497 * <LI> 498 * {@code nested-join} -- An optional JSON object field whose value 499 * represents the join criteria for a nested join operation. If 500 * present, the fields in this object are the same as the fields that 501 * may be used in the top-level {@code value-json} field (optionally 502 * including another {@code nested-jon} element if desired). 503 * </LI> 504 * </UL> 505 * </LI> 506 * </UL> 507 * <BR><BR> 508 * The following encodings may be used for different types of join rules: 509 * <UL> 510 * <LI> 511 * The fields that may be used for a DN join include: 512 * <UL> 513 * <LI> 514 * {@code type} -- A mandatory string field whose value must be 515 * "{@code dn}". 516 * </LI> 517 * <LI> 518 * {@code source-attribute} -- A mandatory string field whose value is 519 * the name of the attribute in the source entry that contains the DNs 520 * of the entries to be joined with that entry. 521 * </LI> 522 * </UL> 523 * </LI> 524 * <LI> 525 * The fields that may be used for a reverse DN join include: 526 * <UL> 527 * <LI> 528 * {@code type} -- A mandatory string field whose value must be 529 * "{@code reverse-dn}". 530 * </LI> 531 * <LI> 532 * {@code target-attribute} -- A mandatory string field whose value is 533 * the name of the attribute in joined entries that contains a value 534 * that matches the DN of the source entry. 535 * </LI> 536 * </UL> 537 * </LI> 538 * <LI> 539 * The fields that may be used for an equality join include: 540 * <UL> 541 * <LI> 542 * {@code type} -- A mandatory string field whose value must be 543 * "{@code equality}". 544 * </LI> 545 * <LI> 546 * {@code source-attribute} -- A mandatory string field whose value is 547 * the name of an attribute in the source entry whose values will be 548 * used to identify entries to be joined with that source entry. 549 * </LI> 550 * <LI> 551 * {@code target-attribute} -- A mandatory string field whose value is 552 * the name of the attribute in joined entries that must contain at 553 * least one of the values from the source attribute in the source 554 * entry. 555 * </LI> 556 * <LI> 557 * {@code match-all} -- A mandatory Boolean field that indicates 558 * whether to only join entries in which the target attribute contains 559 * all of the values of the source attribute. 560 * </LI> 561 * </UL> 562 * </LI> 563 * <LI> 564 * The fields that may be used for a contains join include: 565 * <UL> 566 * <LI> 567 * {@code type} -- A mandatory string field whose value must be 568 * "{@code contains}". 569 * </LI> 570 * <LI> 571 * {@code source-attribute} -- A mandatory string field whose value is 572 * the name of an attribute in the source entry whose values will be 573 * used to identify entries to be joined with that source entry. 574 * </LI> 575 * <LI> 576 * {@code target-attribute} -- A mandatory string field whose value is 577 * the name of the attribute in joined entries that must contain at 578 * least one value that includes the value of a source attribute as a 579 * substring. 580 * </LI> 581 * <LI> 582 * {@code match-all} -- A mandatory Boolean field that indicates 583 * whether to only join entries in which the target attribute contains 584 * values that contain all of the values of the source attribute as 585 * substrings. 586 * </LI> 587 * </UL> 588 * </LI> 589 * <LI> 590 * The fields that may be used for an AND join include: 591 * <UL> 592 * <LI> 593 * {@code type} -- A mandatory string field whose value must be 594 * "{@code and}". 595 * </LI> 596 * <LI> 597 * {@code rules} -- A mandatory, non-empty array field whose values 598 * are the JSON objects that represent the nested join rules that must 599 * all match an entry for it to be joined with the source entry. 600 * </LI> 601 * </UL> 602 * </LI> 603 * <LI> 604 * The fields that may be used for an OR join include: 605 * <UL> 606 * <LI> 607 * {@code type} -- A mandatory string field whose value must be 608 * "{@code or}". 609 * </LI> 610 * <LI> 611 * {@code rules} -- A mandatory, non-empty array field whose values 612 * are the JSON objects that represent the nested join rules of which 613 * at least one must match an entry for it to be joined with the 614 * source entry. 615 * </LI> 616 * </UL> 617 * </LI> 618 * </UL> 619 * 620 * @return A JSON object that contains a representation of this control. 621 */ 622 @Override() 623 @NotNull() 624 public JSONObject toJSONControl() 625 { 626 return new JSONObject( 627 new JSONField(JSONControlDecodeHelper.JSON_FIELD_OID, 628 JOIN_REQUEST_OID), 629 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CONTROL_NAME, 630 INFO_CONTROL_NAME_JOIN_REQUEST.get()), 631 new JSONField(JSONControlDecodeHelper.JSON_FIELD_CRITICALITY, 632 isCritical()), 633 new JSONField(JSONControlDecodeHelper.JSON_FIELD_VALUE_JSON, 634 encodeValueJSON(joinRequestValue))); 635 } 636 637 638 639 /** 640 * Encodes the provided join request value to a JSON object. 641 * 642 * @param value The join request value to encode. It must not be 643 * {@code null}. 644 * 645 * @return The JSON object containing the encoded join request value. 646 */ 647 @NotNull() 648 private static JSONObject encodeValueJSON( 649 @NotNull final JoinRequestValue value) 650 { 651 final Map<String,JSONValue> fields = new LinkedHashMap<>(); 652 fields.put(JSON_FIELD_JOIN_RULE, value.getJoinRule().toJSON()); 653 654 655 final JoinBaseDN joinBaseDN = value.getBaseDN(); 656 switch (joinBaseDN.getType()) 657 { 658 case JoinBaseDN.BASE_TYPE_SEARCH_BASE: 659 fields.put(JSON_FIELD_BASE_DN_TYPE, 660 new JSONString(JSON_BASE_DN_TYPE_USE_SEARCH_BASE_DN)); 661 break; 662 663 case JoinBaseDN.BASE_TYPE_SOURCE_ENTRY_DN: 664 fields.put(JSON_FIELD_BASE_DN_TYPE, 665 new JSONString(JSON_BASE_DN_TYPE_USE_SOURCE_ENTRY_DN)); 666 break; 667 668 case JoinBaseDN.BASE_TYPE_CUSTOM: 669 fields.put(JSON_FIELD_BASE_DN_TYPE, 670 new JSONString(JSON_BASE_DN_TYPE_USE_CUSTOM_BASE_DN)); 671 fields.put(JSON_FIELD_BASE_DN_VALUE, 672 new JSONString(joinBaseDN.getCustomBaseDN())); 673 break; 674 } 675 676 677 final SearchScope scope = value.getScope(); 678 if (scope != null) 679 { 680 switch (scope.intValue()) 681 { 682 case SearchScope.BASE_INT_VALUE: 683 fields.put(JSON_FIELD_SCOPE, 684 new JSONString(JSON_SCOPE_BASE_OBJECT)); 685 break; 686 687 case SearchScope.ONE_INT_VALUE: 688 fields.put(JSON_FIELD_SCOPE, 689 new JSONString(JSON_SCOPE_SINGLE_LEVEL)); 690 break; 691 692 case SearchScope.SUB_INT_VALUE: 693 fields.put(JSON_FIELD_SCOPE, 694 new JSONString(JSON_SCOPE_WHOLE_SUBTREE)); 695 break; 696 697 case SearchScope.SUBORDINATE_SUBTREE_INT_VALUE: 698 fields.put(JSON_FIELD_SCOPE, 699 new JSONString(JSON_SCOPE_SUBORDINATE_SUBTREE)); 700 break; 701 } 702 } 703 704 705 final DereferencePolicy derefPolicy = value.getDerefPolicy(); 706 if (derefPolicy != null) 707 { 708 switch(derefPolicy.intValue()) 709 { 710 case 0: 711 fields.put(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR, 712 new JSONString(JSON_ALIAS_BEHAVIOR_NEVER)); 713 break; 714 case 1: 715 fields.put(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR, 716 new JSONString(JSON_ALIAS_BEHAVIOR_SEARCHING)); 717 break; 718 case 2: 719 fields.put(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR, 720 new JSONString(JSON_ALIAS_BEHAVIOR_FINDING)); 721 break; 722 case 3: 723 fields.put(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR, 724 new JSONString(JSON_ALIAS_BEHAVIOR_ALWAYS)); 725 break; 726 } 727 } 728 729 730 final Integer sizeLimit = value.getSizeLimit(); 731 if (sizeLimit != null) 732 { 733 fields.put(JSON_FIELD_SIZE_LIMIT, new JSONNumber(sizeLimit)); 734 } 735 736 737 final Filter filter = value.getFilter(); 738 if (filter != null) 739 { 740 fields.put(JSON_FIELD_FILTER, new JSONString(filter.toString())); 741 } 742 743 744 final String[] attributes = value.getAttributes(); 745 if ((attributes != null) && (attributes.length > 0)) 746 { 747 final List<JSONValue> attrValues = new ArrayList<>(attributes.length); 748 for (final String attr : attributes) 749 { 750 attrValues.add(new JSONString(attr)); 751 } 752 753 fields.put(JSON_FIELD_ATTRIBUTES, new JSONArray(attrValues)); 754 } 755 756 757 fields.put(JSON_FIELD_REQUIRE_MATCH, 758 new JSONBoolean(value.requireMatch())); 759 760 761 final JoinRequestValue nestedJoin = value.getNestedJoin(); 762 if (nestedJoin != null) 763 { 764 fields.put(JSON_FIELD_NESTED_JOIN, encodeValueJSON(nestedJoin)); 765 } 766 767 return new JSONObject(fields); 768 } 769 770 771 772 /** 773 * Attempts to decode the provided object as a JSON representation of a join 774 * request control. 775 * 776 * @param controlObject The JSON object to be decoded. It must not be 777 * {@code null}. 778 * @param strict Indicates whether to use strict mode when decoding 779 * the provided JSON object. If this is {@code true}, 780 * then this method will throw an exception if the 781 * provided JSON object contains any unrecognized 782 * fields. If this is {@code false}, then unrecognized 783 * fields will be ignored. 784 * 785 * @return The join request control that was decoded from the provided JSON 786 * object. 787 * 788 * @throws LDAPException If the provided JSON object cannot be parsed as a 789 * valid join request control. 790 */ 791 @NotNull() 792 public static JoinRequestControl decodeJSONControl( 793 @NotNull final JSONObject controlObject, 794 final boolean strict) 795 throws LDAPException 796 { 797 final JSONControlDecodeHelper jsonControl = new JSONControlDecodeHelper( 798 controlObject, strict, true, true); 799 800 final ASN1OctetString rawValue = jsonControl.getRawValue(); 801 if (rawValue != null) 802 { 803 return new JoinRequestControl(new Control(jsonControl.getOID(), 804 jsonControl.getCriticality(), rawValue)); 805 } 806 807 808 final JoinRequestValue joinRequestValue = 809 decodeJoinRequestValueJSON(controlObject, jsonControl.getValueObject(), 810 strict); 811 return new JoinRequestControl(joinRequestValue); 812 } 813 814 815 816 /** 817 * Decodes the provided value object as a join request value. 818 * 819 * @param controlObject A JSON object that represents the join request 820 * control being decoded. It must not be {@code null}. 821 * @param valueObject A JSON object that represents the join request value 822 * being decoded. It must not be {@code null}. 823 * @param strict Indicates whether to use strict mode when decoding 824 * the provided JSON object. If this is {@code true}, 825 * then this method will throw an exception if the 826 * provided JSON object contains any unrecognized 827 * fields. If this is {@code false}, then unrecognized 828 * fields will be ignored. 829 * 830 * @return The join request value that was decoded. 831 * 832 * @throws LDAPException If the provided value object does not represent a 833 * valid join request control. 834 */ 835 @NotNull() 836 private static JoinRequestValue decodeJoinRequestValueJSON( 837 @NotNull final JSONObject controlObject, 838 @NotNull final JSONObject valueObject, 839 final boolean strict) 840 throws LDAPException 841 { 842 final JSONObject joinRuleObject = 843 valueObject.getFieldAsObject(JSON_FIELD_JOIN_RULE); 844 if (joinRuleObject == null) 845 { 846 throw new LDAPException(ResultCode.DECODING_ERROR, 847 ERR_JOIN_REQUEST_JSON_MISSING_FIELD.get( 848 controlObject.toSingleLineString(), 849 JSON_FIELD_JOIN_RULE)); 850 } 851 852 final JoinRule joinRule; 853 try 854 { 855 joinRule = JoinRule.decodeJSONJoinRule(joinRuleObject, strict); 856 } 857 catch (final LDAPException e) 858 { 859 Debug.debugException(e); 860 throw new LDAPException(ResultCode.DECODING_ERROR, 861 ERR_JOIN_REQUEST_JSON_INVALID_JOIN_RULE.get( 862 controlObject.toSingleLineString(), 863 JSON_FIELD_JOIN_RULE, e.getMessage()), 864 e); 865 } 866 867 868 final JoinBaseDN baseDN; 869 final String baseDNType = 870 valueObject.getFieldAsString(JSON_FIELD_BASE_DN_TYPE); 871 final String baseDNValue = 872 valueObject.getFieldAsString(JSON_FIELD_BASE_DN_VALUE); 873 if (baseDNType == null) 874 { 875 throw new LDAPException(ResultCode.DECODING_ERROR, 876 ERR_JOIN_REQUEST_JSON_MISSING_FIELD.get( 877 controlObject.toSingleLineString(), 878 JSON_FIELD_BASE_DN_TYPE)); 879 } 880 881 switch (baseDNType) 882 { 883 case JSON_BASE_DN_TYPE_USE_SEARCH_BASE_DN: 884 if (baseDNValue != null) 885 { 886 throw new LDAPException(ResultCode.DECODING_ERROR, 887 ERR_JOIN_REQUEST_JSON_DISALLOWED_BASE_DN_VALUE.get( 888 controlObject.toSingleLineString(), 889 JSON_FIELD_BASE_DN_VALUE, JSON_FIELD_BASE_DN_TYPE, 890 baseDNType)); 891 } 892 893 baseDN = JoinBaseDN.createUseSearchBaseDN(); 894 break; 895 896 case JSON_BASE_DN_TYPE_USE_SOURCE_ENTRY_DN: 897 if (baseDNValue != null) 898 { 899 throw new LDAPException(ResultCode.DECODING_ERROR, 900 ERR_JOIN_REQUEST_JSON_DISALLOWED_BASE_DN_VALUE.get( 901 controlObject.toSingleLineString(), 902 JSON_FIELD_BASE_DN_VALUE, JSON_FIELD_BASE_DN_TYPE, 903 baseDNType)); 904 } 905 906 baseDN = JoinBaseDN.createUseSourceEntryDN(); 907 break; 908 909 case JSON_BASE_DN_TYPE_USE_CUSTOM_BASE_DN: 910 if (baseDNValue == null) 911 { 912 throw new LDAPException(ResultCode.DECODING_ERROR, 913 ERR_JOIN_REQUEST_JSON_MISSING_BASE_DN_VALUE.get( 914 controlObject.toSingleLineString(), 915 JSON_FIELD_BASE_DN_VALUE, JSON_FIELD_BASE_DN_TYPE, 916 baseDNType)); 917 } 918 919 baseDN = JoinBaseDN.createUseCustomBaseDN(baseDNValue); 920 break; 921 922 default: 923 throw new LDAPException(ResultCode.DECODING_ERROR, 924 ERR_JOIN_REQUEST_JSON_INVALID_BASE_DN_TYPE.get( 925 controlObject.toSingleLineString(), baseDNType, 926 JSON_FIELD_BASE_DN_TYPE, JSON_BASE_DN_TYPE_USE_SEARCH_BASE_DN, 927 JSON_BASE_DN_TYPE_USE_SOURCE_ENTRY_DN, 928 JSON_BASE_DN_TYPE_USE_CUSTOM_BASE_DN)); 929 } 930 931 932 final SearchScope scope; 933 final String scopeStr = 934 valueObject.getFieldAsString(JSON_FIELD_SCOPE); 935 if (scopeStr == null) 936 { 937 scope = null; 938 } 939 else 940 { 941 switch (scopeStr) 942 { 943 case JSON_SCOPE_BASE_OBJECT: 944 scope = SearchScope.BASE; 945 break; 946 case JSON_SCOPE_SINGLE_LEVEL: 947 scope = SearchScope.ONE; 948 break; 949 case JSON_SCOPE_WHOLE_SUBTREE: 950 scope = SearchScope.SUB; 951 break; 952 case JSON_SCOPE_SUBORDINATE_SUBTREE: 953 scope = SearchScope.SUBORDINATE_SUBTREE; 954 break; 955 default: 956 throw new LDAPException(ResultCode.DECODING_ERROR, 957 ERR_JOIN_REQUEST_JSON_INVALID_SCOPE.get( 958 controlObject.toSingleLineString(), scopeStr, 959 JSON_FIELD_SCOPE, JSON_SCOPE_BASE_OBJECT, 960 JSON_SCOPE_SINGLE_LEVEL, JSON_SCOPE_WHOLE_SUBTREE, 961 JSON_SCOPE_SUBORDINATE_SUBTREE)); 962 } 963 } 964 965 966 final DereferencePolicy derefPolicy; 967 final String derefStr = 968 valueObject.getFieldAsString(JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR); 969 if (derefStr == null) 970 { 971 derefPolicy = null; 972 } 973 else 974 { 975 switch (derefStr) 976 { 977 case JSON_ALIAS_BEHAVIOR_NEVER: 978 derefPolicy = DereferencePolicy.NEVER; 979 break; 980 case JSON_ALIAS_BEHAVIOR_SEARCHING: 981 derefPolicy = DereferencePolicy.SEARCHING; 982 break; 983 case JSON_ALIAS_BEHAVIOR_FINDING: 984 derefPolicy = DereferencePolicy.FINDING; 985 break; 986 case JSON_ALIAS_BEHAVIOR_ALWAYS: 987 derefPolicy = DereferencePolicy.ALWAYS; 988 break; 989 default: 990 throw new LDAPException(ResultCode.DECODING_ERROR, 991 ERR_JOIN_REQUEST_JSON_INVALID_DEREF.get( 992 controlObject.toSingleLineString(), derefStr, 993 JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR, 994 JSON_ALIAS_BEHAVIOR_NEVER, JSON_ALIAS_BEHAVIOR_SEARCHING, 995 JSON_ALIAS_BEHAVIOR_FINDING, JSON_ALIAS_BEHAVIOR_ALWAYS)); 996 } 997 } 998 999 1000 final Integer sizeLimit = 1001 valueObject.getFieldAsInteger(JSON_FIELD_SIZE_LIMIT); 1002 1003 1004 final Filter filter; 1005 final String filterStr = valueObject.getFieldAsString(JSON_FIELD_FILTER); 1006 if (filterStr == null) 1007 { 1008 filter = null; 1009 } 1010 else 1011 { 1012 try 1013 { 1014 filter = Filter.create(filterStr); 1015 } 1016 catch (final Exception e) 1017 { 1018 Debug.debugException(e); 1019 throw new LDAPException(ResultCode.DECODING_ERROR, 1020 ERR_JOIN_REQUEST_JSON_INVALID_FILTER.get( 1021 controlObject.toSingleLineString(), filterStr, 1022 JSON_FIELD_FILTER), 1023 e); 1024 } 1025 } 1026 1027 1028 final String[] attributes; 1029 final List<JSONValue> attrValues = 1030 valueObject.getFieldAsArray(JSON_FIELD_ATTRIBUTES); 1031 if (attrValues == null) 1032 { 1033 attributes = null; 1034 } 1035 else 1036 { 1037 attributes = new String[attrValues.size()]; 1038 for (int i=0; i < attributes.length; i++) 1039 { 1040 final JSONValue v = attrValues.get(i); 1041 if (v instanceof JSONString) 1042 { 1043 attributes[i] = ((JSONString) v).stringValue(); 1044 } 1045 else 1046 { 1047 throw new LDAPException(ResultCode.DECODING_ERROR, 1048 ERR_JOIN_REQUEST_JSON_ATTR_NOT_STRING.get( 1049 controlObject.toSingleLineString(), 1050 JSON_FIELD_ATTRIBUTES)); 1051 } 1052 } 1053 } 1054 1055 1056 final Boolean requireMatch = 1057 valueObject.getFieldAsBoolean(JSON_FIELD_REQUIRE_MATCH); 1058 if (requireMatch == null) 1059 { 1060 throw new LDAPException(ResultCode.DECODING_ERROR, 1061 ERR_JOIN_REQUEST_JSON_MISSING_FIELD.get( 1062 controlObject.toSingleLineString(), 1063 JSON_FIELD_REQUIRE_MATCH)); 1064 } 1065 1066 1067 final JoinRequestValue nestedJoin; 1068 final JSONObject nestedJoinObject = 1069 valueObject.getFieldAsObject(JSON_FIELD_NESTED_JOIN); 1070 if (nestedJoinObject == null) 1071 { 1072 nestedJoin = null; 1073 } 1074 else 1075 { 1076 nestedJoin = 1077 decodeJoinRequestValueJSON(controlObject, nestedJoinObject, strict); 1078 } 1079 1080 1081 if (strict) 1082 { 1083 final List<String> unrecognizedFields = 1084 JSONControlDecodeHelper.getControlObjectUnexpectedFields( 1085 valueObject, JSON_FIELD_JOIN_RULE, JSON_FIELD_BASE_DN_TYPE, 1086 JSON_FIELD_BASE_DN_VALUE, JSON_FIELD_SCOPE, 1087 JSON_FIELD_ALIAS_DEREFERENCING_BEHAVIOR, JSON_FIELD_SIZE_LIMIT, 1088 JSON_FIELD_FILTER, JSON_FIELD_ATTRIBUTES, 1089 JSON_FIELD_REQUIRE_MATCH, JSON_FIELD_NESTED_JOIN); 1090 if (! unrecognizedFields.isEmpty()) 1091 { 1092 throw new LDAPException(ResultCode.DECODING_ERROR, 1093 ERR_JOIN_REQUEST_JSON_UNRECOGNIZED_FIELD.get( 1094 controlObject.toSingleLineString(), 1095 unrecognizedFields.get(0))); 1096 } 1097 } 1098 1099 1100 return new JoinRequestValue(joinRule, baseDN, scope, derefPolicy, sizeLimit, 1101 filter, attributes, requireMatch, nestedJoin); 1102 } 1103 1104 1105 1106 /** 1107 * {@inheritDoc} 1108 */ 1109 @Override() 1110 public void toString(@NotNull final StringBuilder buffer) 1111 { 1112 buffer.append("JoinRequestControl(value="); 1113 joinRequestValue.toString(buffer); 1114 buffer.append(')'); 1115 } 1116}