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