diff options
author | Brett Porter <brett@apache.org> | 2009-10-14 05:41:45 +0000 |
---|---|---|
committer | Brett Porter <brett@apache.org> | 2009-10-14 05:41:45 +0000 |
commit | c61e55fd4f369147dca6a1529a6f6a498df5e4cd (patch) | |
tree | 3309c68bcfd84fa334443e0d08d8b2a2b3bcfc0f | |
parent | 2d73132f91afe1f68fe33ab7200f7d02bd71c3df (diff) | |
download | archiva-c61e55fd4f369147dca6a1529a6f6a498df5e4cd.tar.gz archiva-c61e55fd4f369147dca6a1529a6f6a498df5e4cd.zip |
[MRM-1262] move the index packing job to the end of a scan
git-svn-id: https://svn.apache.org/repos/asf/archiva/trunk@825012 13f79535-47bb-0310-9956-ffa450edef68
6 files changed, 203 insertions, 150 deletions
diff --git a/archiva-modules/archiva-base/archiva-consumers/archiva-lucene-consumers/src/main/java/org/apache/archiva/consumers/lucene/LuceneCleanupRemoveIndexedConsumer.java b/archiva-modules/archiva-base/archiva-consumers/archiva-lucene-consumers/src/main/java/org/apache/archiva/consumers/lucene/LuceneCleanupRemoveIndexedConsumer.java index a3aab84fe..4ad4c2f7d 100644 --- a/archiva-modules/archiva-base/archiva-consumers/archiva-lucene-consumers/src/main/java/org/apache/archiva/consumers/lucene/LuceneCleanupRemoveIndexedConsumer.java +++ b/archiva-modules/archiva-base/archiva-consumers/archiva-lucene-consumers/src/main/java/org/apache/archiva/consumers/lucene/LuceneCleanupRemoveIndexedConsumer.java @@ -66,7 +66,6 @@ public class LuceneCleanupRemoveIndexedConsumer public void completeScan() { - } public List<String> getIncludedTypes() @@ -102,6 +101,13 @@ public class LuceneCleanupRemoveIndexedConsumer log.debug( "Queueing indexing task '" + task.getName() + "' to remove the artifact from the index." ); scheduler.queueIndexingTask( task ); + + // note we finish immediately here since it isn't done repo-by-repo. It might be nice to ensure that is + // the case for optimisation though + task = + TaskCreator.createIndexingTask( repository.getId(), artifactFile, ArtifactIndexingTask.FINISH ); + log.debug( "Queueing indexing task + '" + task.getName() + "' to finish indexing." ); + scheduler.queueIndexingTask( task ); } } diff --git a/archiva-modules/archiva-base/archiva-consumers/archiva-lucene-consumers/src/main/java/org/apache/archiva/consumers/lucene/NexusIndexerConsumer.java b/archiva-modules/archiva-base/archiva-consumers/archiva-lucene-consumers/src/main/java/org/apache/archiva/consumers/lucene/NexusIndexerConsumer.java index 8e62eee38..69ce89f48 100644 --- a/archiva-modules/archiva-base/archiva-consumers/archiva-lucene-consumers/src/main/java/org/apache/archiva/consumers/lucene/NexusIndexerConsumer.java +++ b/archiva-modules/archiva-base/archiva-consumers/archiva-lucene-consumers/src/main/java/org/apache/archiva/consumers/lucene/NexusIndexerConsumer.java @@ -79,6 +79,9 @@ public class NexusIndexerConsumer repositoryContent = new ManagedDefaultRepositoryContent(); repositoryContent.setRepository( repository ); + + // TODO: investigate whether it is reasonable to create the indexing context here rather than file-by-file + // we may want to be able to "flush" it after every file without closing it though, if necessary } public void processFile( String path ) @@ -100,8 +103,18 @@ public class NexusIndexerConsumer } public void completeScan() - { - + { + ArtifactIndexingTask task = + TaskCreator.createIndexingTask( repositoryContent.getId(), null, ArtifactIndexingTask.FINISH ); + try + { + log.debug( "Queueing indexing task + '" + task.getName() + "' to finish indexing." ); + scheduler.queueIndexingTask( task ); + } + catch ( TaskQueueException e ) + { + log.error( "Error queueing task: " + task.getName() + ": " + e.getMessage(), e ); + } } public List<String> getExcludes() diff --git a/archiva-modules/archiva-scheduled/src/main/java/org/apache/maven/archiva/scheduled/executors/ArchivaIndexingTaskExecutor.java b/archiva-modules/archiva-scheduled/src/main/java/org/apache/maven/archiva/scheduled/executors/ArchivaIndexingTaskExecutor.java index c805c3f2c..4576eae0a 100644 --- a/archiva-modules/archiva-scheduled/src/main/java/org/apache/maven/archiva/scheduled/executors/ArchivaIndexingTaskExecutor.java +++ b/archiva-modules/archiva-scheduled/src/main/java/org/apache/maven/archiva/scheduled/executors/ArchivaIndexingTaskExecutor.java @@ -63,34 +63,34 @@ public class ArchivaIndexingTaskExecutor * @plexus.requirement */ private IndexerEngine indexerEngine; - + /** * @plexus.requirement */ private ArchivaConfiguration archivaConfiguration; - + /** * @plexus.requirement */ private IndexPacker indexPacker; - + private ArtifactContextProducer artifactContextProducer; - + public void executeTask( Task task ) throws TaskExecutionException { - synchronized( indexerEngine ) + synchronized ( indexerEngine ) { - ArtifactIndexingTask indexingTask = ( ArtifactIndexingTask ) task; - + ArtifactIndexingTask indexingTask = (ArtifactIndexingTask) task; + ManagedRepositoryConfiguration repository = archivaConfiguration.getConfiguration().findManagedRepositoryById( indexingTask.getRepositoryId() ); - + String indexDir = repository.getIndexDir(); File managedRepository = new File( repository.getLocation() ); - + File indexDirectory = null; - if( indexDir != null && !"".equals( indexDir ) ) + if ( indexDir != null && !"".equals( indexDir ) ) { indexDirectory = new File( repository.getIndexDir() ); } @@ -98,70 +98,75 @@ public class ArchivaIndexingTaskExecutor { indexDirectory = new File( managedRepository, ".indexer" ); } - + IndexingContext context = null; try { context = new DefaultIndexingContext( repository.getId(), repository.getId(), managedRepository, - indexDirectory, null, null, NexusIndexer.FULL_INDEX, false ); + indexDirectory, null, null, NexusIndexer.FULL_INDEX, false ); context.setSearchable( repository.isScanned() ); - - File artifactFile = indexingTask.getResourceFile(); - ArtifactContext ac = artifactContextProducer.getArtifactContext( context, artifactFile ); - - if( ac != null ) - { - if( indexingTask.getAction().equals( ArtifactIndexingTask.ADD ) ) + + if ( ArtifactIndexingTask.FINISH.equals( indexingTask.getAction() ) ) + { + final File indexLocation = new File( managedRepository, ".index" ); + IndexPackingRequest request = new IndexPackingRequest( context, indexLocation ); + indexPacker.packIndex( request ); + + log.debug( "Index file packaged at '" + indexLocation.getPath() + "'." ); + } + else + { + File artifactFile = indexingTask.getResourceFile(); + ArtifactContext ac = artifactContextProducer.getArtifactContext( context, artifactFile ); + + if ( ac != null ) { - boolean add = true; - IndexReader r = context.getIndexReader(); - for ( int i = 0; i < r.numDocs(); i++ ) + if ( indexingTask.getAction().equals( ArtifactIndexingTask.ADD ) ) { - if ( !r.isDeleted( i ) ) + boolean add = true; + IndexReader r = context.getIndexReader(); + for ( int i = 0; i < r.numDocs(); i++ ) { - Document d = r.document( i ); - String uinfo = d.get( ArtifactInfo.UINFO ); - if( ac.getArtifactInfo().getUinfo().equals( uinfo ) ) + if ( !r.isDeleted( i ) ) { - add = false; - break; + Document d = r.document( i ); + String uinfo = d.get( ArtifactInfo.UINFO ); + if ( ac.getArtifactInfo().getUinfo().equals( uinfo ) ) + { + add = false; + break; + } } } - } - - if( add ) - { - log.debug( "Adding artifact '" + ac.getArtifactInfo() + "' to index.." ); - indexerEngine.index( context, ac ); - context.optimize(); + + if ( add ) + { + log.debug( "Adding artifact '" + ac.getArtifactInfo() + "' to index.." ); + indexerEngine.index( context, ac ); + context.optimize(); + } + else + { + log.debug( "Updating artifact '" + ac.getArtifactInfo() + "' in index.." ); + indexerEngine.update( context, ac ); + context.optimize(); + } } else { - log.debug( "Updating artifact '" + ac.getArtifactInfo() + "' in index.." ); - indexerEngine.update( context, ac ); + log.debug( "Removing artifact '" + ac.getArtifactInfo() + "' from index.." ); + indexerEngine.remove( context, ac ); context.optimize(); } } - else - { - log.debug( "Removing artifact '" + ac.getArtifactInfo() + "' from index.." ); - indexerEngine.remove( context, ac ); - context.optimize(); - } - - final File indexLocation = new File( managedRepository, ".index" ); - IndexPackingRequest request = new IndexPackingRequest( context, indexLocation ); - indexPacker.packIndex( request ); - - log.debug( "Index file packaged at '" + indexLocation.getPath() + "'." ); - } + } } catch ( IOException e ) { - log.error( "Error occurred while executing indexing task '" + indexingTask.getName() + "'" ); - throw new TaskExecutionException( "Error occurred while executing indexing task '" + - indexingTask.getName() + "'" ); + log.error( "Error occurred while executing indexing task '" + indexingTask.getName() + "'" ); + throw new TaskExecutionException( "Error occurred while executing indexing task '" + + indexingTask.getName() + "'" ); } catch ( UnsupportedExistingLuceneIndexException e ) { @@ -170,10 +175,10 @@ public class ArchivaIndexingTaskExecutor } finally { - if( context != null ) + if ( context != null ) { try - { + { context.close( false ); } catch ( IOException e ) @@ -190,7 +195,7 @@ public class ArchivaIndexingTaskExecutor throws InitializationException { log.info( "Initialized " + this.getClass().getName() ); - + artifactContextProducer = new DefaultArtifactContextProducer(); } diff --git a/archiva-modules/archiva-scheduled/src/main/java/org/apache/maven/archiva/scheduled/tasks/ArtifactIndexingTask.java b/archiva-modules/archiva-scheduled/src/main/java/org/apache/maven/archiva/scheduled/tasks/ArtifactIndexingTask.java index 5af3c583b..f0b23cba8 100644 --- a/archiva-modules/archiva-scheduled/src/main/java/org/apache/maven/archiva/scheduled/tasks/ArtifactIndexingTask.java +++ b/archiva-modules/archiva-scheduled/src/main/java/org/apache/maven/archiva/scheduled/tasks/ArtifactIndexingTask.java @@ -30,6 +30,8 @@ public class ArtifactIndexingTask public static final String DELETE = "delete"; + public static final String FINISH = "finish"; + String repositoryId; String name; diff --git a/archiva-modules/archiva-scheduled/src/main/java/org/apache/maven/archiva/scheduled/tasks/TaskCreator.java b/archiva-modules/archiva-scheduled/src/main/java/org/apache/maven/archiva/scheduled/tasks/TaskCreator.java index 32ea4620f..227e8c46b 100644 --- a/archiva-modules/archiva-scheduled/src/main/java/org/apache/maven/archiva/scheduled/tasks/TaskCreator.java +++ b/archiva-modules/archiva-scheduled/src/main/java/org/apache/maven/archiva/scheduled/tasks/TaskCreator.java @@ -25,67 +25,71 @@ import org.apache.commons.lang.StringUtils; import org.apache.maven.archiva.scheduled.DefaultArchivaTaskScheduler; /** - * TaskCreator - * - * Convenience class for creating Archiva tasks. + * TaskCreator Convenience class for creating Archiva tasks. */ public class TaskCreator { public static RepositoryTask createRepositoryTask( String repositoryId, String taskNameSuffix ) { String suffix = ""; - if( !StringUtils.isEmpty( taskNameSuffix ) ) + if ( !StringUtils.isEmpty( taskNameSuffix ) ) { suffix = ":" + taskNameSuffix; } - + RepositoryTask task = new RepositoryTask(); task.setRepositoryId( repositoryId ); task.setName( DefaultArchivaTaskScheduler.REPOSITORY_JOB + ":" + repositoryId + suffix ); task.setQueuePolicy( ArchivaTask.QUEUE_POLICY_WAIT ); - + return task; } - + public static RepositoryTask createRepositoryTask( String repositoryId, String taskNameSuffix, boolean scanAll ) { RepositoryTask task = createRepositoryTask( repositoryId, taskNameSuffix ); task.setScanAll( scanAll ); - + return task; } - + public static RepositoryTask createRepositoryTask( String repositoryId, String taskNameSuffix, File resourceFile, boolean updateRelatedArtifacts ) { RepositoryTask task = createRepositoryTask( repositoryId, taskNameSuffix ); task.setResourceFile( resourceFile ); task.setUpdateRelatedArtifacts( updateRelatedArtifacts ); - + return task; } - + public static RepositoryTask createRepositoryTask( String repositoryId, String taskNameSuffix, File resourceFile, boolean updateRelatedArtifacts, boolean scanAll ) { RepositoryTask task = createRepositoryTask( repositoryId, taskNameSuffix, resourceFile, updateRelatedArtifacts ); task.setScanAll( scanAll ); - + return task; } - - public static ArtifactIndexingTask createIndexingTask( String repositoryId, File resource, - String action ) + + public static ArtifactIndexingTask createIndexingTask( String repositoryId, File resource, String action ) { ArtifactIndexingTask task = new ArtifactIndexingTask(); task.setRepositoryId( repositoryId ); - task.setName( DefaultArchivaTaskScheduler.INDEXING_JOB + ":" + repositoryId + ":" + resource.getName() + ":" + - action ); + + String name = DefaultArchivaTaskScheduler.INDEXING_JOB + ":" + repositoryId; + if ( resource != null ) + { + name = name + ":" + resource.getName(); + } + name = name + ":" + action; + + task.setName( name ); task.setAction( action ); task.setQueuePolicy( ArchivaTask.QUEUE_POLICY_WAIT ); task.setResourceFile( resource ); - + return task; } - + } diff --git a/archiva-modules/archiva-scheduled/src/test/java/org/apache/maven/archiva/scheduled/executors/ArchivaIndexingTaskExecutorTest.java b/archiva-modules/archiva-scheduled/src/test/java/org/apache/maven/archiva/scheduled/executors/ArchivaIndexingTaskExecutorTest.java index 9c97364e5..5c23800c7 100644 --- a/archiva-modules/archiva-scheduled/src/test/java/org/apache/maven/archiva/scheduled/executors/ArchivaIndexingTaskExecutorTest.java +++ b/archiva-modules/archiva-scheduled/src/test/java/org/apache/maven/archiva/scheduled/executors/ArchivaIndexingTaskExecutorTest.java @@ -57,28 +57,29 @@ public class ArchivaIndexingTaskExecutorTest extends PlexusInSpringTestCase { private ArchivaIndexingTaskExecutor indexingExecutor; - + private IndexerEngine indexerEngine; - + private IndexPacker indexPacker; - + private MockControl archivaConfigControl; - + private ArchivaConfiguration archivaConfiguration; - + private ManagedRepositoryConfiguration repositoryConfig; - + private Configuration configuration; - + private NexusIndexer indexer; - - protected void setUp() throws Exception + + protected void setUp() + throws Exception { super.setUp(); - + indexingExecutor = new ArchivaIndexingTaskExecutor(); - indexingExecutor.initialize(); - + indexingExecutor.initialize(); + repositoryConfig = new ManagedRepositoryConfiguration(); repositoryConfig.setId( "test-repo" ); repositoryConfig.setLocation( getBasedir() + "/target/test-classes/test-repo" ); @@ -87,36 +88,37 @@ public class ArchivaIndexingTaskExecutorTest repositoryConfig.setScanned( true ); repositoryConfig.setSnapshots( false ); repositoryConfig.setReleases( true ); - + configuration = new Configuration(); configuration.addManagedRepository( repositoryConfig ); - + archivaConfigControl = MockControl.createControl( ArchivaConfiguration.class ); - archivaConfiguration = ( ArchivaConfiguration ) archivaConfigControl.getMock(); - - indexer = ( NexusIndexer ) lookup( NexusIndexer.class ); - indexerEngine = ( IndexerEngine ) lookup ( IndexerEngine.class ); - indexPacker = ( IndexPacker ) lookup( IndexPacker.class ); - - indexingExecutor.setIndexerEngine( indexerEngine ); - indexingExecutor.setIndexPacker( indexPacker ); + archivaConfiguration = (ArchivaConfiguration) archivaConfigControl.getMock(); + + indexer = (NexusIndexer) lookup( NexusIndexer.class ); + indexerEngine = (IndexerEngine) lookup( IndexerEngine.class ); + indexPacker = (IndexPacker) lookup( IndexPacker.class ); + + indexingExecutor.setIndexerEngine( indexerEngine ); + indexingExecutor.setIndexPacker( indexPacker ); indexingExecutor.setArchivaConfiguration( archivaConfiguration ); } - - protected void tearDown() throws Exception + + protected void tearDown() + throws Exception { // delete created index in the repository File indexDir = new File( repositoryConfig.getLocation(), ".indexer" ); FileUtils.deleteDirectory( indexDir ); assertFalse( indexDir.exists() ); - + indexDir = new File( repositoryConfig.getLocation(), ".index" ); FileUtils.deleteDirectory( indexDir ); assertFalse( indexDir.exists() ); - + super.tearDown(); } - + public void testAddArtifactToIndex() throws Exception { @@ -126,38 +128,41 @@ public class ArchivaIndexingTaskExecutorTest ArtifactIndexingTask task = TaskCreator.createIndexingTask( repositoryConfig.getId(), artifactFile, ArtifactIndexingTask.ADD ); - + archivaConfigControl.expectAndReturn( archivaConfiguration.getConfiguration(), configuration ); - + archivaConfigControl.replay(); - + indexingExecutor.executeTask( task ); - + archivaConfigControl.verify(); - - BooleanQuery q = new BooleanQuery(); + + BooleanQuery q = new BooleanQuery(); q.add( indexer.constructQuery( ArtifactInfo.GROUP_ID, "org.apache.archiva" ), Occur.SHOULD ); q.add( indexer.constructQuery( ArtifactInfo.ARTIFACT_ID, "archiva-index-methods-jar-test" ), Occur.SHOULD ); - - IndexingContext context = indexer.addIndexingContext( repositoryConfig.getId(), repositoryConfig.getId(), new File( repositoryConfig.getLocation() ), - new File( repositoryConfig.getLocation(), ".indexer" ), null, null, NexusIndexer.FULL_INDEX ); + + IndexingContext context = + indexer.addIndexingContext( repositoryConfig.getId(), repositoryConfig.getId(), + new File( repositoryConfig.getLocation() ), + new File( repositoryConfig.getLocation(), ".indexer" ), null, null, + NexusIndexer.FULL_INDEX ); context.setSearchable( true ); - + FlatSearchRequest request = new FlatSearchRequest( q ); FlatSearchResponse response = indexer.searchFlat( request ); - + assertTrue( new File( repositoryConfig.getLocation(), ".indexer" ).exists() ); - assertTrue( new File( repositoryConfig.getLocation(), ".index" ).exists() ); + assertFalse( new File( repositoryConfig.getLocation(), ".index" ).exists() ); assertEquals( 1, response.getTotalHits() ); - + Set<ArtifactInfo> results = response.getResults(); - + ArtifactInfo artifactInfo = (ArtifactInfo) results.iterator().next(); assertEquals( "org.apache.archiva", artifactInfo.groupId ); assertEquals( "archiva-index-methods-jar-test", artifactInfo.artifactId ); assertEquals( "test-repo", artifactInfo.repository ); } - + public void testUpdateArtifactInIndex() throws Exception { @@ -167,30 +172,30 @@ public class ArchivaIndexingTaskExecutorTest ArtifactIndexingTask task = TaskCreator.createIndexingTask( repositoryConfig.getId(), artifactFile, ArtifactIndexingTask.ADD ); - + archivaConfigControl.expectAndReturn( archivaConfiguration.getConfiguration(), configuration, 2 ); - + archivaConfigControl.replay(); - + indexingExecutor.executeTask( task ); indexingExecutor.executeTask( task ); - + archivaConfigControl.verify(); - - BooleanQuery q = new BooleanQuery(); + + BooleanQuery q = new BooleanQuery(); q.add( indexer.constructQuery( ArtifactInfo.GROUP_ID, "org.apache.archiva" ), Occur.SHOULD ); q.add( indexer.constructQuery( ArtifactInfo.ARTIFACT_ID, "archiva-index-methods-jar-test" ), Occur.SHOULD ); - + IndexSearcher searcher = new IndexSearcher( repositoryConfig.getLocation() + "/.indexer" ); TopDocs topDocs = searcher.search( q, null, 10 ); - + assertTrue( new File( repositoryConfig.getLocation(), ".indexer" ).exists() ); - assertTrue( new File( repositoryConfig.getLocation(), ".index" ).exists() ); - + assertFalse( new File( repositoryConfig.getLocation(), ".index" ).exists() ); + // should only return 1 hit! assertEquals( 1, topDocs.totalHits ); } - + public void testRemoveArtifactFromIndex() throws Exception { @@ -200,48 +205,54 @@ public class ArchivaIndexingTaskExecutorTest ArtifactIndexingTask task = TaskCreator.createIndexingTask( repositoryConfig.getId(), artifactFile, ArtifactIndexingTask.ADD ); - - archivaConfigControl.expectAndReturn( archivaConfiguration.getConfiguration(), configuration, 2 ); - + + archivaConfigControl.expectAndReturn( archivaConfiguration.getConfiguration(), configuration, 3 ); + archivaConfigControl.replay(); - + // add artifact to index indexingExecutor.executeTask( task ); - - BooleanQuery q = new BooleanQuery(); + + task = TaskCreator.createIndexingTask( repositoryConfig.getId(), artifactFile, ArtifactIndexingTask.FINISH ); + + // add artifact to index + indexingExecutor.executeTask( task ); + + BooleanQuery q = new BooleanQuery(); q.add( indexer.constructQuery( ArtifactInfo.GROUP_ID, "org.apache.archiva" ), Occur.SHOULD ); q.add( indexer.constructQuery( ArtifactInfo.ARTIFACT_ID, "archiva-index-methods-jar-test" ), Occur.SHOULD ); - + IndexSearcher searcher = new IndexSearcher( repositoryConfig.getLocation() + "/.indexer" ); TopDocs topDocs = searcher.search( q, null, 10 ); - + assertTrue( new File( repositoryConfig.getLocation(), ".indexer" ).exists() ); assertTrue( new File( repositoryConfig.getLocation(), ".index" ).exists() ); - + // should return 1 hit assertEquals( 1, topDocs.totalHits ); - + // remove added artifact from index - task = - TaskCreator.createIndexingTask( repositoryConfig.getId(), artifactFile, ArtifactIndexingTask.DELETE ); + task = TaskCreator.createIndexingTask( repositoryConfig.getId(), artifactFile, ArtifactIndexingTask.DELETE ); indexingExecutor.executeTask( task ); - + archivaConfigControl.verify(); - - q = new BooleanQuery(); + + q = new BooleanQuery(); q.add( indexer.constructQuery( ArtifactInfo.GROUP_ID, "org.apache.archiva" ), Occur.SHOULD ); q.add( indexer.constructQuery( ArtifactInfo.ARTIFACT_ID, "archiva-index-methods-jar-test" ), Occur.SHOULD ); - + searcher = new IndexSearcher( repositoryConfig.getLocation() + "/.indexer" ); topDocs = searcher.search( q, null, 10 ); - + assertTrue( new File( repositoryConfig.getLocation(), ".indexer" ).exists() ); assertTrue( new File( repositoryConfig.getLocation(), ".index" ).exists() ); - + // artifact should have been removed from the index! assertEquals( 0, topDocs.totalHits ); + + // TODO: test it was removed from the packaged index also } - + public void testPackagedIndex() throws Exception { @@ -259,6 +270,18 @@ public class ArchivaIndexingTaskExecutorTest indexingExecutor.executeTask( task ); archivaConfigControl.verify(); + + archivaConfigControl.reset(); + + task = TaskCreator.createIndexingTask( repositoryConfig.getId(), artifactFile, ArtifactIndexingTask.FINISH ); + + archivaConfigControl.expectAndReturn( archivaConfiguration.getConfiguration(), configuration ); + + archivaConfigControl.replay(); + + indexingExecutor.executeTask( task ); + + archivaConfigControl.verify(); assertTrue( new File( repositoryConfig.getLocation(), ".indexer" ).exists() ); assertTrue( new File( repositoryConfig.getLocation(), ".index" ).exists() ); |