001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020 package org.apache.directory.shared.ldap.codec.search; 021 022 023 import java.io.UnsupportedEncodingException; 024 import java.nio.BufferOverflowException; 025 import java.nio.ByteBuffer; 026 import java.util.ArrayList; 027 import java.util.List; 028 029 import org.apache.directory.shared.asn1.Asn1Object; 030 import org.apache.directory.shared.asn1.ber.IAsn1Container; 031 import org.apache.directory.shared.asn1.ber.tlv.TLV; 032 import org.apache.directory.shared.asn1.ber.tlv.UniversalTag; 033 import org.apache.directory.shared.asn1.ber.tlv.Value; 034 import org.apache.directory.shared.asn1.codec.DecoderException; 035 import org.apache.directory.shared.asn1.codec.EncoderException; 036 import org.apache.directory.shared.i18n.I18n; 037 import org.apache.directory.shared.ldap.codec.LdapConstants; 038 import org.apache.directory.shared.ldap.codec.LdapMessageCodec; 039 import org.apache.directory.shared.ldap.codec.LdapMessageContainer; 040 import org.apache.directory.shared.ldap.codec.MessageTypeEnum; 041 import org.apache.directory.shared.ldap.entry.EntryAttribute; 042 import org.apache.directory.shared.ldap.entry.client.DefaultClientAttribute; 043 import org.apache.directory.shared.ldap.filter.SearchScope; 044 import org.apache.directory.shared.ldap.name.DN; 045 046 047 /** 048 * A SearchRequest ldapObject. It's a sub-class of Asn1Object, and it implements 049 * the ldapObject class to be seen as a member of the LdapMessage CHOICE. 050 * 051 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 052 * @version $Rev: 919009 $, $Date: 2010-03-04 15:57:10 +0100 (Thu, 04 Mar 2010) $, 053 */ 054 public class SearchRequestCodec extends LdapMessageCodec 055 { 056 // ~ Instance fields 057 // ---------------------------------------------------------------------------- 058 059 /** The base DN */ 060 private DN baseObject; 061 062 /** The scope. It could be baseObject, singleLevel or wholeSubtree. */ 063 private SearchScope scope; 064 065 /** 066 * The deref alias could be neverDerefAliases, derefInSearching, 067 * derefFindingBaseObj or derefAlways. 068 */ 069 private int derefAliases; 070 071 /** The size limit (number of objects returned) */ 072 private long sizeLimit; 073 074 /** 075 * The time limit (max time to process the response before returning the 076 * result) 077 */ 078 private int timeLimit; 079 080 /** 081 * An indicator as to whether search results will contain both attribute 082 * types and values, or just attribute types. Setting this field to TRUE 083 * causes only attribute types (no values) to be returned. Setting this 084 * field to FALSE causes both attribute types and values to be returned. 085 */ 086 private boolean typesOnly; 087 088 /** The filter tree */ 089 private Filter filter; 090 091 /** The list of attributes to get */ 092 private List<EntryAttribute> attributes = new ArrayList<EntryAttribute>(); 093 094 /** The current filter. This is used while decoding a PDU */ 095 private Filter currentFilter; 096 097 /** A temporary storage for a terminal Filter */ 098 private Filter terminalFilter; 099 100 /** The searchRequest length */ 101 private int searchRequestLength; 102 103 /** The attributeDescriptionList length */ 104 private int attributeDescriptionListLength; 105 106 107 // ~ Constructors 108 // ------------------------------------------------------------------------------- 109 110 /** 111 * Creates a new SearchRequest object. 112 */ 113 public SearchRequestCodec() 114 { 115 super(); 116 } 117 118 119 // ~ Methods 120 // ------------------------------------------------------------------------------------ 121 122 /** 123 * Get the message type 124 * 125 * @return Returns the type. 126 */ 127 public MessageTypeEnum getMessageType() 128 { 129 return MessageTypeEnum.SEARCH_REQUEST; 130 } 131 132 133 /** 134 * {@inheritDoc} 135 */ 136 public String getMessageTypeName() 137 { 138 return "SEARCH_REQUEST"; 139 } 140 141 142 /** 143 * Get the list of attributes 144 * 145 * @return Returns the attributes. 146 */ 147 public List<EntryAttribute> getAttributes() 148 { 149 return attributes; 150 } 151 152 153 /** 154 * Add an attribute to the attributes list. 155 * 156 * @param attribute The attribute to add to the list 157 */ 158 public void addAttribute( String attribute ) 159 { 160 attributes.add( new DefaultClientAttribute( attribute ) ); 161 } 162 163 164 /** 165 * Get the base object 166 * 167 * @return Returns the baseObject. 168 */ 169 public DN getBaseObject() 170 { 171 return baseObject; 172 } 173 174 175 /** 176 * Set the base object 177 * 178 * @param baseObject The baseObject to set. 179 */ 180 public void setBaseObject( DN baseObject ) 181 { 182 this.baseObject = baseObject; 183 } 184 185 186 /** 187 * Get the derefAliases flag 188 * 189 * @return Returns the derefAliases. 190 */ 191 public int getDerefAliases() 192 { 193 return derefAliases; 194 } 195 196 197 /** 198 * Set the derefAliases flag 199 * 200 * @param derefAliases The derefAliases to set. 201 */ 202 public void setDerefAliases( int derefAliases ) 203 { 204 this.derefAliases = derefAliases; 205 } 206 207 208 /** 209 * Get the filter 210 * 211 * @return Returns the filter. 212 */ 213 public Filter getFilter() 214 { 215 return filter; 216 } 217 218 219 /** 220 * Set the filter 221 * 222 * @param filter The filter to set. 223 */ 224 public void setFilter( Filter filter ) 225 { 226 this.filter = filter; 227 } 228 229 230 /** 231 * Get the search scope 232 * 233 * @return Returns the scope. 234 */ 235 public SearchScope getScope() 236 { 237 return scope; 238 } 239 240 241 /** 242 * Set the search scope 243 * 244 * @param scope The scope to set. 245 */ 246 public void setScope( SearchScope scope ) 247 { 248 this.scope = scope; 249 } 250 251 252 /** 253 * Get the size limit 254 * 255 * @return Returns the sizeLimit. 256 */ 257 public long getSizeLimit() 258 { 259 return sizeLimit; 260 } 261 262 263 /** 264 * Set the size limit 265 * 266 * @param sizeLimit The sizeLimit to set. 267 */ 268 public void setSizeLimit( long sizeLimit ) 269 { 270 this.sizeLimit = sizeLimit; 271 } 272 273 274 /** 275 * Get the time limit 276 * 277 * @return Returns the timeLimit. 278 */ 279 public int getTimeLimit() 280 { 281 return timeLimit; 282 } 283 284 285 /** 286 * Set the time limit 287 * 288 * @param timeLimit The timeLimit to set. 289 */ 290 public void setTimeLimit( int timeLimit ) 291 { 292 this.timeLimit = timeLimit; 293 } 294 295 296 /** 297 * Get the typesOnly flag 298 * 299 * @return Returns the typesOnly. 300 */ 301 public boolean isTypesOnly() 302 { 303 return typesOnly; 304 } 305 306 307 /** 308 * Set the typesOnly flag 309 * 310 * @param typesOnly The typesOnly to set. 311 */ 312 public void setTypesOnly( boolean typesOnly ) 313 { 314 this.typesOnly = typesOnly; 315 } 316 317 318 /** 319 * Get the current dilter 320 * 321 * @return Returns the currentFilter. 322 */ 323 public Filter getCurrentFilter() 324 { 325 return currentFilter; 326 } 327 328 /** 329 * Get the comparison dilter 330 * 331 * @return Returns the comparisonFilter. 332 */ 333 public Filter getTerminalFilter() 334 { 335 return terminalFilter; 336 } 337 338 /** 339 * Set the terminal filter 340 * 341 * @param terminalFilter the teminalFilter. 342 */ 343 public void setTerminalFilter( Filter terminalFilter ) 344 { 345 this.terminalFilter = terminalFilter; 346 } 347 348 349 /** 350 * Add a current filter. We have two cases : 351 * - there is no previous current filter : the filter 352 * is the top level filter 353 * - there is a previous current filter : the filter is added 354 * to the currentFilter set, and the current filter is changed 355 * 356 * In any case, the previous current filter will always be a 357 * ConnectorFilter when this method is called. 358 * 359 * @param localFilter The filter to set. 360 */ 361 public void addCurrentFilter( Filter localFilter ) throws DecoderException 362 { 363 if ( currentFilter != null ) 364 { 365 // Ok, we have a parent. The new Filter will be added to 366 // this parent, and will become the currentFilter if it's a connector. 367 ( ( ConnectorFilter ) currentFilter ).addFilter( localFilter ); 368 localFilter.setParent( currentFilter ); 369 370 if ( localFilter instanceof ConnectorFilter ) 371 { 372 currentFilter = localFilter; 373 } 374 } 375 else 376 { 377 // No parent. This Filter will become the root. 378 currentFilter = localFilter; 379 currentFilter.setParent( this ); 380 this.filter = localFilter; 381 } 382 } 383 384 /** 385 * Set the current dilter 386 * 387 * @param filter The filter to set. 388 */ 389 public void setCurrentFilter( Filter filter ) 390 { 391 currentFilter = filter; 392 } 393 394 395 /** 396 * This method is used to clear the filter's stack for terminated elements. An element 397 * is considered as terminated either if : 398 * - it's a final element (ie an element which cannot contains a Filter) 399 * - its current length equals its expected length. 400 * 401 * @param container The container being decoded 402 */ 403 public void unstackFilters( IAsn1Container container ) 404 { 405 LdapMessageContainer ldapMessageContainer = ( LdapMessageContainer ) container; 406 407 TLV tlv = ldapMessageContainer.getCurrentTLV(); 408 TLV localParent = tlv.getParent(); 409 Filter localFilter = terminalFilter; 410 411 // The parent has been completed, so fold it 412 while ( ( localParent != null ) && ( localParent.getExpectedLength() == 0 ) ) 413 { 414 if ( localParent.getId() != localFilter.getParent().getTlvId() ) 415 { 416 localParent = localParent.getParent(); 417 418 } 419 else 420 { 421 Asn1Object filterParent = localFilter.getParent(); 422 423 // We have a special case with PresentFilter, which has not been 424 // pushed on the stack, so we need to get its parent's parent 425 if ( localFilter instanceof PresentFilter ) 426 { 427 filterParent = filterParent.getParent(); 428 } 429 else if ( filterParent instanceof Filter ) 430 { 431 filterParent = filterParent.getParent(); 432 } 433 434 if ( filterParent instanceof Filter ) 435 { 436 // The parent is a filter ; it will become the new currentFilter 437 // and we will loop again. 438 currentFilter = (Filter)filterParent; 439 localFilter = currentFilter; 440 localParent = localParent.getParent(); 441 } 442 else 443 { 444 // We can stop the recursion, we have reached the searchResult Object 445 break; 446 } 447 } 448 } 449 } 450 451 /** 452 * Compute the SearchRequest length 453 * 454 * SearchRequest : 455 * <pre> 456 * 0x63 L1 457 * | 458 * +--> 0x04 L2 baseObject 459 * +--> 0x0A 0x01 scope 460 * +--> 0x0A 0x01 derefAliases 461 * +--> 0x02 0x0(1..4) sizeLimit 462 * +--> 0x02 0x0(1..4) timeLimit 463 * +--> 0x01 0x01 typesOnly 464 * +--> filter.computeLength() 465 * +--> 0x30 L3 (Attribute description list) 466 * | 467 * +--> 0x04 L4-1 Attribute description 468 * +--> 0x04 L4-2 Attribute description 469 * +--> ... 470 * +--> 0x04 L4-i Attribute description 471 * +--> ... 472 * +--> 0x04 L4-n Attribute description 473 * </pre> 474 */ 475 protected int computeLengthProtocolOp() 476 { 477 searchRequestLength = 0; 478 479 // The baseObject 480 searchRequestLength += 1 + TLV.getNbBytes( DN.getNbBytes( baseObject ) ) 481 + DN.getNbBytes( baseObject ); 482 483 // The scope 484 searchRequestLength += 1 + 1 + 1; 485 486 // The derefAliases 487 searchRequestLength += 1 + 1 + 1; 488 489 // The sizeLimit 490 searchRequestLength += 1 + 1 + Value.getNbBytes( sizeLimit ); 491 492 // The timeLimit 493 searchRequestLength += 1 + 1 + Value.getNbBytes( timeLimit ); 494 495 // The typesOnly 496 searchRequestLength += 1 + 1 + 1; 497 498 // The filter 499 searchRequestLength += filter.computeLength(); 500 501 // The attributes description list 502 attributeDescriptionListLength = 0; 503 504 if ( ( attributes != null ) && ( attributes.size() != 0 ) ) 505 { 506 // Compute the attributes length 507 for ( EntryAttribute attribute:attributes ) 508 { 509 // add the attribute length to the attributes length 510 try 511 { 512 int idLength = attribute.getId().getBytes( "UTF-8" ).length; 513 attributeDescriptionListLength += 1 + TLV.getNbBytes( idLength ) + idLength; 514 } 515 catch ( UnsupportedEncodingException uee ) 516 { 517 // Should not be possible. The encoding of the Attribute ID 518 // will check that this ID is valid, and if not, it will 519 // throw an exception. 520 // The allocated length will be set to a null length value 521 // in order to avoid an exception thrown while encoding the 522 // Attribute ID. 523 attributeDescriptionListLength += 1 + 1; 524 } 525 } 526 } 527 528 searchRequestLength += 1 + TLV.getNbBytes( attributeDescriptionListLength ) + attributeDescriptionListLength; 529 530 // Return the result. 531 return 1 + TLV.getNbBytes( searchRequestLength ) + searchRequestLength; 532 } 533 534 535 /** 536 * Encode the SearchRequest message to a PDU. 537 * 538 * SearchRequest : 539 * <pre> 540 * 0x63 LL 541 * 0x04 LL baseObject 542 * 0x0A 01 scope 543 * 0x0A 01 derefAliases 544 * 0x02 0N sizeLimit 545 * 0x02 0N timeLimit 546 * 0x01 0x01 typesOnly 547 * filter.encode() 548 * 0x30 LL attributeDescriptionList 549 * 0x04 LL attributeDescription 550 * ... 551 * 0x04 LL attributeDescription 552 * </pre> 553 * @param buffer The buffer where to put the PDU 554 * @return The PDU. 555 */ 556 protected void encodeProtocolOp( ByteBuffer buffer ) throws EncoderException 557 { 558 try 559 { 560 // The SearchRequest Tag 561 buffer.put( LdapConstants.SEARCH_REQUEST_TAG ); 562 buffer.put( TLV.getBytes( searchRequestLength ) ); 563 564 // The baseObject 565 Value.encode( buffer, DN.getBytes( baseObject ) ); 566 567 // The scope 568 Value.encodeEnumerated( buffer, scope.getScope() ); 569 570 // The derefAliases 571 Value.encodeEnumerated( buffer, derefAliases ); 572 573 // The sizeLimit 574 Value.encode( buffer, sizeLimit ); 575 576 // The timeLimit 577 Value.encode( buffer, timeLimit ); 578 579 // The typesOnly 580 Value.encode( buffer, typesOnly ); 581 582 // The filter 583 filter.encode( buffer ); 584 585 // The attributeDescriptionList 586 buffer.put( UniversalTag.SEQUENCE_TAG ); 587 buffer.put( TLV.getBytes( attributeDescriptionListLength ) ); 588 589 if ( ( attributes != null ) && ( attributes.size() != 0 ) ) 590 { 591 // encode each attribute 592 for ( EntryAttribute attribute:attributes ) 593 { 594 Value.encode( buffer, attribute.getId() ); 595 } 596 } 597 } 598 catch ( BufferOverflowException boe ) 599 { 600 throw new EncoderException( I18n.err( I18n.ERR_04005 ) ); 601 } 602 } 603 604 605 /** 606 * @return A string that represent the Filter 607 */ 608 private String buildFilter() 609 { 610 if ( filter == null ) 611 { 612 return ""; 613 } 614 615 StringBuffer sb = new StringBuffer(); 616 617 sb.append( "(" ).append( filter ).append( ")" ); 618 619 return sb.toString(); 620 } 621 622 623 /** 624 * @return A string that represent the atributes list 625 */ 626 private String buildAttributes() 627 { 628 StringBuffer sb = new StringBuffer(); 629 630 if ( attributes != null ) 631 { 632 boolean isFirst = true; 633 634 if ( ( attributes != null ) && ( attributes.size() != 0 ) ) 635 { 636 // encode each attribute 637 for ( EntryAttribute attribute:attributes ) 638 { 639 if ( isFirst ) 640 { 641 isFirst = false; 642 } 643 else 644 { 645 sb.append( ", " ); 646 } 647 648 sb.append( attribute != null ? attribute.getId() : "<no ID>" ); 649 } 650 } 651 } 652 653 return sb.toString(); 654 } 655 656 657 /** 658 * Return a string the represent a SearchRequest 659 */ 660 public String toString() 661 { 662 StringBuffer sb = new StringBuffer(); 663 664 sb.append( " Search Request\n" ); 665 sb.append( " Base Object : '" ).append( baseObject ).append( "'\n" ); 666 sb.append( " Scope : " ); 667 668 switch ( scope ) 669 { 670 case OBJECT: 671 sb.append( "base object" ); 672 break; 673 674 case ONELEVEL: 675 sb.append( "single level" ); 676 break; 677 678 case SUBTREE: 679 sb.append( "whole subtree" ); 680 break; 681 } 682 683 sb.append( "\n" ); 684 685 sb.append( " Deref Aliases : " ); 686 687 switch ( derefAliases ) 688 { 689 case LdapConstants.NEVER_DEREF_ALIASES: 690 sb.append( "never Deref Aliases" ); 691 break; 692 693 case LdapConstants.DEREF_IN_SEARCHING: 694 sb.append( "deref In Searching" ); 695 break; 696 697 case LdapConstants.DEREF_FINDING_BASE_OBJ: 698 sb.append( "deref Finding Base Obj" ); 699 break; 700 701 case LdapConstants.DEREF_ALWAYS: 702 sb.append( "deref Always" ); 703 break; 704 } 705 706 sb.append( "\n" ); 707 708 sb.append( " Size Limit : " ); 709 710 if ( sizeLimit == 0 ) 711 { 712 sb.append( "no limit" ); 713 } 714 else 715 { 716 sb.append( sizeLimit ); 717 } 718 719 sb.append( "\n" ); 720 721 sb.append( " Time Limit : " ); 722 723 if ( timeLimit == 0 ) 724 { 725 sb.append( "no limit" ); 726 } 727 else 728 { 729 sb.append( timeLimit ); 730 } 731 732 sb.append( "\n" ); 733 734 sb.append( " Types Only : " ).append( typesOnly ).append( "\n" ); 735 sb.append( " Filter : '" ).append( buildFilter() ).append( "'\n" ); 736 737 if ( ( attributes != null ) && ( attributes.size() != 0 ) ) 738 { 739 sb.append( " Attributes : " ).append( buildAttributes() ).append( "\n" ); 740 } 741 return sb.toString(); 742 } 743 }