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.util.ArrayList; 026 import java.util.Collections; 027 import java.util.Iterator; 028 import java.util.List; 029 030 import com.unboundid.asn1.ASN1Element; 031 import com.unboundid.asn1.ASN1Enumerated; 032 import com.unboundid.asn1.ASN1OctetString; 033 import com.unboundid.asn1.ASN1Sequence; 034 import com.unboundid.ldap.sdk.Control; 035 import com.unboundid.ldap.sdk.DecodeableControl; 036 import com.unboundid.ldap.sdk.LDAPException; 037 import com.unboundid.ldap.sdk.ResultCode; 038 import com.unboundid.ldap.sdk.SearchResultEntry; 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 provides an implementation of a control that may be included in a 058 * search result entry in response to a join request control to provide a set of 059 * entries related to the search result entry. See the class-level 060 * documentation for the {@link JoinRequestControl} class for additional 061 * information and an example demonstrating its use. 062 * <BR><BR> 063 * The value of the join result control is encoded as follows: 064 * <PRE> 065 * JoinResult ::= SEQUENCE { 066 * COMPONENTS OF LDAPResult, 067 * entries [4] SEQUENCE OF JoinedEntry } 068 * </PRE> 069 */ 070 @NotMutable() 071 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 072 public final class JoinResultControl 073 extends Control 074 implements DecodeableControl 075 { 076 /** 077 * The OID (1.3.6.1.4.1.30221.2.5.9) for the join result control. 078 */ 079 public static final String JOIN_RESULT_OID = "1.3.6.1.4.1.30221.2.5.9"; 080 081 082 083 /** 084 * The BER type for the referral URLs element. 085 */ 086 private static final byte TYPE_REFERRAL_URLS = (byte) 0xA3; 087 088 089 090 /** 091 * The BER type for the join results element. 092 */ 093 private static final byte TYPE_JOIN_RESULTS = (byte) 0xA4; 094 095 096 097 /** 098 * The serial version UID for this serializable class. 099 */ 100 private static final long serialVersionUID = 681831114773253358L; 101 102 103 104 // The set of entries which have been joined with the associated search result 105 // entry. 106 private final List<JoinedEntry> joinResults; 107 108 // The set of referral URLs for this join result. 109 private final List<String> referralURLs; 110 111 // The result code for this join result. 112 private final ResultCode resultCode; 113 114 // The diagnostic message for this join result. 115 private final String diagnosticMessage; 116 117 // The matched DN for this join result. 118 private final String matchedDN; 119 120 121 122 /** 123 * Creates a new empty control instance that is intended to be used only for 124 * decoding controls via the {@code DecodeableControl} interface. 125 */ 126 JoinResultControl() 127 { 128 resultCode = null; 129 diagnosticMessage = null; 130 matchedDN = null; 131 referralURLs = null; 132 joinResults = null; 133 } 134 135 136 137 /** 138 * Creates a new join result control indicating a successful join. 139 * 140 * @param joinResults The set of entries that have been joined with the 141 * associated search result entry. It may be 142 * {@code null} or empty if no entries were joined with 143 * the search result entry. 144 */ 145 public JoinResultControl(final List<JoinedEntry> joinResults) 146 { 147 this(ResultCode.SUCCESS, null, null, null, joinResults); 148 } 149 150 151 152 /** 153 * Creates a new join result control with the provided information. 154 * 155 * @param resultCode The result code for the join processing. It 156 * must not be {@code null}. 157 * @param diagnosticMessage A message with additional information about the 158 * result of the join processing. It may be 159 * {@code null} if no message is needed. 160 * @param matchedDN The matched DN for the join processing. It may 161 * be {@code null} if no matched DN is needed. 162 * @param referralURLs The set of referral URLs for any referrals 163 * encountered while processing the join. It may 164 * be {@code null} or empty if no referral URLs 165 * are needed. 166 * @param joinResults The set of entries that have been joined with 167 * associated search result entry. It may be 168 * {@code null} or empty if no entries were joined 169 * with the search result entry. 170 */ 171 public JoinResultControl(final ResultCode resultCode, 172 final String diagnosticMessage, final String matchedDN, 173 final List<String> referralURLs, 174 final List<JoinedEntry> joinResults) 175 { 176 super(JOIN_RESULT_OID, false, 177 encodeValue(resultCode, diagnosticMessage, matchedDN, referralURLs, 178 joinResults)); 179 180 this.resultCode = resultCode; 181 this.diagnosticMessage = diagnosticMessage; 182 this.matchedDN = matchedDN; 183 184 if (referralURLs == null) 185 { 186 this.referralURLs = Collections.emptyList(); 187 } 188 else 189 { 190 this.referralURLs = Collections.unmodifiableList(referralURLs); 191 } 192 193 if (joinResults == null) 194 { 195 this.joinResults = Collections.emptyList(); 196 } 197 else 198 { 199 this.joinResults = Collections.unmodifiableList(joinResults); 200 } 201 } 202 203 204 205 /** 206 * Creates a new join result control with the provided information. 207 * 208 * @param oid The OID for the control. 209 * @param isCritical Indicates whether the control should be marked 210 * critical. 211 * @param value The encoded value for the control. This may be 212 * {@code null} if no value was provided. 213 * 214 * @throws LDAPException If the provided control cannot be decoded as an 215 * account usable response control. 216 */ 217 public JoinResultControl(final String oid, final boolean isCritical, 218 final ASN1OctetString value) 219 throws LDAPException 220 { 221 super(oid, isCritical, value); 222 223 if (value == null) 224 { 225 throw new LDAPException(ResultCode.DECODING_ERROR, 226 ERR_JOIN_RESULT_NO_VALUE.get()); 227 } 228 229 try 230 { 231 final ASN1Element valueElement = ASN1Element.decode(value.getValue()); 232 final ASN1Element[] elements = 233 ASN1Sequence.decodeAsSequence(valueElement).elements(); 234 235 resultCode = ResultCode.valueOf( 236 ASN1Enumerated.decodeAsEnumerated(elements[0]).intValue()); 237 238 final String matchedDNStr = 239 ASN1OctetString.decodeAsOctetString(elements[1]).stringValue(); 240 if (matchedDNStr.length() == 0) 241 { 242 matchedDN = null; 243 } 244 else 245 { 246 matchedDN = matchedDNStr; 247 } 248 249 final String diagnosticMessageStr = 250 ASN1OctetString.decodeAsOctetString(elements[2]).stringValue(); 251 if (diagnosticMessageStr.length() == 0) 252 { 253 diagnosticMessage = null; 254 } 255 else 256 { 257 diagnosticMessage = diagnosticMessageStr; 258 } 259 260 final ArrayList<String> refs = new ArrayList<String>(); 261 final ArrayList<JoinedEntry> entries = new ArrayList<JoinedEntry>(); 262 for (int i=3; i < elements.length; i++) 263 { 264 switch (elements[i].getType()) 265 { 266 case TYPE_REFERRAL_URLS: 267 final ASN1Element[] refElements = 268 ASN1Sequence.decodeAsSequence(elements[i]).elements(); 269 for (final ASN1Element e : refElements) 270 { 271 refs.add(ASN1OctetString.decodeAsOctetString(e).stringValue()); 272 } 273 break; 274 275 case TYPE_JOIN_RESULTS: 276 final ASN1Element[] entryElements = 277 ASN1Sequence.decodeAsSequence(elements[i]).elements(); 278 for (final ASN1Element e : entryElements) 279 { 280 entries.add(JoinedEntry.decode(e)); 281 } 282 break; 283 284 default: 285 throw new LDAPException(ResultCode.DECODING_ERROR, 286 ERR_JOIN_RESULT_INVALID_ELEMENT_TYPE.get( 287 toHex(elements[i].getType()))); 288 } 289 } 290 291 referralURLs = Collections.unmodifiableList(refs); 292 joinResults = Collections.unmodifiableList(entries); 293 } 294 catch (Exception e) 295 { 296 debugException(e); 297 298 throw new LDAPException(ResultCode.DECODING_ERROR, 299 ERR_JOIN_RESULT_CANNOT_DECODE.get(getExceptionMessage(e)), e); 300 } 301 } 302 303 304 305 /** 306 * Encodes the provided information as appropriate for use as the value of 307 * this control. 308 * 309 * @param resultCode The result code for the join processing. It 310 * must not be {@code null}. 311 * @param diagnosticMessage A message with additional information about the 312 * result of the join processing. It may be 313 * {@code null} if no message is needed. 314 * @param matchedDN The matched DN for the join processing. It may 315 * be {@code null} if no matched DN is needed. 316 * @param referralURLs The set of referral URLs for any referrals 317 * encountered while processing the join. It may 318 * be {@code null} or empty if no referral URLs 319 * are needed. 320 * @param joinResults The set of entries that have been joined with 321 * associated search result entry. It may be 322 * {@code null} or empty if no entries were joined 323 * with the search result entry. 324 * 325 * @return An ASN.1 element containing an encoded representation of the 326 * value for this control. 327 */ 328 private static ASN1OctetString encodeValue(final ResultCode resultCode, 329 final String diagnosticMessage, final String matchedDN, 330 final List<String> referralURLs, 331 final List<JoinedEntry> joinResults) 332 { 333 ensureNotNull(resultCode); 334 335 final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(5); 336 elements.add(new ASN1Enumerated(resultCode.intValue())); 337 338 if (matchedDN == null) 339 { 340 elements.add(new ASN1OctetString()); 341 } 342 else 343 { 344 elements.add(new ASN1OctetString(matchedDN)); 345 } 346 347 if (diagnosticMessage == null) 348 { 349 elements.add(new ASN1OctetString()); 350 } 351 else 352 { 353 elements.add(new ASN1OctetString(diagnosticMessage)); 354 } 355 356 if ((referralURLs != null) && (! referralURLs.isEmpty())) 357 { 358 final ArrayList<ASN1Element> refElements = 359 new ArrayList<ASN1Element>(referralURLs.size()); 360 for (final String s : referralURLs) 361 { 362 refElements.add(new ASN1OctetString(s)); 363 } 364 elements.add(new ASN1Sequence(TYPE_REFERRAL_URLS, refElements)); 365 } 366 367 if ((joinResults == null) || joinResults.isEmpty()) 368 { 369 elements.add(new ASN1Sequence(TYPE_JOIN_RESULTS)); 370 } 371 else 372 { 373 final ArrayList<ASN1Element> entryElements = 374 new ArrayList<ASN1Element>(joinResults.size()); 375 for (final JoinedEntry e : joinResults) 376 { 377 entryElements.add(e.encode()); 378 } 379 elements.add(new ASN1Sequence(TYPE_JOIN_RESULTS, entryElements)); 380 } 381 382 return new ASN1OctetString(new ASN1Sequence(elements).encode()); 383 } 384 385 386 387 /** 388 * Retrieves the result code for this join result. 389 * 390 * @return The result code for this join result. 391 */ 392 public ResultCode getResultCode() 393 { 394 return resultCode; 395 } 396 397 398 399 /** 400 * Retrieves the diagnostic message for this join result. 401 * 402 * @return The diagnostic message for this join result, or {@code null} if 403 * there is no diagnostic message. 404 */ 405 public String getDiagnosticMessage() 406 { 407 return diagnosticMessage; 408 } 409 410 411 412 /** 413 * Retrieves the matched DN for this join result. 414 * 415 * @return The matched DN for this join result, or {@code null} if there is 416 * no matched DN. 417 */ 418 public String getMatchedDN() 419 { 420 return matchedDN; 421 } 422 423 424 425 /** 426 * Retrieves the set of referral URLs for this join result. 427 * 428 * @return The set of referral URLs for this join result, or an empty list 429 * if there are no referral URLs. 430 */ 431 public List<String> getReferralURLs() 432 { 433 return referralURLs; 434 } 435 436 437 438 /** 439 * Retrieves the set of entries that have been joined with the associated 440 * search result entry. 441 * 442 * @return The set of entries that have been joined with the associated 443 * search result entry. 444 */ 445 public List<JoinedEntry> getJoinResults() 446 { 447 return joinResults; 448 } 449 450 451 452 /** 453 * {@inheritDoc} 454 */ 455 public JoinResultControl decodeControl(final String oid, 456 final boolean isCritical, 457 final ASN1OctetString value) 458 throws LDAPException 459 { 460 return new JoinResultControl(oid, isCritical, value); 461 } 462 463 464 465 /** 466 * Extracts a join result control from the provided search result entry. 467 * 468 * @param entry The search result entry from which to retrieve the join 469 * result control. 470 * 471 * @return The join result control contained in the provided search result 472 * entry, or {@code null} if the entry did not contain a join result 473 * control. 474 * 475 * @throws LDAPException If a problem is encountered while attempting to 476 * decode the join result control contained in the 477 * provided search result entry. 478 */ 479 public static JoinResultControl get(final SearchResultEntry entry) 480 throws LDAPException 481 { 482 final Control c = entry.getControl(JOIN_RESULT_OID); 483 if (c == null) 484 { 485 return null; 486 } 487 488 if (c instanceof JoinResultControl) 489 { 490 return (JoinResultControl) c; 491 } 492 else 493 { 494 return new JoinResultControl(c.getOID(), c.isCritical(), c.getValue()); 495 } 496 } 497 498 499 500 /** 501 * {@inheritDoc} 502 */ 503 @Override() 504 public String getControlName() 505 { 506 return INFO_CONTROL_NAME_JOIN_RESULT.get(); 507 } 508 509 510 511 /** 512 * {@inheritDoc} 513 */ 514 @Override() 515 public void toString(final StringBuilder buffer) 516 { 517 buffer.append("JoinResultControl(resultCode='"); 518 buffer.append(resultCode.getName()); 519 buffer.append("', diagnosticMessage='"); 520 521 if (diagnosticMessage != null) 522 { 523 buffer.append(diagnosticMessage); 524 } 525 526 buffer.append("', matchedDN='"); 527 if (matchedDN != null) 528 { 529 buffer.append(matchedDN); 530 } 531 532 buffer.append("', referralURLs={"); 533 final Iterator<String> refIterator = referralURLs.iterator(); 534 while (refIterator.hasNext()) 535 { 536 buffer.append(refIterator.next()); 537 if (refIterator.hasNext()) 538 { 539 buffer.append(", "); 540 } 541 } 542 543 buffer.append("}, joinResults={"); 544 final Iterator<JoinedEntry> entryIterator = joinResults.iterator(); 545 while (entryIterator.hasNext()) 546 { 547 entryIterator.next().toString(buffer); 548 if (entryIterator.hasNext()) 549 { 550 buffer.append(", "); 551 } 552 } 553 554 buffer.append("})"); 555 } 556 }