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