blob: 029d92690d0b0acf769d6cec7213494c049b4ea0 [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.server.core.api.filtering;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.directory.api.ldap.model.constants.Loggers;
import org.apache.directory.api.ldap.model.cursor.AbstractCursor;
import org.apache.directory.api.ldap.model.cursor.ClosureMonitor;
import org.apache.directory.api.ldap.model.cursor.Cursor;
import org.apache.directory.api.ldap.model.cursor.CursorException;
import org.apache.directory.api.ldap.model.cursor.InvalidCursorPositionException;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.exception.OperationAbandonedException;
import org.apache.directory.api.ldap.model.schema.SchemaManager;
import org.apache.directory.server.core.api.entry.ClonedServerEntry;
import org.apache.directory.server.core.api.entry.ServerEntryUtils;
import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A Cursor which uses a list of filters to selectively return entries and/or
* modify the contents of entries. Uses lazy pre-fetching on positioning
* operations which means adding filters after creation will not miss candidate
* entries.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public class EntryFilteringCursorImpl extends AbstractCursor<Entry> implements EntryFilteringCursor
{
/** the logger used by this class */
private static final Logger log = LoggerFactory.getLogger( EntryFilteringCursorImpl.class );
/** A dedicated log for cursors */
private static final Logger LOG_CURSOR = LoggerFactory.getLogger( Loggers.CURSOR_LOG.getName() );
/** Speedup for logs */
private static final boolean IS_DEBUG = LOG_CURSOR.isDebugEnabled();
/** the underlying wrapped search results Cursor */
private final Cursor<Entry> wrapped;
/** the parameters associated with the search operation */
private final SearchOperationContext operationContext;
/** The SchemaManager */
private final SchemaManager schemaManager;
/** the list of filters to be applied */
private final List<EntryFilter> filters;
/** the first accepted search result that is pre fetched */
private Entry prefetched;
// ------------------------------------------------------------------------
// C O N S T R U C T O R S
// ------------------------------------------------------------------------
/**
* Creates a new entry filtering Cursor over an existing Cursor using a
* single filter initially: more can be added later after creation.
*
* @param wrapped the underlying wrapped Cursor whose entries are filtered
* @param searchControls the controls of search that created this Cursor
* @param invocation the search operation invocation creating this Cursor
* @param filter a single filter to be used
*/
public EntryFilteringCursorImpl( Cursor<Entry> wrapped,
SearchOperationContext operationContext, SchemaManager schemaManager, EntryFilter filter )
{
this( wrapped, operationContext, schemaManager, Collections.singletonList( filter ) );
}
/**
* Creates a new entry filtering Cursor over an existing Cursor using a
* no filter initially: more can be added later after creation.
*
* @param wrapped the underlying wrapped Cursor whose entries are filtered
* @param searchControls the controls of search that created this Cursor
* @param invocation the search operation invocation creating this Cursor
* @param filter a single filter to be used
*/
public EntryFilteringCursorImpl( Cursor<Entry> wrapped, SearchOperationContext operationContext,
SchemaManager schemaManager )
{
if ( IS_DEBUG )
{
LOG_CURSOR.debug( "Creating BaseEntryFilteringCursor {}", this );
}
this.wrapped = wrapped;
this.operationContext = operationContext;
this.filters = new ArrayList<EntryFilter>();
this.schemaManager = schemaManager;
}
/**
* Creates a new entry filtering Cursor over an existing Cursor using a
* list of filters initially: more can be added later after creation.
*
* @param wrapped the underlying wrapped Cursor whose entries are filtered
* @param operationContext the operation context that created this Cursor
* @param invocation the search operation invocation creating this Cursor
* @param filters a list of filters to be used
*/
public EntryFilteringCursorImpl( Cursor<Entry> wrapped,
SearchOperationContext operationContext,
SchemaManager schemaManager,
List<EntryFilter> filters )
{
if ( IS_DEBUG )
{
LOG_CURSOR.debug( "Creating BaseEntryFilteringCursor {}", this );
}
this.wrapped = wrapped;
this.operationContext = operationContext;
this.filters = new ArrayList<EntryFilter>();
this.filters.addAll( filters );
this.schemaManager = schemaManager;
}
// ------------------------------------------------------------------------
// Class Specific Methods
// ------------------------------------------------------------------------
/* (non-Javadoc)
* @see org.apache.directory.server.core.filtering.EntryFilteringCursor#isAbandoned()
*/
public boolean isAbandoned()
{
return operationContext.isAbandoned();
}
/* (non-Javadoc)
* @see org.apache.directory.server.core.filtering.EntryFilteringCursor#setAbandoned(boolean)
*/
public void setAbandoned( boolean abandoned )
{
operationContext.setAbandoned( abandoned );
if ( abandoned )
{
log.info( "Cursor has been abandoned." );
}
}
/* (non-Javadoc)
* @see org.apache.directory.server.core.filtering.EntryFilteringCursor#addEntryFilter(org.apache.directory.server.core.filtering.EntryFilter)
*/
public boolean addEntryFilter( EntryFilter filter )
{
return filters.add( filter );
}
/* (non-Javadoc)
* @see org.apache.directory.server.core.filtering.EntryFilteringCursor#removeEntryFilter(org.apache.directory.server.core.filtering.EntryFilter)
*/
public boolean removeEntryFilter( EntryFilter filter )
{
return filters.remove( filter );
}
/**
* {@inheritDoc}
*/
public List<EntryFilter> getEntryFilters()
{
return Collections.unmodifiableList( filters );
}
/**
* {@inheritDoc}
*/
public SearchOperationContext getOperationContext()
{
return operationContext;
}
// ------------------------------------------------------------------------
// Cursor Interface Methods
// ------------------------------------------------------------------------
/**
* {@inheritDoc}
*/
public void after( Entry element ) throws LdapException, CursorException
{
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
public void afterLast() throws LdapException, CursorException
{
wrapped.afterLast();
prefetched = null;
}
/**
* {@inheritDoc}
*/
public boolean available()
{
return prefetched != null;
}
/**
* {@inheritDoc}
*/
public void before( Entry element ) throws LdapException, CursorException
{
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
public void beforeFirst() throws LdapException, CursorException
{
wrapped.beforeFirst();
prefetched = null;
}
/**
* {@inheritDoc}
*/
public void close()
{
if ( IS_DEBUG )
{
LOG_CURSOR.debug( "Closing BaseEntryFilteringCursor {}", this );
}
wrapped.close();
prefetched = null;
}
/**
* {@inheritDoc}
*/
public void close( Exception reason )
{
if ( IS_DEBUG )
{
LOG_CURSOR.debug( "Closing BaseEntryFilteringCursor {}", this );
}
wrapped.close( reason );
prefetched = null;
}
/**
* {@inheritDoc}
*/
public final void setClosureMonitor( ClosureMonitor monitor )
{
wrapped.setClosureMonitor( monitor );
}
/**
* {@inheritDoc}
*/
public boolean first() throws LdapException, CursorException
{
if ( operationContext.isAbandoned() )
{
log.info( "Cursor has been abandoned." );
close();
throw new OperationAbandonedException();
}
beforeFirst();
return next();
}
/**
* {@inheritDoc}
*/
public Entry get() throws InvalidCursorPositionException
{
if ( available() )
{
return prefetched;
}
throw new InvalidCursorPositionException();
}
/**
* {@inheritDoc}
*/
public boolean isClosed()
{
return wrapped.isClosed();
}
/**
* {@inheritDoc}
*/
public boolean last() throws LdapException, CursorException
{
if ( operationContext.isAbandoned() )
{
log.info( "Cursor has been abandoned." );
close();
throw new OperationAbandonedException();
}
afterLast();
return previous();
}
/**
* {@inheritDoc}
*/
public boolean next() throws LdapException, CursorException
{
if ( operationContext.isAbandoned() )
{
log.info( "Cursor has been abandoned." );
close();
throw new OperationAbandonedException();
}
Entry tempResult = null;
outer: while ( wrapped.next() )
{
Entry tempEntry = wrapped.get();
if ( tempEntry == null )
{
// no candidate
continue;
}
if ( tempEntry instanceof ClonedServerEntry )
{
tempResult = tempEntry;
}
else
{
tempResult = new ClonedServerEntry( tempEntry );
}
/*
* O P T I M I Z A T I O N
* -----------------------
*
* Don't want to waste cycles on enabling a loop for processing
* filters if we have zero or one filter.
*/
if ( filters.isEmpty() )
{
prefetched = tempResult;
ServerEntryUtils.filterContents(
schemaManager,
operationContext, prefetched );
return true;
}
if ( ( filters.size() == 1 ) && filters.get( 0 ).accept( operationContext, tempResult ) )
{
prefetched = tempResult;
ServerEntryUtils.filterContents(
schemaManager,
operationContext, prefetched );
return true;
}
/* E N D O P T I M I Z A T I O N */
for ( EntryFilter filter : filters )
{
// if a filter rejects then short and continue with outer loop
if ( !filter.accept( operationContext, tempResult ) )
{
continue outer;
}
}
/*
* Here the entry has been accepted by all filters.
*/
prefetched = tempResult;
return true;
}
prefetched = null;
return false;
}
/**
* {@inheritDoc}
*/
public boolean previous() throws LdapException, CursorException
{
if ( operationContext.isAbandoned() )
{
log.info( "Cursor has been abandoned." );
close();
throw new OperationAbandonedException();
}
Entry tempResult = null;
outer: while ( wrapped.previous() )
{
Entry entry = wrapped.get();
if ( entry == null )
{
continue;
}
tempResult = new ClonedServerEntry/*Search*/( entry );
/*
* O P T I M I Z A T I O N
* -----------------------
*
* Don't want to waste cycles on enabling a loop for processing
* filters if we have zero or one filter.
*/
if ( filters.isEmpty() )
{
prefetched = tempResult;
ServerEntryUtils.filterContents(
schemaManager,
operationContext, prefetched );
return true;
}
if ( ( filters.size() == 1 ) && filters.get( 0 ).accept( operationContext, tempResult ) )
{
prefetched = tempResult;
ServerEntryUtils.filterContents(
schemaManager,
operationContext, prefetched );
return true;
}
/* E N D O P T I M I Z A T I O N */
for ( EntryFilter filter : filters )
{
// if a filter rejects then short and continue with outer loop
if ( !filter.accept( operationContext, tempResult ) )
{
continue outer;
}
}
/*
* Here the entry has been accepted by all filters.
*/
prefetched = tempResult;
ServerEntryUtils.filterContents(
schemaManager,
operationContext, prefetched );
return true;
}
prefetched = null;
return false;
}
/**
* @see Object#toString()
*/
public String toString( String tabs )
{
StringBuilder sb = new StringBuilder();
if ( wrapped != null )
{
sb.append( tabs ).append( "BaseEntryFilteringCursor, wrapped : \n" );
sb.append( wrapped.toString( tabs + " " ) );
}
else
{
sb.append( tabs ).append( "BaseEntryFilteringCursor, no wrapped\n" );
}
if ( ( filters != null ) && ( filters.size() > 0 ) )
{
sb.append( tabs ).append( "Filters : \n" );
for ( EntryFilter filter : filters )
{
sb.append( filter.toString( tabs + " " ) ).append( "\n" );
}
}
else
{
sb.append( tabs ).append( "No filter\n" );
}
if ( prefetched != null )
{
sb.append( tabs ).append( "Prefetched : \n" );
sb.append( prefetched.toString( tabs + " " ) );
}
else
{
sb.append( tabs ).append( "No prefetched" );
}
return sb.toString();
}
/**
* @see Object#toString()
*/
public String toString()
{
return toString( "" );
}
}