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 */ 020package org.apache.directory.server.xdbm.search.cursor; 021 022 023import java.io.IOException; 024 025import org.apache.directory.api.ldap.model.constants.Loggers; 026import org.apache.directory.api.ldap.model.cursor.Cursor; 027import org.apache.directory.api.ldap.model.cursor.CursorException; 028import org.apache.directory.api.ldap.model.cursor.InvalidCursorPositionException; 029import org.apache.directory.api.ldap.model.exception.LdapException; 030import org.apache.directory.api.ldap.model.schema.PrepareString; 031import org.apache.directory.server.core.api.partition.PartitionTxn; 032import org.apache.directory.server.i18n.I18n; 033import org.apache.directory.server.xdbm.AbstractIndexCursor; 034import org.apache.directory.server.xdbm.Index; 035import org.apache.directory.server.xdbm.IndexEntry; 036import org.apache.directory.server.xdbm.IndexNotFoundException; 037import org.apache.directory.server.xdbm.Store; 038import org.apache.directory.server.xdbm.search.evaluator.SubstringEvaluator; 039import org.slf4j.Logger; 040import org.slf4j.LoggerFactory; 041 042 043/** 044 * A Cursor traversing candidates matching a Substring assertion expression. 045 * 046 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 047 */ 048public class SubstringCursor extends AbstractIndexCursor<String> 049{ 050 /** A dedicated log for cursors */ 051 private static final Logger LOG_CURSOR = LoggerFactory.getLogger( Loggers.CURSOR_LOG.getName() ); 052 053 /** Speedup for logs */ 054 private static final boolean IS_DEBUG = LOG_CURSOR.isDebugEnabled(); 055 056 private static final String UNSUPPORTED_MSG = I18n.err( I18n.ERR_725 ); 057 private final boolean hasIndex; 058 private final Cursor<IndexEntry<String, String>> wrapped; 059 private final SubstringEvaluator evaluator; 060 private final IndexEntry<String, String> indexEntry = new IndexEntry<>(); 061 062 063 /** 064 * Creates a new instance of an SubstringCursor 065 * 066 * @param partitionTxn The transaction to use 067 * @param store The store 068 * @param substringEvaluator The SubstringEvaluator 069 * @throws LdapException If the creation failed 070 * @throws IndexNotFoundException If the index was not found 071 */ 072 @SuppressWarnings("unchecked") 073 public SubstringCursor( PartitionTxn partitionTxn, Store store, final SubstringEvaluator substringEvaluator ) 074 throws LdapException, IndexNotFoundException 075 { 076 if ( IS_DEBUG ) 077 { 078 LOG_CURSOR.debug( "Creating SubstringCursor {}", this ); 079 } 080 081 evaluator = substringEvaluator; 082 this.partitionTxn = partitionTxn; 083 hasIndex = store.hasIndexOn( evaluator.getExpression().getAttributeType() ); 084 085 if ( hasIndex ) 086 { 087 wrapped = ( ( Index<String, String> ) store.getIndex( evaluator.getExpression().getAttributeType() ) ) 088 .forwardCursor( partitionTxn ); 089 } 090 else 091 { 092 /* 093 * There is no index on the attribute here. We have no choice but 094 * to perform a full table scan but need to leverage an index for the 095 * wrapped Cursor. We know that all entries are listed under 096 * the ndn index and so this will enumerate over all entries. The 097 * substringEvaluator is used in an assertion to constrain the 098 * result set to only those entries matching the pattern. The 099 * substringEvaluator handles all the details of normalization and 100 * knows to use it, when it itself detects the lack of an index on 101 * the node's attribute. 102 */ 103 wrapped = new AllEntriesCursor( partitionTxn, store ); 104 } 105 } 106 107 108 /** 109 * {@inheritDoc} 110 */ 111 protected String getUnsupportedMessage() 112 { 113 return UNSUPPORTED_MSG; 114 } 115 116 117 /** 118 * {@inheritDoc} 119 */ 120 public void beforeFirst() throws LdapException, CursorException 121 { 122 checkNotClosed(); 123 124 if ( evaluator.getExpression().getInitial() != null && hasIndex ) 125 { 126 IndexEntry<String, String> beforeFirstIndexEntry = new IndexEntry<>(); 127 String normalizedKey = evaluator.getExpression().getAttributeType().getEquality().getNormalizer().normalize( 128 evaluator.getExpression().getInitial(), PrepareString.AssertionType.SUBSTRING_INITIAL ); 129 beforeFirstIndexEntry.setKey( normalizedKey ); 130 wrapped.before( beforeFirstIndexEntry ); 131 } 132 else 133 { 134 wrapped.beforeFirst(); 135 } 136 137 clear(); 138 } 139 140 141 private void clear() 142 { 143 setAvailable( false ); 144 indexEntry.setEntry( null ); 145 indexEntry.setId( null ); 146 indexEntry.setKey( null ); 147 } 148 149 150 /** 151 * {@inheritDoc} 152 */ 153 public void afterLast() throws LdapException, CursorException 154 { 155 checkNotClosed(); 156 157 // to keep the cursor always *after* the last matched tuple 158 // This fixes an issue if the last matched tuple is also the last record present in the 159 // index. In this case the wrapped cursor is positioning on the last tuple instead of positioning after that 160 wrapped.afterLast(); 161 clear(); 162 } 163 164 165 /** 166 * {@inheritDoc} 167 */ 168 public boolean first() throws LdapException, CursorException 169 { 170 beforeFirst(); 171 return next(); 172 } 173 174 175 private boolean evaluateCandidate( PartitionTxn partitionTxn, IndexEntry<String, String> indexEntry ) throws LdapException 176 { 177 if ( hasIndex ) 178 { 179 String key = indexEntry.getKey(); 180 return evaluator.getPattern().matcher( key ).matches(); 181 } 182 else 183 { 184 return evaluator.evaluate( partitionTxn, indexEntry ); 185 } 186 } 187 188 189 /** 190 * {@inheritDoc} 191 */ 192 public boolean last() throws LdapException, CursorException 193 { 194 afterLast(); 195 196 return previous(); 197 } 198 199 200 /** 201 * {@inheritDoc} 202 */ 203 public boolean previous() throws LdapException, CursorException 204 { 205 while ( wrapped.previous() ) 206 { 207 checkNotClosed(); 208 IndexEntry<String, String> entry = wrapped.get(); 209 210 if ( evaluateCandidate( partitionTxn, entry ) ) 211 { 212 setAvailable( true ); 213 this.indexEntry.setId( entry.getId() ); 214 this.indexEntry.setKey( entry.getKey() ); 215 this.indexEntry.setEntry( entry.getEntry() ); 216 return true; 217 } 218 } 219 220 clear(); 221 return false; 222 } 223 224 225 /** 226 * {@inheritDoc} 227 */ 228 public boolean next() throws LdapException, CursorException 229 { 230 while ( wrapped.next() ) 231 { 232 checkNotClosed(); 233 IndexEntry<String, String> entry = wrapped.get(); 234 235 if ( evaluateCandidate( partitionTxn, entry ) ) 236 { 237 setAvailable( true ); 238 this.indexEntry.setId( entry.getId() ); 239 this.indexEntry.setKey( entry.getKey() ); 240 this.indexEntry.setEntry( entry.getEntry() ); 241 242 return true; 243 } 244 } 245 246 clear(); 247 return false; 248 } 249 250 251 /** 252 * {@inheritDoc} 253 */ 254 public IndexEntry<String, String> get() throws CursorException 255 { 256 checkNotClosed(); 257 258 if ( available() ) 259 { 260 return indexEntry; 261 } 262 263 throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) ); 264 } 265 266 267 /** 268 * {@inheritDoc} 269 */ 270 @Override 271 public void close() throws IOException 272 { 273 if ( IS_DEBUG ) 274 { 275 LOG_CURSOR.debug( "Closing SubstringCursor {}", this ); 276 } 277 278 super.close(); 279 wrapped.close(); 280 clear(); 281 } 282 283 284 /** 285 * {@inheritDoc} 286 */ 287 @Override 288 public void close( Exception cause ) throws IOException 289 { 290 if ( IS_DEBUG ) 291 { 292 LOG_CURSOR.debug( "Closing SubstringCursor {}", this ); 293 } 294 295 super.close( cause ); 296 wrapped.close( cause ); 297 clear(); 298 } 299 300 301 /** 302 * @see Object#toString() 303 */ 304 @Override 305 public String toString( String tabs ) 306 { 307 StringBuilder sb = new StringBuilder(); 308 309 sb.append( tabs ).append( "SubstringCursor (" ); 310 311 if ( available() ) 312 { 313 sb.append( "available)" ); 314 } 315 else 316 { 317 sb.append( "absent)" ); 318 } 319 320 sb.append( "#index<" ).append( hasIndex ).append( "> :\n" ); 321 322 sb.append( tabs + " >>" ).append( evaluator ).append( '\n' ); 323 324 sb.append( wrapped.toString( tabs + " " ) ); 325 326 return sb.toString(); 327 } 328 329 330 /** 331 * @see Object#toString() 332 */ 333 public String toString() 334 { 335 return toString( "" ); 336 } 337}