001/* 002 * Copyright 2007-2017 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright (C) 2008-2017 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 * Attempts to create the most appropriate control instance from the provided 510 * information. If a {@link DecodeableControl} instance has been registered 511 * for the specified OID, then this method will attempt to use that instance 512 * to construct a control. If that fails, or if no appropriate 513 * {@code DecodeableControl} is registered, then a generic control will be 514 * returned. 515 * 516 * @param oid The OID for the control. It must not be {@code null}. 517 * @param isCritical Indicates whether the control should be considered 518 * critical. 519 * @param value The value for the control. It may be {@code null} if 520 * there is no value. 521 * 522 * @return The decoded control. 523 * 524 * @throws LDAPException If a problem occurs while attempting to decode the 525 * provided ASN.1 sequence as an LDAP control. 526 */ 527 public static Control decode(final String oid, final boolean isCritical, 528 final ASN1OctetString value) 529 throws LDAPException 530 { 531 final DecodeableControl decodeableControl = decodeableControlMap.get(oid); 532 if (decodeableControl == null) 533 { 534 return new Control(oid, isCritical, value); 535 } 536 else 537 { 538 try 539 { 540 return decodeableControl.decodeControl(oid, isCritical, value); 541 } 542 catch (final Exception e) 543 { 544 debugException(e); 545 return new Control(oid, isCritical, value); 546 } 547 } 548 } 549 550 551 552 /** 553 * Encodes the provided set of controls to an ASN.1 sequence suitable for 554 * inclusion in an LDAP message. 555 * 556 * @param controls The set of controls to be encoded. 557 * 558 * @return An ASN.1 sequence containing the encoded set of controls. 559 */ 560 public static ASN1Sequence encodeControls(final Control[] controls) 561 { 562 final ASN1Sequence[] controlElements = new ASN1Sequence[controls.length]; 563 for (int i=0; i < controls.length; i++) 564 { 565 controlElements[i] = controls[i].encode(); 566 } 567 568 return new ASN1Sequence(CONTROLS_TYPE, controlElements); 569 } 570 571 572 573 /** 574 * Decodes the contents of the provided sequence as a set of controls. 575 * 576 * @param controlSequence The ASN.1 sequence containing the encoded set of 577 * controls. 578 * 579 * @return The decoded set of controls. 580 * 581 * @throws LDAPException If a problem occurs while attempting to decode any 582 * of the controls. 583 */ 584 public static Control[] decodeControls(final ASN1Sequence controlSequence) 585 throws LDAPException 586 { 587 final ASN1Element[] controlElements = controlSequence.elements(); 588 final Control[] controls = new Control[controlElements.length]; 589 590 for (int i=0; i < controlElements.length; i++) 591 { 592 try 593 { 594 controls[i] = decode(ASN1Sequence.decodeAsSequence(controlElements[i])); 595 } 596 catch (ASN1Exception ae) 597 { 598 debugException(ae); 599 throw new LDAPException(ResultCode.DECODING_ERROR, 600 ERR_CONTROLS_DECODE_ELEMENT_NOT_SEQUENCE.get( 601 getExceptionMessage(ae)), 602 ae); 603 } 604 } 605 606 return controls; 607 } 608 609 610 611 /** 612 * Registers the provided class to be used in an attempt to decode controls 613 * with the specified OID. 614 * 615 * @param oid The response control OID for which the provided 616 * class will be registered. 617 * @param controlInstance The control instance that should be used to decode 618 * controls with the provided OID. 619 */ 620 public static void registerDecodeableControl(final String oid, 621 final DecodeableControl controlInstance) 622 { 623 decodeableControlMap.put(oid, controlInstance); 624 } 625 626 627 628 /** 629 * Deregisters the decodeable control class associated with the provided OID. 630 * 631 * @param oid The response control OID for which to deregister the 632 * decodeable control class. 633 */ 634 public static void deregisterDecodeableControl(final String oid) 635 { 636 decodeableControlMap.remove(oid); 637 } 638 639 640 641 /** 642 * Retrieves a hash code for this control. 643 * 644 * @return A hash code for this control. 645 */ 646 @Override() 647 public final int hashCode() 648 { 649 int hashCode = oid.hashCode(); 650 651 if (isCritical) 652 { 653 hashCode++; 654 } 655 656 if (value != null) 657 { 658 hashCode += value.hashCode(); 659 } 660 661 return hashCode; 662 } 663 664 665 666 /** 667 * Indicates whether the provided object may be considered equal to this 668 * control. 669 * 670 * @param o The object for which to make the determination. 671 * 672 * @return {@code true} if the provided object may be considered equal to 673 * this control, or {@code false} if not. 674 */ 675 @Override() 676 public final boolean equals(final Object o) 677 { 678 if (o == null) 679 { 680 return false; 681 } 682 683 if (o == this) 684 { 685 return true; 686 } 687 688 if (! (o instanceof Control)) 689 { 690 return false; 691 } 692 693 final Control c = (Control) o; 694 if (! oid.equals(c.oid)) 695 { 696 return false; 697 } 698 699 if (isCritical != c.isCritical) 700 { 701 return false; 702 } 703 704 if (value == null) 705 { 706 if (c.value != null) 707 { 708 return false; 709 } 710 } 711 else 712 { 713 if (c.value == null) 714 { 715 return false; 716 } 717 718 if (! value.equals(c.value)) 719 { 720 return false; 721 } 722 } 723 724 725 return true; 726 } 727 728 729 730 /** 731 * Retrieves the user-friendly name for this control, if available. If no 732 * user-friendly name has been defined, then the OID will be returned. 733 * 734 * @return The user-friendly name for this control, or the OID if no 735 * user-friendly name is available. 736 */ 737 public String getControlName() 738 { 739 // By default, we will return the OID. Subclasses should override this to 740 // provide the user-friendly name. 741 return oid; 742 } 743 744 745 746 /** 747 * Retrieves a string representation of this LDAP control. 748 * 749 * @return A string representation of this LDAP control. 750 */ 751 @Override() 752 public String toString() 753 { 754 final StringBuilder buffer = new StringBuilder(); 755 toString(buffer); 756 return buffer.toString(); 757 } 758 759 760 761 /** 762 * Appends a string representation of this LDAP control to the provided 763 * buffer. 764 * 765 * @param buffer The buffer to which to append the string representation of 766 * this buffer. 767 */ 768 public void toString(final StringBuilder buffer) 769 { 770 buffer.append("Control(oid="); 771 buffer.append(oid); 772 buffer.append(", isCritical="); 773 buffer.append(isCritical); 774 buffer.append(", value="); 775 776 if (value == null) 777 { 778 buffer.append("{null}"); 779 } 780 else 781 { 782 buffer.append("{byte["); 783 buffer.append(value.getValue().length); 784 buffer.append("]}"); 785 } 786 787 buffer.append(')'); 788 } 789}