001/* 002 * Copyright 2007-2014 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2014 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 */ 021package com.unboundid.ldap.sdk; 022 023 024 025import java.io.Serializable; 026import java.lang.reflect.Method; 027import java.util.ArrayList; 028import java.util.concurrent.ConcurrentHashMap; 029 030import com.unboundid.asn1.ASN1Boolean; 031import com.unboundid.asn1.ASN1Buffer; 032import com.unboundid.asn1.ASN1BufferSequence; 033import com.unboundid.asn1.ASN1Element; 034import com.unboundid.asn1.ASN1Exception; 035import com.unboundid.asn1.ASN1OctetString; 036import com.unboundid.asn1.ASN1Sequence; 037import com.unboundid.asn1.ASN1StreamReader; 038import com.unboundid.asn1.ASN1StreamReaderSequence; 039import com.unboundid.util.Extensible; 040import com.unboundid.util.NotMutable; 041import com.unboundid.util.ThreadSafety; 042import com.unboundid.util.ThreadSafetyLevel; 043 044import static com.unboundid.asn1.ASN1Constants.*; 045import static com.unboundid.ldap.sdk.LDAPMessages.*; 046import static com.unboundid.util.Debug.*; 047import static com.unboundid.util.StaticUtils.*; 048import static com.unboundid.util.Validator.*; 049 050 051 052/** 053 * This class provides a data structure that represents an LDAP control. A 054 * control is an element that may be attached to an LDAP request or response 055 * to provide additional information about the processing that should be (or has 056 * been) performed. This class may be overridden to provide additional 057 * processing for specific types of controls. 058 * <BR><BR> 059 * A control includes the following elements: 060 * <UL> 061 * <LI>An object identifier (OID), which identifies the type of control.</LI> 062 * <LI>A criticality flag, which indicates whether the control should be 063 * considered critical to the processing of the operation. If a control 064 * is marked critical but the server either does not support that control 065 * or it is not appropriate for the associated request, then the server 066 * will reject the request. If a control is not marked critical and the 067 * server either does not support it or it is not appropriate for the 068 * associated request, then the server will simply ignore that 069 * control and process the request as if it were not present.</LI> 070 * <LI>An optional value, which provides additional information for the 071 * control. Some controls do not take values, and the value encoding for 072 * controls which do take values varies based on the type of control.</LI> 073 * </UL> 074 * Controls may be included in a request from the client to the server, as well 075 * as responses from the server to the client (including intermediate response, 076 * search result entry, and search result references, in addition to the final 077 * response message for an operation). When using request controls, they may be 078 * included in the request object at the time it is created, or may be added 079 * after the fact for {@link UpdatableLDAPRequest} objects. When using 080 * response controls, each response control class includes a {@code get} method 081 * that can be used to extract the appropriate control from an appropriate 082 * result (e.g., {@link LDAPResult}, {@link SearchResultEntry}, or 083 * {@link SearchResultReference}). 084 */ 085@Extensible() 086@NotMutable() 087@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 088public class Control 089 implements Serializable 090{ 091 /** 092 * The BER type to use for the encoded set of controls in an LDAP message. 093 */ 094 private static final byte CONTROLS_TYPE = (byte) 0xA0; 095 096 097 098 // The registered set of decodeable controls, mapped from their OID to the 099 // class implementing the DecodeableControl interface that should be used to 100 // decode controls with that OID. 101 private static final ConcurrentHashMap<String,DecodeableControl> 102 decodeableControlMap = new ConcurrentHashMap<String,DecodeableControl>(); 103 104 105 106 /** 107 * The serial version UID for this serializable class. 108 */ 109 private static final long serialVersionUID = 4440956109070220054L; 110 111 112 113 // The encoded value for this control, if there is one. 114 private final ASN1OctetString value; 115 116 // Indicates whether this control should be considered critical. 117 private final boolean isCritical; 118 119 // The OID for this control 120 private final String oid; 121 122 123 124 static 125 { 126 try 127 { 128 final Class<?> unboundIDControlHelperClass = Class.forName( 129 "com.unboundid.ldap.sdk.controls.ControlHelper"); 130 final Method method = unboundIDControlHelperClass.getMethod( 131 "registerDefaultResponseControls"); 132 method.invoke(null); 133 } 134 catch (Exception e) 135 { 136 // This is expected in the minimal release, since it doesn't include any 137 // controls. 138 } 139 140 try 141 { 142 final Class<?> unboundIDControlHelperClass = Class.forName( 143 "com.unboundid.ldap.sdk.experimental.ControlHelper"); 144 final Method method = unboundIDControlHelperClass.getMethod( 145 "registerDefaultResponseControls"); 146 method.invoke(null); 147 } 148 catch (Exception e) 149 { 150 // This is expected in the minimal release, since it doesn't include any 151 // controls. 152 } 153 154 try 155 { 156 final Class<?> unboundIDControlHelperClass = Class.forName( 157 "com.unboundid.ldap.sdk.unboundidds.controls.ControlHelper"); 158 final Method method = unboundIDControlHelperClass.getMethod( 159 "registerDefaultResponseControls"); 160 method.invoke(null); 161 } 162 catch (Exception e) 163 { 164 // This is expected in the open source release, since it doesn't contain 165 // the UnboundID-specific controls. In that case, we'll try enable some 166 // additional experimental controls instead. 167 try 168 { 169 final Class<?> experimentalControlHelperClass = Class.forName( 170 "com.unboundid.ldap.sdk.experimental.ControlHelper"); 171 final Method method = experimentalControlHelperClass.getMethod( 172 "registerNonCommercialResponseControls"); 173 method.invoke(null); 174 } 175 catch (Exception e2) 176 { 177 // This is expected in the minimal release, since it doesn't contain any 178 // controls. 179 } 180 } 181 } 182 183 184 185 /** 186 * Creates a new empty control instance that is intended to be used only for 187 * decoding controls via the {@code DecodeableControl} interface. All 188 * {@code DecodeableControl} objects must provide a default constructor that 189 * can be used to create an instance suitable for invoking the 190 * {@code decodeControl} method. 191 */ 192 protected Control() 193 { 194 oid = null; 195 isCritical = true; 196 value = null; 197 } 198 199 200 201 /** 202 * Creates a new control whose fields are initialized from the contents of the 203 * provided control. 204 * 205 * @param control The control whose information should be used to create 206 * this new control. 207 */ 208 protected Control(final Control control) 209 { 210 oid = control.oid; 211 isCritical = control.isCritical; 212 value = control.value; 213 } 214 215 216 217 /** 218 * Creates a new control with the provided OID. It will not be critical, and 219 * it will not have a value. 220 * 221 * @param oid The OID for this control. It must not be {@code null}. 222 */ 223 public Control(final String oid) 224 { 225 ensureNotNull(oid); 226 227 this.oid = oid; 228 isCritical = false; 229 value = null; 230 } 231 232 233 234 /** 235 * Creates a new control with the provided OID and criticality. It will not 236 * have a value. 237 * 238 * @param oid The OID for this control. It must not be {@code null}. 239 * @param isCritical Indicates whether this control should be considered 240 * critical. 241 */ 242 public Control(final String oid, final boolean isCritical) 243 { 244 ensureNotNull(oid); 245 246 this.oid = oid; 247 this.isCritical = isCritical; 248 value = null; 249 } 250 251 252 253 /** 254 * Creates a new control with the provided information. 255 * 256 * @param oid The OID for this control. It must not be {@code null}. 257 * @param isCritical Indicates whether this control should be considered 258 * critical. 259 * @param value The value for this control. It may be {@code null} if 260 * there is no value. 261 */ 262 public Control(final String oid, final boolean isCritical, 263 final ASN1OctetString value) 264 { 265 ensureNotNull(oid); 266 267 this.oid = oid; 268 this.isCritical = isCritical; 269 this.value = value; 270 } 271 272 273 274 /** 275 * Retrieves the OID for this control. 276 * 277 * @return The OID for this control. 278 */ 279 public final String getOID() 280 { 281 return oid; 282 } 283 284 285 286 /** 287 * Indicates whether this control should be considered critical. 288 * 289 * @return {@code true} if this control should be considered critical, or 290 * {@code false} if not. 291 */ 292 public final boolean isCritical() 293 { 294 return isCritical; 295 } 296 297 298 299 /** 300 * Indicates whether this control has a value. 301 * 302 * @return {@code true} if this control has a value, or {@code false} if not. 303 */ 304 public final boolean hasValue() 305 { 306 return (value != null); 307 } 308 309 310 311 /** 312 * Retrieves the encoded value for this control. 313 * 314 * @return The encoded value for this control, or {@code null} if there is no 315 * value. 316 */ 317 public final ASN1OctetString getValue() 318 { 319 return value; 320 } 321 322 323 324 /** 325 * Writes an ASN.1-encoded representation of this control to the provided 326 * ASN.1 stream writer. 327 * 328 * @param writer The ASN.1 stream writer to which the encoded representation 329 * should be written. 330 */ 331 public final void writeTo(final ASN1Buffer writer) 332 { 333 final ASN1BufferSequence controlSequence = writer.beginSequence(); 334 writer.addOctetString(oid); 335 336 if (isCritical) 337 { 338 writer.addBoolean(true); 339 } 340 341 if (value != null) 342 { 343 writer.addOctetString(value.getValue()); 344 } 345 346 controlSequence.end(); 347 } 348 349 350 351 /** 352 * Encodes this control to an ASN.1 sequence suitable for use in an LDAP 353 * message. 354 * 355 * @return The encoded representation of this control. 356 */ 357 public final ASN1Sequence encode() 358 { 359 final ArrayList<ASN1Element> elementList = new ArrayList<ASN1Element>(3); 360 elementList.add(new ASN1OctetString(oid)); 361 362 if (isCritical) 363 { 364 elementList.add(new ASN1Boolean(isCritical)); 365 } 366 367 if (value != null) 368 { 369 elementList.add(new ASN1OctetString(value.getValue())); 370 } 371 372 return new ASN1Sequence(elementList); 373 } 374 375 376 377 /** 378 * Reads an LDAP control from the provided ASN.1 stream reader. 379 * 380 * @param reader The ASN.1 stream reader from which to read the control. 381 * 382 * @return The decoded control. 383 * 384 * @throws LDAPException If a problem occurs while attempting to read or 385 * parse the control. 386 */ 387 public static Control readFrom(final ASN1StreamReader reader) 388 throws LDAPException 389 { 390 try 391 { 392 final ASN1StreamReaderSequence controlSequence = reader.beginSequence(); 393 final String oid = reader.readString(); 394 395 boolean isCritical = false; 396 ASN1OctetString value = null; 397 while (controlSequence.hasMoreElements()) 398 { 399 final byte type = (byte) reader.peek(); 400 switch (type) 401 { 402 case UNIVERSAL_BOOLEAN_TYPE: 403 isCritical = reader.readBoolean(); 404 break; 405 case UNIVERSAL_OCTET_STRING_TYPE: 406 value = new ASN1OctetString(reader.readBytes()); 407 break; 408 default: 409 throw new LDAPException(ResultCode.DECODING_ERROR, 410 ERR_CONTROL_INVALID_TYPE.get(toHex(type))); 411 } 412 } 413 414 return decode(oid, isCritical, value); 415 } 416 catch (LDAPException le) 417 { 418 debugException(le); 419 throw le; 420 } 421 catch (Exception e) 422 { 423 debugException(e); 424 throw new LDAPException(ResultCode.DECODING_ERROR, 425 ERR_CONTROL_CANNOT_DECODE.get(getExceptionMessage(e)), e); 426 } 427 } 428 429 430 431 /** 432 * Decodes the provided ASN.1 sequence as an LDAP control. 433 * 434 * @param controlSequence The ASN.1 sequence to be decoded. 435 * 436 * @return The decoded control. 437 * 438 * @throws LDAPException If a problem occurs while attempting to decode the 439 * provided ASN.1 sequence as an LDAP control. 440 */ 441 public static Control decode(final ASN1Sequence controlSequence) 442 throws LDAPException 443 { 444 final ASN1Element[] elements = controlSequence.elements(); 445 446 if ((elements.length < 1) || (elements.length > 3)) 447 { 448 throw new LDAPException(ResultCode.DECODING_ERROR, 449 ERR_CONTROL_DECODE_INVALID_ELEMENT_COUNT.get( 450 elements.length)); 451 } 452 453 final String oid = 454 ASN1OctetString.decodeAsOctetString(elements[0]).stringValue(); 455 456 boolean isCritical = false; 457 ASN1OctetString value = null; 458 if (elements.length == 2) 459 { 460 switch (elements[1].getType()) 461 { 462 case UNIVERSAL_BOOLEAN_TYPE: 463 try 464 { 465 isCritical = 466 ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue(); 467 } 468 catch (ASN1Exception ae) 469 { 470 debugException(ae); 471 throw new LDAPException(ResultCode.DECODING_ERROR, 472 ERR_CONTROL_DECODE_CRITICALITY.get(getExceptionMessage(ae)), 473 ae); 474 } 475 break; 476 477 case UNIVERSAL_OCTET_STRING_TYPE: 478 value = ASN1OctetString.decodeAsOctetString(elements[1]); 479 break; 480 481 default: 482 throw new LDAPException(ResultCode.DECODING_ERROR, 483 ERR_CONTROL_INVALID_TYPE.get( 484 toHex(elements[1].getType()))); 485 } 486 } 487 else if (elements.length == 3) 488 { 489 try 490 { 491 isCritical = ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue(); 492 } 493 catch (ASN1Exception ae) 494 { 495 debugException(ae); 496 throw new LDAPException(ResultCode.DECODING_ERROR, 497 ERR_CONTROL_DECODE_CRITICALITY.get(getExceptionMessage(ae)), ae); 498 } 499 500 value = ASN1OctetString.decodeAsOctetString(elements[2]); 501 } 502 503 return decode(oid, isCritical, value); 504 } 505 506 507 508 /** 509 * Decodes the provided ASN.1 sequence as an LDAP control. 510 * 511 * @param oid The OID for this control. It must not be {@code null}. 512 * @param isCritical Indicates whether this control should be considered 513 * critical. 514 * @param value The value for this control. It may be {@code null} if 515 * there is no value. 516 * 517 * @return The decoded control. 518 * 519 * @throws LDAPException If a problem occurs while attempting to decode the 520 * provided ASN.1 sequence as an LDAP control. 521 */ 522 public static Control decode(final String oid, final boolean isCritical, 523 final ASN1OctetString value) 524 throws LDAPException 525 { 526 final DecodeableControl decodeableControl = decodeableControlMap.get(oid); 527 if (decodeableControl == null) 528 { 529 return new Control(oid, isCritical, value); 530 } 531 else 532 { 533 try 534 { 535 return decodeableControl.decodeControl(oid, isCritical, value); 536 } 537 catch (Exception e) 538 { 539 debugException(e); 540 return new Control(oid, isCritical, value); 541 } 542 } 543 } 544 545 546 547 /** 548 * Encodes the provided set of controls to an ASN.1 sequence suitable for 549 * inclusion in an LDAP message. 550 * 551 * @param controls The set of controls to be encoded. 552 * 553 * @return An ASN.1 sequence containing the encoded set of controls. 554 */ 555 public static ASN1Sequence encodeControls(final Control[] controls) 556 { 557 final ASN1Sequence[] controlElements = new ASN1Sequence[controls.length]; 558 for (int i=0; i < controls.length; i++) 559 { 560 controlElements[i] = controls[i].encode(); 561 } 562 563 return new ASN1Sequence(CONTROLS_TYPE, controlElements); 564 } 565 566 567 568 /** 569 * Decodes the contents of the provided sequence as a set of controls. 570 * 571 * @param controlSequence The ASN.1 sequence containing the encoded set of 572 * controls. 573 * 574 * @return The decoded set of controls. 575 * 576 * @throws LDAPException If a problem occurs while attempting to decode any 577 * of the controls. 578 */ 579 public static Control[] decodeControls(final ASN1Sequence controlSequence) 580 throws LDAPException 581 { 582 final ASN1Element[] controlElements = controlSequence.elements(); 583 final Control[] controls = new Control[controlElements.length]; 584 585 for (int i=0; i < controlElements.length; i++) 586 { 587 try 588 { 589 controls[i] = decode(ASN1Sequence.decodeAsSequence(controlElements[i])); 590 } 591 catch (ASN1Exception ae) 592 { 593 debugException(ae); 594 throw new LDAPException(ResultCode.DECODING_ERROR, 595 ERR_CONTROLS_DECODE_ELEMENT_NOT_SEQUENCE.get( 596 getExceptionMessage(ae)), 597 ae); 598 } 599 } 600 601 return controls; 602 } 603 604 605 606 /** 607 * Registers the provided class to be used in an attempt to decode controls 608 * with the specified OID. 609 * 610 * @param oid The response control OID for which the provided 611 * class will be registered. 612 * @param controlInstance The control instance that should be used to decode 613 * controls with the provided OID. 614 */ 615 public static void registerDecodeableControl(final String oid, 616 final DecodeableControl controlInstance) 617 { 618 decodeableControlMap.put(oid, controlInstance); 619 } 620 621 622 623 /** 624 * Deregisters the decodeable control class associated with the provided OID. 625 * 626 * @param oid The response control OID for which to deregister the 627 * decodeable control class. 628 */ 629 public static void deregisterDecodeableControl(final String oid) 630 { 631 decodeableControlMap.remove(oid); 632 } 633 634 635 636 /** 637 * Retrieves a hash code for this control. 638 * 639 * @return A hash code for this control. 640 */ 641 @Override() 642 public final int hashCode() 643 { 644 int hashCode = oid.hashCode(); 645 646 if (isCritical) 647 { 648 hashCode++; 649 } 650 651 if (value != null) 652 { 653 hashCode += value.hashCode(); 654 } 655 656 return hashCode; 657 } 658 659 660 661 /** 662 * Indicates whether the provided object may be considered equal to this 663 * control. 664 * 665 * @param o The object for which to make the determination. 666 * 667 * @return {@code true} if the provided object may be considered equal to 668 * this control, or {@code false} if not. 669 */ 670 @Override() 671 public final boolean equals(final Object o) 672 { 673 if (o == null) 674 { 675 return false; 676 } 677 678 if (o == this) 679 { 680 return true; 681 } 682 683 if (! (o instanceof Control)) 684 { 685 return false; 686 } 687 688 final Control c = (Control) o; 689 if (! oid.equals(c.oid)) 690 { 691 return false; 692 } 693 694 if (isCritical != c.isCritical) 695 { 696 return false; 697 } 698 699 if (value == null) 700 { 701 if (c.value != null) 702 { 703 return false; 704 } 705 } 706 else 707 { 708 if (c.value == null) 709 { 710 return false; 711 } 712 713 if (! value.equals(c.value)) 714 { 715 return false; 716 } 717 } 718 719 720 return true; 721 } 722 723 724 725 /** 726 * Retrieves the user-friendly name for this control, if available. If no 727 * user-friendly name has been defined, then the OID will be returned. 728 * 729 * @return The user-friendly name for this control, or the OID if no 730 * user-friendly name is available. 731 */ 732 public String getControlName() 733 { 734 // By default, we will return the OID. Subclasses should override this to 735 // provide the user-friendly name. 736 return oid; 737 } 738 739 740 741 /** 742 * Retrieves a string representation of this LDAP control. 743 * 744 * @return A string representation of this LDAP control. 745 */ 746 @Override() 747 public String toString() 748 { 749 final StringBuilder buffer = new StringBuilder(); 750 toString(buffer); 751 return buffer.toString(); 752 } 753 754 755 756 /** 757 * Appends a string representation of this LDAP control to the provided 758 * buffer. 759 * 760 * @param buffer The buffer to which to append the string representation of 761 * this buffer. 762 */ 763 public void toString(final StringBuilder buffer) 764 { 765 buffer.append("Control(oid="); 766 buffer.append(oid); 767 buffer.append(", isCritical="); 768 buffer.append(isCritical); 769 buffer.append(", value="); 770 771 if (value == null) 772 { 773 buffer.append("{null}"); 774 } 775 else 776 { 777 buffer.append("{byte["); 778 buffer.append(value.getValue().length); 779 buffer.append("]}"); 780 } 781 782 buffer.append(')'); 783 } 784}