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 @Override 204 public boolean previous() throws LdapException, CursorException 205 { 206 while ( wrapped.previous() ) 207 { 208 checkNotClosed(); 209 IndexEntry<String, String> entry = wrapped.get(); 210 211 if ( evaluateCandidate( partitionTxn, entry ) ) 212 { 213 setAvailable( true ); 214 this.indexEntry.setId( entry.getId() ); 215 this.indexEntry.setKey( entry.getKey() ); 216 this.indexEntry.setEntry( entry.getEntry() ); 217 return true; 218 } 219 } 220 221 clear(); 222 return false; 223 } 224 225 226 /** 227 * {@inheritDoc} 228 */ 229 @Override 230 public boolean next() throws LdapException, CursorException 231 { 232 while ( wrapped.next() ) 233 { 234 checkNotClosed(); 235 IndexEntry<String, String> entry = wrapped.get(); 236 237 if ( evaluateCandidate( partitionTxn, entry ) ) 238 { 239 setAvailable( true ); 240 this.indexEntry.setId( entry.getId() ); 241 this.indexEntry.setKey( entry.getKey() ); 242 this.indexEntry.setEntry( entry.getEntry() ); 243 244 return true; 245 } 246 } 247 248 clear(); 249 return false; 250 } 251 252 253 /** 254 * {@inheritDoc} 255 */ 256 public IndexEntry<String, String> get() throws CursorException 257 { 258 checkNotClosed(); 259 260 if ( available() ) 261 { 262 return indexEntry; 263 } 264 265 throw new InvalidCursorPositionException( I18n.err( I18n.ERR_708 ) ); 266 } 267 268 269 /** 270 * {@inheritDoc} 271 */ 272 @Override 273 public void close() throws IOException 274 { 275 if ( IS_DEBUG ) 276 { 277 LOG_CURSOR.debug( "Closing SubstringCursor {}", this ); 278 } 279 280 super.close(); 281 wrapped.close(); 282 clear(); 283 } 284 285 286 /** 287 * {@inheritDoc} 288 */ 289 @Override 290 public void close( Exception cause ) throws IOException 291 { 292 if ( IS_DEBUG ) 293 { 294 LOG_CURSOR.debug( "Closing SubstringCursor {}", this ); 295 } 296 297 super.close( cause ); 298 wrapped.close( cause ); 299 clear(); 300 } 301 302 303 /** 304 * @see Object#toString() 305 */ 306 @Override 307 public String toString( String tabs ) 308 { 309 StringBuilder sb = new StringBuilder(); 310 311 sb.append( tabs ).append( "SubstringCursor (" ); 312 313 if ( available() ) 314 { 315 sb.append( "available)" ); 316 } 317 else 318 { 319 sb.append( "absent)" ); 320 } 321 322 sb.append( "#index<" ).append( hasIndex ).append( "> :\n" ); 323 324 sb.append( tabs + " >>" ).append( evaluator ).append( '\n' ); 325 326 sb.append( wrapped.toString( tabs + " " ) ); 327 328 return sb.toString(); 329 } 330 331 332 /** 333 * @see Object#toString() 334 */ 335 public String toString() 336 { 337 return toString( "" ); 338 } 339}