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