blob: c98d32eca8ea29c13689f98079d5ef2b43860274 [file] [log] [blame]
/*
* 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();
}
}