| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.directory.shared.ldap.codec.search; |
| |
| |
| import java.io.UnsupportedEncodingException; |
| import java.nio.BufferOverflowException; |
| import java.nio.ByteBuffer; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.apache.directory.shared.asn1.Asn1Object; |
| import org.apache.directory.shared.asn1.ber.IAsn1Container; |
| import org.apache.directory.shared.asn1.ber.tlv.TLV; |
| import org.apache.directory.shared.asn1.ber.tlv.UniversalTag; |
| import org.apache.directory.shared.asn1.ber.tlv.Value; |
| import org.apache.directory.shared.asn1.codec.DecoderException; |
| import org.apache.directory.shared.asn1.codec.EncoderException; |
| import org.apache.directory.shared.i18n.I18n; |
| import org.apache.directory.shared.ldap.codec.LdapConstants; |
| import org.apache.directory.shared.ldap.codec.LdapMessageCodec; |
| import org.apache.directory.shared.ldap.codec.LdapMessageContainer; |
| import org.apache.directory.shared.ldap.codec.MessageTypeEnum; |
| import org.apache.directory.shared.ldap.entry.DefaultEntryAttribute; |
| import org.apache.directory.shared.ldap.entry.EntryAttribute; |
| import org.apache.directory.shared.ldap.filter.SearchScope; |
| import org.apache.directory.shared.ldap.name.DN; |
| |
| |
| /** |
| * A SearchRequest ldapObject. It's a sub-class of Asn1Object, and it implements |
| * the ldapObject class to be seen as a member of the LdapMessage CHOICE. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public class SearchRequestCodec extends LdapMessageCodec |
| { |
| // ~ Instance fields |
| // ---------------------------------------------------------------------------- |
| |
| /** The base DN */ |
| private DN baseObject; |
| |
| /** The scope. It could be baseObject, singleLevel or wholeSubtree. */ |
| private SearchScope scope; |
| |
| /** |
| * The deref alias could be neverDerefAliases, derefInSearching, |
| * derefFindingBaseObj or derefAlways. |
| */ |
| private int derefAliases; |
| |
| /** The size limit (number of objects returned) */ |
| private long sizeLimit; |
| |
| /** |
| * The time limit (max time to process the response before returning the |
| * result) |
| */ |
| private int timeLimit; |
| |
| /** |
| * An indicator as to whether search results will contain both attribute |
| * types and values, or just attribute types. Setting this field to TRUE |
| * causes only attribute types (no values) to be returned. Setting this |
| * field to FALSE causes both attribute types and values to be returned. |
| */ |
| private boolean typesOnly; |
| |
| /** The filter tree */ |
| private Filter filter; |
| |
| /** The list of attributes to get */ |
| private List<EntryAttribute> attributes = new ArrayList<EntryAttribute>(); |
| |
| /** The current filter. This is used while decoding a PDU */ |
| private Filter currentFilter; |
| |
| /** A temporary storage for a terminal Filter */ |
| private Filter terminalFilter; |
| |
| /** The searchRequest length */ |
| private int searchRequestLength; |
| |
| /** The attributeDescriptionList length */ |
| private int attributeDescriptionListLength; |
| |
| |
| // ~ Constructors |
| // ------------------------------------------------------------------------------- |
| |
| /** |
| * Creates a new SearchRequest object. |
| */ |
| public SearchRequestCodec() |
| { |
| super(); |
| } |
| |
| |
| // ~ Methods |
| // ------------------------------------------------------------------------------------ |
| |
| /** |
| * Get the message type |
| * |
| * @return Returns the type. |
| */ |
| public MessageTypeEnum getMessageType() |
| { |
| return MessageTypeEnum.SEARCH_REQUEST; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public String getMessageTypeName() |
| { |
| return "SEARCH_REQUEST"; |
| } |
| |
| |
| /** |
| * Get the list of attributes |
| * |
| * @return Returns the attributes. |
| */ |
| public List<EntryAttribute> getAttributes() |
| { |
| return attributes; |
| } |
| |
| |
| /** |
| * Add an attribute to the attributes list. |
| * |
| * @param attribute The attribute to add to the list |
| */ |
| public void addAttribute( String attribute ) |
| { |
| attributes.add( new DefaultEntryAttribute( attribute ) ); |
| } |
| |
| |
| /** |
| * Get the base object |
| * |
| * @return Returns the baseObject. |
| */ |
| public DN getBaseObject() |
| { |
| return baseObject; |
| } |
| |
| |
| /** |
| * Set the base object |
| * |
| * @param baseObject The baseObject to set. |
| */ |
| public void setBaseObject( DN baseObject ) |
| { |
| this.baseObject = baseObject; |
| } |
| |
| |
| /** |
| * Get the derefAliases flag |
| * |
| * @return Returns the derefAliases. |
| */ |
| public int getDerefAliases() |
| { |
| return derefAliases; |
| } |
| |
| |
| /** |
| * Set the derefAliases flag |
| * |
| * @param derefAliases The derefAliases to set. |
| */ |
| public void setDerefAliases( int derefAliases ) |
| { |
| this.derefAliases = derefAliases; |
| } |
| |
| |
| /** |
| * Get the filter |
| * |
| * @return Returns the filter. |
| */ |
| public Filter getFilter() |
| { |
| return filter; |
| } |
| |
| |
| /** |
| * Set the filter |
| * |
| * @param filter The filter to set. |
| */ |
| public void setFilter( Filter filter ) |
| { |
| this.filter = filter; |
| } |
| |
| |
| /** |
| * Get the search scope |
| * |
| * @return Returns the scope. |
| */ |
| public SearchScope getScope() |
| { |
| return scope; |
| } |
| |
| |
| /** |
| * Set the search scope |
| * |
| * @param scope The scope to set. |
| */ |
| public void setScope( SearchScope scope ) |
| { |
| this.scope = scope; |
| } |
| |
| |
| /** |
| * Get the size limit |
| * |
| * @return Returns the sizeLimit. |
| */ |
| public long getSizeLimit() |
| { |
| return sizeLimit; |
| } |
| |
| |
| /** |
| * Set the size limit |
| * |
| * @param sizeLimit The sizeLimit to set. |
| */ |
| public void setSizeLimit( long sizeLimit ) |
| { |
| this.sizeLimit = sizeLimit; |
| } |
| |
| |
| /** |
| * Get the time limit |
| * |
| * @return Returns the timeLimit. |
| */ |
| public int getTimeLimit() |
| { |
| return timeLimit; |
| } |
| |
| |
| /** |
| * Set the time limit |
| * |
| * @param timeLimit The timeLimit to set. |
| */ |
| public void setTimeLimit( int timeLimit ) |
| { |
| this.timeLimit = timeLimit; |
| } |
| |
| |
| /** |
| * Get the typesOnly flag |
| * |
| * @return Returns the typesOnly. |
| */ |
| public boolean isTypesOnly() |
| { |
| return typesOnly; |
| } |
| |
| |
| /** |
| * Set the typesOnly flag |
| * |
| * @param typesOnly The typesOnly to set. |
| */ |
| public void setTypesOnly( boolean typesOnly ) |
| { |
| this.typesOnly = typesOnly; |
| } |
| |
| |
| /** |
| * Get the current dilter |
| * |
| * @return Returns the currentFilter. |
| */ |
| public Filter getCurrentFilter() |
| { |
| return currentFilter; |
| } |
| |
| /** |
| * Get the comparison dilter |
| * |
| * @return Returns the comparisonFilter. |
| */ |
| public Filter getTerminalFilter() |
| { |
| return terminalFilter; |
| } |
| |
| /** |
| * Set the terminal filter |
| * |
| * @param terminalFilter the teminalFilter. |
| */ |
| public void setTerminalFilter( Filter terminalFilter ) |
| { |
| this.terminalFilter = terminalFilter; |
| } |
| |
| |
| /** |
| * Add a current filter. We have two cases : |
| * - there is no previous current filter : the filter |
| * is the top level filter |
| * - there is a previous current filter : the filter is added |
| * to the currentFilter set, and the current filter is changed |
| * |
| * In any case, the previous current filter will always be a |
| * ConnectorFilter when this method is called. |
| * |
| * @param localFilter The filter to set. |
| */ |
| public void addCurrentFilter( Filter localFilter ) throws DecoderException |
| { |
| if ( currentFilter != null ) |
| { |
| // Ok, we have a parent. The new Filter will be added to |
| // this parent, and will become the currentFilter if it's a connector. |
| ( ( ConnectorFilter ) currentFilter ).addFilter( localFilter ); |
| localFilter.setParent( currentFilter ); |
| |
| if ( localFilter instanceof ConnectorFilter ) |
| { |
| currentFilter = localFilter; |
| } |
| } |
| else |
| { |
| // No parent. This Filter will become the root. |
| currentFilter = localFilter; |
| currentFilter.setParent( this ); |
| this.filter = localFilter; |
| } |
| } |
| |
| /** |
| * Set the current dilter |
| * |
| * @param filter The filter to set. |
| */ |
| public void setCurrentFilter( Filter filter ) |
| { |
| currentFilter = filter; |
| } |
| |
| |
| /** |
| * This method is used to clear the filter's stack for terminated elements. An element |
| * is considered as terminated either if : |
| * - it's a final element (ie an element which cannot contains a Filter) |
| * - its current length equals its expected length. |
| * |
| * @param container The container being decoded |
| */ |
| public void unstackFilters( IAsn1Container container ) |
| { |
| LdapMessageContainer ldapMessageContainer = ( LdapMessageContainer ) container; |
| |
| TLV tlv = ldapMessageContainer.getCurrentTLV(); |
| TLV localParent = tlv.getParent(); |
| Filter localFilter = terminalFilter; |
| |
| // The parent has been completed, so fold it |
| while ( ( localParent != null ) && ( localParent.getExpectedLength() == 0 ) ) |
| { |
| if ( localParent.getId() != localFilter.getParent().getTlvId() ) |
| { |
| localParent = localParent.getParent(); |
| |
| } |
| else |
| { |
| Asn1Object filterParent = localFilter.getParent(); |
| |
| // We have a special case with PresentFilter, which has not been |
| // pushed on the stack, so we need to get its parent's parent |
| if ( localFilter instanceof PresentFilter ) |
| { |
| filterParent = filterParent.getParent(); |
| } |
| else if ( filterParent instanceof Filter ) |
| { |
| filterParent = filterParent.getParent(); |
| } |
| |
| if ( filterParent instanceof Filter ) |
| { |
| // The parent is a filter ; it will become the new currentFilter |
| // and we will loop again. |
| currentFilter = (Filter)filterParent; |
| localFilter = currentFilter; |
| localParent = localParent.getParent(); |
| } |
| else |
| { |
| // We can stop the recursion, we have reached the searchResult Object |
| break; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Compute the SearchRequest length |
| * |
| * SearchRequest : |
| * <pre> |
| * 0x63 L1 |
| * | |
| * +--> 0x04 L2 baseObject |
| * +--> 0x0A 0x01 scope |
| * +--> 0x0A 0x01 derefAliases |
| * +--> 0x02 0x0(1..4) sizeLimit |
| * +--> 0x02 0x0(1..4) timeLimit |
| * +--> 0x01 0x01 typesOnly |
| * +--> filter.computeLength() |
| * +--> 0x30 L3 (Attribute description list) |
| * | |
| * +--> 0x04 L4-1 Attribute description |
| * +--> 0x04 L4-2 Attribute description |
| * +--> ... |
| * +--> 0x04 L4-i Attribute description |
| * +--> ... |
| * +--> 0x04 L4-n Attribute description |
| * </pre> |
| */ |
| protected int computeLengthProtocolOp() |
| { |
| searchRequestLength = 0; |
| |
| // The baseObject |
| searchRequestLength += 1 + TLV.getNbBytes( DN.getNbBytes( baseObject ) ) |
| + DN.getNbBytes( baseObject ); |
| |
| // The scope |
| searchRequestLength += 1 + 1 + 1; |
| |
| // The derefAliases |
| searchRequestLength += 1 + 1 + 1; |
| |
| // The sizeLimit |
| searchRequestLength += 1 + 1 + Value.getNbBytes( sizeLimit ); |
| |
| // The timeLimit |
| searchRequestLength += 1 + 1 + Value.getNbBytes( timeLimit ); |
| |
| // The typesOnly |
| searchRequestLength += 1 + 1 + 1; |
| |
| // The filter |
| searchRequestLength += filter.computeLength(); |
| |
| // The attributes description list |
| attributeDescriptionListLength = 0; |
| |
| if ( ( attributes != null ) && ( attributes.size() != 0 ) ) |
| { |
| // Compute the attributes length |
| for ( EntryAttribute attribute:attributes ) |
| { |
| // add the attribute length to the attributes length |
| try |
| { |
| int idLength = attribute.getId().getBytes( "UTF-8" ).length; |
| attributeDescriptionListLength += 1 + TLV.getNbBytes( idLength ) + idLength; |
| } |
| catch ( UnsupportedEncodingException uee ) |
| { |
| // Should not be possible. The encoding of the Attribute ID |
| // will check that this ID is valid, and if not, it will |
| // throw an exception. |
| // The allocated length will be set to a null length value |
| // in order to avoid an exception thrown while encoding the |
| // Attribute ID. |
| attributeDescriptionListLength += 1 + 1; |
| } |
| } |
| } |
| |
| searchRequestLength += 1 + TLV.getNbBytes( attributeDescriptionListLength ) + attributeDescriptionListLength; |
| |
| // Return the result. |
| return 1 + TLV.getNbBytes( searchRequestLength ) + searchRequestLength; |
| } |
| |
| |
| /** |
| * Encode the SearchRequest message to a PDU. |
| * |
| * SearchRequest : |
| * <pre> |
| * 0x63 LL |
| * 0x04 LL baseObject |
| * 0x0A 01 scope |
| * 0x0A 01 derefAliases |
| * 0x02 0N sizeLimit |
| * 0x02 0N timeLimit |
| * 0x01 0x01 typesOnly |
| * filter.encode() |
| * 0x30 LL attributeDescriptionList |
| * 0x04 LL attributeDescription |
| * ... |
| * 0x04 LL attributeDescription |
| * </pre> |
| * @param buffer The buffer where to put the PDU |
| * @return The PDU. |
| */ |
| protected void encodeProtocolOp( ByteBuffer buffer ) throws EncoderException |
| { |
| try |
| { |
| // The SearchRequest Tag |
| buffer.put( LdapConstants.SEARCH_REQUEST_TAG ); |
| buffer.put( TLV.getBytes( searchRequestLength ) ); |
| |
| // The baseObject |
| Value.encode( buffer, DN.getBytes( baseObject ) ); |
| |
| // The scope |
| Value.encodeEnumerated( buffer, scope.getScope() ); |
| |
| // The derefAliases |
| Value.encodeEnumerated( buffer, derefAliases ); |
| |
| // The sizeLimit |
| Value.encode( buffer, sizeLimit ); |
| |
| // The timeLimit |
| Value.encode( buffer, timeLimit ); |
| |
| // The typesOnly |
| Value.encode( buffer, typesOnly ); |
| |
| // The filter |
| filter.encode( buffer ); |
| |
| // The attributeDescriptionList |
| buffer.put( UniversalTag.SEQUENCE_TAG ); |
| buffer.put( TLV.getBytes( attributeDescriptionListLength ) ); |
| |
| if ( ( attributes != null ) && ( attributes.size() != 0 ) ) |
| { |
| // encode each attribute |
| for ( EntryAttribute attribute:attributes ) |
| { |
| Value.encode( buffer, attribute.getId() ); |
| } |
| } |
| } |
| catch ( BufferOverflowException boe ) |
| { |
| throw new EncoderException( I18n.err( I18n.ERR_04005 ) ); |
| } |
| } |
| |
| |
| /** |
| * @return A string that represent the Filter |
| */ |
| private String buildFilter() |
| { |
| if ( filter == null ) |
| { |
| return ""; |
| } |
| |
| StringBuffer sb = new StringBuffer(); |
| |
| sb.append( "(" ).append( filter ).append( ")" ); |
| |
| return sb.toString(); |
| } |
| |
| |
| /** |
| * @return A string that represent the atributes list |
| */ |
| private String buildAttributes() |
| { |
| StringBuffer sb = new StringBuffer(); |
| |
| if ( attributes != null ) |
| { |
| boolean isFirst = true; |
| |
| if ( ( attributes != null ) && ( attributes.size() != 0 ) ) |
| { |
| // encode each attribute |
| for ( EntryAttribute attribute:attributes ) |
| { |
| if ( isFirst ) |
| { |
| isFirst = false; |
| } |
| else |
| { |
| sb.append( ", " ); |
| } |
| |
| sb.append( attribute != null ? attribute.getId() : "<no ID>" ); |
| } |
| } |
| } |
| |
| return sb.toString(); |
| } |
| |
| |
| /** |
| * Return a string the represent a SearchRequest |
| */ |
| public String toString() |
| { |
| StringBuffer sb = new StringBuffer(); |
| |
| sb.append( " Search Request\n" ); |
| sb.append( " Base Object : '" ).append( baseObject ).append( "'\n" ); |
| sb.append( " Scope : " ); |
| |
| switch ( scope ) |
| { |
| case OBJECT: |
| sb.append( "base object" ); |
| break; |
| |
| case ONELEVEL: |
| sb.append( "single level" ); |
| break; |
| |
| case SUBTREE: |
| sb.append( "whole subtree" ); |
| break; |
| } |
| |
| sb.append( "\n" ); |
| |
| sb.append( " Deref Aliases : " ); |
| |
| switch ( derefAliases ) |
| { |
| case LdapConstants.NEVER_DEREF_ALIASES: |
| sb.append( "never Deref Aliases" ); |
| break; |
| |
| case LdapConstants.DEREF_IN_SEARCHING: |
| sb.append( "deref In Searching" ); |
| break; |
| |
| case LdapConstants.DEREF_FINDING_BASE_OBJ: |
| sb.append( "deref Finding Base Obj" ); |
| break; |
| |
| case LdapConstants.DEREF_ALWAYS: |
| sb.append( "deref Always" ); |
| break; |
| } |
| |
| sb.append( "\n" ); |
| |
| sb.append( " Size Limit : " ); |
| |
| if ( sizeLimit == 0 ) |
| { |
| sb.append( "no limit" ); |
| } |
| else |
| { |
| sb.append( sizeLimit ); |
| } |
| |
| sb.append( "\n" ); |
| |
| sb.append( " Time Limit : " ); |
| |
| if ( timeLimit == 0 ) |
| { |
| sb.append( "no limit" ); |
| } |
| else |
| { |
| sb.append( timeLimit ); |
| } |
| |
| sb.append( "\n" ); |
| |
| sb.append( " Types Only : " ).append( typesOnly ).append( "\n" ); |
| sb.append( " Filter : '" ).append( buildFilter() ).append( "'\n" ); |
| |
| if ( ( attributes != null ) && ( attributes.size() != 0 ) ) |
| { |
| sb.append( " Attributes : " ).append( buildAttributes() ).append( "\n" ); |
| } |
| return sb.toString(); |
| } |
| } |