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