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 (Jeu, 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 }