aboutsummaryrefslogtreecommitdiffstats
path: root/archiva-modules/archiva-maven/archiva-maven-scheduler
diff options
context:
space:
mode:
authorMartin Stockhammer <martin_s@apache.org>2019-05-06 22:08:19 +0200
committerMartin Stockhammer <martin_s@apache.org>2019-05-06 22:08:19 +0200
commita13bfb5a01af459e734d2f5b4b36d227a8a03a91 (patch)
tree6c9f3e512e6556a93fb5b1e55d308da13cb5c312 /archiva-modules/archiva-maven/archiva-maven-scheduler
parent79bee6e84771de9b71e7d957e9f10a17364520ed (diff)
downloadarchiva-a13bfb5a01af459e734d2f5b4b36d227a8a03a91.tar.gz
archiva-a13bfb5a01af459e734d2f5b4b36d227a8a03a91.zip
Moving maven specific modules
Diffstat (limited to 'archiva-modules/archiva-maven/archiva-maven-scheduler')
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-scheduler/pom.xml186
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/ArchivaIndexingTaskExecutor.java302
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/DefaultDownloadRemoteIndexScheduler.java211
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/DefaultIndexUpdateSideEffect.java47
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/DownloadRemoteIndexTask.java380
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/DownloadRemoteIndexTaskRequest.java116
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/resources/META-INF/spring-context.xml48
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/index-updates/nexus-maven-repository-index.gzbin0 -> 95880 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/index-updates/nexus-maven-repository-index.properties36
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/index-updates/nexus-maven-repository-index.zipbin0 -> 201643 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/java/org/apache/archiva/scheduler/indexing/maven/ArchivaIndexingTaskExecutorTest.java352
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/java/org/apache/archiva/scheduler/indexing/maven/DownloadRemoteIndexTaskTest.java187
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/resources/log4j2-test.xml42
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/resources/spring-context.xml99
14 files changed, 2006 insertions, 0 deletions
diff --git a/archiva-modules/archiva-maven/archiva-maven-scheduler/pom.xml b/archiva-modules/archiva-maven/archiva-maven-scheduler/pom.xml
new file mode 100644
index 000000000..52763e1a9
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-scheduler/pom.xml
@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <artifactId>archiva-maven</artifactId>
+ <groupId>org.apache.archiva.maven</groupId>
+ <version>3.0.0-SNAPSHOT</version>
+ </parent>
+
+
+ <properties>
+ <site.staging.base>${project.parent.parent.basedir}</site.staging.base>
+ </properties>
+
+ <artifactId>archiva-maven-scheduler</artifactId>
+ <name>Archiva :: Maven :: Scheduler</name>
+
+ <description>Scheduler for downloading remote Maven indexes.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-configuration</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-scheduler-indexing</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-repository-layer</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva.maven</groupId>
+ <artifactId>archiva-maven-proxy</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.maven.wagon</groupId>
+ <artifactId>wagon-http</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.wagon</groupId>
+ <artifactId>wagon-http-lightweight</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Test dependencies -->
+ <dependency>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-repository-admin-default</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva.maven</groupId>
+ <artifactId>archiva-maven-indexer</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva.maven</groupId>
+ <artifactId>archiva-maven-repository</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-test-utils</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${jetty.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-plus</artifactId>
+ <version>${jetty.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-mock</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.archiva.redback</groupId>
+ <artifactId>redback-rbac-cached</artifactId>
+ <scope>test</scope>
+ <version>${redback.version}</version>
+ </dependency>
+
+
+ <dependency>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-scheduler-repository</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-slf4j-impl</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.archiva.redback</groupId>
+ <artifactId>redback-common-test-resources</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.ow2.asm</groupId>
+ <artifactId>asm</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.rat</groupId>
+ <artifactId>apache-rat-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>src/test/index-updates/nexus-maven-repository-index.properties</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemPropertyVariables>
+ <appserver.base>${project.build.directory}/appserver-base</appserver.base>
+ <plexus.home>${project.build.directory}/appserver-base</plexus.home>
+ <derby.system.home>${project.build.directory}/appserver-base</derby.system.home>
+ <redback.jdbc.url>${redbackTestJdbcUrl}</redback.jdbc.url>
+ <redback.jdbc.driver.name>${redbackTestJdbcDriver}</redback.jdbc.driver.name>
+ <basedir>${basedir}</basedir>
+ <archiva.repositorySessionFactory.id>mock</archiva.repositorySessionFactory.id>
+ <openjpa.Log>${openjpa.Log}</openjpa.Log>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/ArchivaIndexingTaskExecutor.java b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/ArchivaIndexingTaskExecutor.java
new file mode 100644
index 000000000..8192cce2b
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/ArchivaIndexingTaskExecutor.java
@@ -0,0 +1,302 @@
+package org.apache.archiva.scheduler.indexing.maven;
+
+/*
+ * 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 Li
+ * cense 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.
+ */
+
+import org.apache.archiva.indexer.ArchivaIndexingContext;
+import org.apache.archiva.indexer.UnsupportedBaseContextException;
+import org.apache.archiva.redback.components.taskqueue.Task;
+import org.apache.archiva.redback.components.taskqueue.execution.TaskExecutionException;
+import org.apache.archiva.redback.components.taskqueue.execution.TaskExecutor;
+import org.apache.archiva.repository.ManagedRepository;
+import org.apache.archiva.repository.features.IndexCreationFeature;
+import org.apache.archiva.scheduler.indexing.ArtifactIndexingTask;
+import org.apache.maven.index.ArtifactContext;
+import org.apache.maven.index.ArtifactContextProducer;
+import org.apache.maven.index.DefaultScannerListener;
+import org.apache.maven.index.FlatSearchRequest;
+import org.apache.maven.index.FlatSearchResponse;
+import org.apache.maven.index.Indexer;
+import org.apache.maven.index.IndexerEngine;
+import org.apache.maven.index.MAVEN;
+import org.apache.maven.index.Scanner;
+import org.apache.maven.index.ScanningRequest;
+import org.apache.maven.index.ScanningResult;
+import org.apache.maven.index.context.IndexingContext;
+import org.apache.maven.index.expr.SourcedSearchExpression;
+import org.apache.maven.index.packer.IndexPacker;
+import org.apache.maven.index.packer.IndexPackingRequest;
+import org.apache.maven.index_shaded.lucene.search.BooleanClause;
+import org.apache.maven.index_shaded.lucene.search.BooleanQuery;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.nio.file.Path;
+
+/**
+ * ArchivaIndexingTaskExecutor Executes all indexing tasks. Adding, updating and removing artifacts from the index are
+ * all performed by this executor. Add and update artifact in index tasks are added in the indexing task queue by the
+ * NexusIndexerConsumer while remove artifact from index tasks are added by the LuceneCleanupRemoveIndexedConsumer.
+ */
+@Service( "taskExecutor#indexing" )
+public class ArchivaIndexingTaskExecutor
+ implements TaskExecutor
+{
+ private Logger log = LoggerFactory.getLogger( ArchivaIndexingTaskExecutor.class );
+
+ @Inject
+ private IndexPacker indexPacker;
+
+ @Inject
+ private ArtifactContextProducer artifactContextProducer;
+
+ @Inject
+ private Indexer indexer;
+
+ @Inject
+ private Scanner scanner;
+
+ @Inject
+ IndexerEngine indexerEngine;
+
+ /**
+ * depending on current {@link Task} you have.
+ * If {@link org.apache.archiva.scheduler.indexing.ArtifactIndexingTask.Action#FINISH} &amp;&amp; isExecuteOnEntireRepo:
+ * repository will be scanned.
+ *
+ * @param task
+ * @throws TaskExecutionException
+ */
+ @Override
+ public void executeTask( Task task )
+ throws TaskExecutionException
+ {
+ ArtifactIndexingTask indexingTask = (ArtifactIndexingTask) task;
+
+ ManagedRepository repository = indexingTask.getRepository( );
+ ArchivaIndexingContext archivaContext = indexingTask.getContext( );
+ IndexingContext context = null;
+ try
+ {
+ context = archivaContext.getBaseContext( IndexingContext.class );
+ }
+ catch ( UnsupportedBaseContextException e )
+ {
+ throw new TaskExecutionException( "Bad repository type.", e );
+ }
+
+ if ( ArtifactIndexingTask.Action.FINISH.equals( indexingTask.getAction( ) )
+ && indexingTask.isExecuteOnEntireRepo( ) )
+ {
+ long start = System.currentTimeMillis( );
+ try
+ {
+ context.updateTimestamp( );
+ DefaultScannerListener listener = new DefaultScannerListener( context, indexerEngine, true, null );
+ ScanningRequest request = new ScanningRequest( context, listener );
+ ScanningResult result = scanner.scan( request );
+ if ( result.hasExceptions( ) )
+ {
+ log.error( "Exceptions occured during index scan of " + context.getId( ) );
+ result.getExceptions( ).stream( ).map( e -> e.getMessage( ) ).distinct( ).limit( 5 ).forEach(
+ s -> log.error( "Message: " + s )
+ );
+ }
+ }
+ catch ( IOException e )
+ {
+ log.error( "Error during context scan {}: {}", context.getId( ), context.getIndexDirectory( ) );
+ }
+ long end = System.currentTimeMillis( );
+ log.info( "indexed maven repository: {}, onlyUpdate: {}, time {} ms", repository.getId( ),
+ indexingTask.isOnlyUpdate( ), ( end - start ) );
+ log.debug( "Finishing indexing task on repo: {}", repository.getId( ) );
+ finishIndexingTask( indexingTask, repository, context );
+ }
+ else
+ {
+ // create context if not a repo scan request
+ if ( !indexingTask.isExecuteOnEntireRepo( ) )
+ {
+ try
+ {
+ log.debug( "Creating indexing context on resource: {}", //
+ ( indexingTask.getResourceFile( ) == null
+ ? "none"
+ : indexingTask.getResourceFile( ) ) );
+ archivaContext = repository.getIndexingContext( );
+ context = archivaContext.getBaseContext( IndexingContext.class );
+ }
+ catch ( UnsupportedBaseContextException e )
+ {
+ log.error( "Error occurred while creating context: {}", e.getMessage( ) );
+ throw new TaskExecutionException( "Error occurred while creating context: " + e.getMessage( ), e );
+ }
+ }
+
+ if ( context == null || context.getIndexDirectory( ) == null )
+ {
+ throw new TaskExecutionException( "Trying to index an artifact but the context is already closed" );
+ }
+
+ try
+ {
+ Path artifactFile = indexingTask.getResourceFile( );
+ if ( artifactFile == null )
+ {
+ log.debug( "no artifact pass in indexing task so skip it" );
+ }
+ else
+ {
+ ArtifactContext ac = artifactContextProducer.getArtifactContext( context, artifactFile.toFile( ) );
+
+ if ( ac != null )
+ {
+ // MRM-1779 pom must be indexed too
+ // TODO make that configurable?
+ if ( artifactFile.getFileName( ).toString( ).endsWith( ".pom" ) )
+ {
+ ac.getArtifactInfo( ).setFileExtension( "pom" );
+ ac.getArtifactInfo( ).setPackaging( "pom" );
+ ac.getArtifactInfo( ).setClassifier( "pom" );
+ }
+ if ( indexingTask.getAction( ).equals( ArtifactIndexingTask.Action.ADD ) )
+ {
+ //IndexSearcher s = context.getIndexSearcher();
+ //String uinfo = ac.getArtifactInfo().getUinfo();
+ //TopDocs d = s.search( new TermQuery( new Term( ArtifactInfo.UINFO, uinfo ) ), 1 );
+
+ BooleanQuery.Builder qb = new BooleanQuery.Builder();
+ qb.add( indexer.constructQuery( MAVEN.GROUP_ID, new SourcedSearchExpression(
+ ac.getArtifactInfo( ).getGroupId( ) ) ), BooleanClause.Occur.MUST );
+ qb.add( indexer.constructQuery( MAVEN.ARTIFACT_ID, new SourcedSearchExpression(
+ ac.getArtifactInfo( ).getArtifactId( ) ) ), BooleanClause.Occur.MUST );
+ qb.add( indexer.constructQuery( MAVEN.VERSION, new SourcedSearchExpression(
+ ac.getArtifactInfo( ).getVersion( ) ) ), BooleanClause.Occur.MUST );
+ if ( ac.getArtifactInfo( ).getClassifier( ) != null )
+ {
+ qb.add( indexer.constructQuery( MAVEN.CLASSIFIER, new SourcedSearchExpression(
+ ac.getArtifactInfo( ).getClassifier( ) ) ), BooleanClause.Occur.MUST );
+ }
+ if ( ac.getArtifactInfo( ).getPackaging( ) != null )
+ {
+ qb.add( indexer.constructQuery( MAVEN.PACKAGING, new SourcedSearchExpression(
+ ac.getArtifactInfo( ).getPackaging( ) ) ), BooleanClause.Occur.MUST );
+ }
+ FlatSearchRequest flatSearchRequest = new FlatSearchRequest( qb.build(), context );
+ FlatSearchResponse flatSearchResponse = indexer.searchFlat( flatSearchRequest );
+ if ( flatSearchResponse.getResults( ).isEmpty( ) )
+ {
+ log.debug( "Adding artifact '{}' to index..", ac.getArtifactInfo( ) );
+ indexerEngine.index( context, ac );
+ }
+ else
+ {
+ log.debug( "Updating artifact '{}' in index..", ac.getArtifactInfo( ) );
+ // TODO check if update exists !!
+ indexerEngine.update( context, ac );
+ }
+
+ context.updateTimestamp( );
+ context.commit( );
+
+
+ }
+ else
+ {
+ log.debug( "Removing artifact '{}' from index..", ac.getArtifactInfo( ) );
+ indexerEngine.remove( context, ac );
+ }
+ }
+ }
+ // close the context if not a repo scan request
+ if ( !indexingTask.isExecuteOnEntireRepo( ) )
+ {
+ log.debug( "Finishing indexing task on resource file : {}", indexingTask.getResourceFile( ) != null
+ ? indexingTask.getResourceFile( )
+ : " none " );
+ finishIndexingTask( indexingTask, repository, context );
+ }
+ }
+ catch ( IOException e )
+ {
+ log.error( "Error occurred while executing indexing task '{}': {}", indexingTask, e.getMessage( ),
+ e );
+ throw new TaskExecutionException( "Error occurred while executing indexing task '" + indexingTask + "'",
+ e );
+ }
+ }
+
+ }
+
+ private void finishIndexingTask( ArtifactIndexingTask indexingTask, ManagedRepository repository,
+ IndexingContext context )
+ throws TaskExecutionException
+ {
+ try
+ {
+
+ log.debug( "Finishing indexing" );
+ context.optimize( );
+
+ if ( repository.supportsFeature( IndexCreationFeature.class ) )
+ {
+ IndexCreationFeature icf = repository.getFeature( IndexCreationFeature.class ).get( );
+ if ( !icf.isSkipPackedIndexCreation( ) && icf.getLocalPackedIndexPath( ) != null )
+ {
+
+ log.debug( "Creating packed index from {} on {}", context.getIndexDirectoryFile( ), icf.getLocalPackedIndexPath( ) );
+ IndexPackingRequest request = new IndexPackingRequest( context, //
+ context.acquireIndexSearcher( ).getIndexReader( ),
+ //
+ icf.getLocalPackedIndexPath( ).toFile( ) );
+
+ indexPacker.packIndex( request );
+ context.updateTimestamp( true );
+
+ log.debug( "Index file packed at '{}'.", icf.getLocalPackedIndexPath( ) );
+ }
+ else
+ {
+ log.debug( "skip packed index creation" );
+ }
+ }
+ else
+ {
+ log.debug( "skip packed index creation" );
+ }
+ }
+ catch ( IOException e )
+ {
+ log.error( "Error occurred while executing indexing task '{}': {}", indexingTask, e.getMessage( ) );
+ throw new TaskExecutionException( "Error occurred while executing indexing task '" + indexingTask + "'",
+ e );
+ }
+ }
+
+ public void setIndexPacker( IndexPacker indexPacker )
+ {
+ this.indexPacker = indexPacker;
+ }
+
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/DefaultDownloadRemoteIndexScheduler.java b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/DefaultDownloadRemoteIndexScheduler.java
new file mode 100644
index 000000000..2496c1d81
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/DefaultDownloadRemoteIndexScheduler.java
@@ -0,0 +1,211 @@
+package org.apache.archiva.scheduler.indexing.maven;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.proxy.ProxyRegistry;
+import org.apache.archiva.proxy.model.NetworkProxy;
+import org.apache.archiva.scheduler.indexing.DownloadRemoteIndexException;
+import org.apache.archiva.scheduler.indexing.DownloadRemoteIndexScheduler;
+import org.apache.archiva.admin.model.RepositoryAdminException;
+import org.apache.archiva.configuration.ArchivaConfiguration;
+import org.apache.archiva.configuration.ConfigurationEvent;
+import org.apache.archiva.configuration.ConfigurationListener;
+import org.apache.archiva.indexer.UnsupportedBaseContextException;
+import org.apache.archiva.proxy.maven.WagonFactory;
+import org.apache.archiva.repository.RepositoryRegistry;
+import org.apache.archiva.repository.features.RemoteIndexFeature;
+import org.apache.commons.lang.StringUtils;
+import org.apache.maven.index.context.IndexingContext;
+import org.apache.maven.index.packer.IndexPacker;
+import org.apache.maven.index.updater.IndexUpdater;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.support.CronTrigger;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import javax.inject.Named;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * @author Olivier Lamy
+ * @since 1.4-M1
+ */
+@Service( "downloadRemoteIndexScheduler#default" )
+public class DefaultDownloadRemoteIndexScheduler
+ implements ConfigurationListener, DownloadRemoteIndexScheduler
+{
+
+ private Logger log = LoggerFactory.getLogger( getClass() );
+
+ @Inject
+ @Named( value = "taskScheduler#indexDownloadRemote" )
+ private TaskScheduler taskScheduler;
+
+ @Inject
+ RepositoryRegistry repositoryRegistry;
+
+ @Inject
+ private ArchivaConfiguration archivaConfiguration;
+
+ @Inject
+ private WagonFactory wagonFactory;
+
+ @Inject
+ private IndexUpdater indexUpdater;
+
+ @Inject
+ private IndexPacker indexPacker;
+
+ @Inject
+ private ProxyRegistry proxyRegistry;
+
+ // store ids about currently running remote download : updated in DownloadRemoteIndexTask
+ private List<String> runningRemoteDownloadIds = new CopyOnWriteArrayList<String>();
+
+ @PostConstruct
+ public void startup()
+ throws
+ DownloadRemoteIndexException, UnsupportedBaseContextException {
+ archivaConfiguration.addListener( this );
+ // TODO add indexContexts even if null
+
+ for ( org.apache.archiva.repository.RemoteRepository remoteRepository : repositoryRegistry.getRemoteRepositories() )
+ {
+ String contextKey = "remote-" + remoteRepository.getId();
+ IndexingContext context = remoteRepository.getIndexingContext().getBaseContext(IndexingContext.class);
+ if ( context == null )
+ {
+ continue;
+ }
+ RemoteIndexFeature rif = remoteRepository.getFeature(RemoteIndexFeature.class).get();
+
+
+ // TODO record jobs from configuration
+ if ( rif.isDownloadRemoteIndex() && StringUtils.isNotEmpty(
+ remoteRepository.getSchedulingDefinition() ) )
+ {
+ boolean fullDownload = context.getIndexDirectoryFile().list().length == 0;
+ scheduleDownloadRemote( remoteRepository.getId(), false, fullDownload );
+ }
+ }
+
+
+ }
+
+ @Override
+ public void configurationEvent( ConfigurationEvent event )
+ {
+ // TODO remove jobs and add again
+ }
+
+
+ @Override
+ public void scheduleDownloadRemote( String repositoryId, boolean now, boolean fullDownload )
+ throws DownloadRemoteIndexException
+ {
+ org.apache.archiva.repository.RemoteRepository remoteRepo = repositoryRegistry.getRemoteRepository(repositoryId);
+
+ if ( remoteRepo == null )
+ {
+ log.warn( "ignore scheduleDownloadRemote for repo with id {} as not exists", repositoryId );
+ return;
+ }
+ if (!remoteRepo.supportsFeature(RemoteIndexFeature.class)) {
+ log.warn("ignore scheduleDownloadRemote for repo with id {}. Does not support remote index.", repositoryId);
+ return;
+ }
+ RemoteIndexFeature rif = remoteRepo.getFeature(RemoteIndexFeature.class).get();
+ NetworkProxy networkProxy = null;
+ if ( StringUtils.isNotBlank( rif.getProxyId() ) )
+ {
+ networkProxy = proxyRegistry.getNetworkProxy( rif.getProxyId() );
+ if ( networkProxy == null )
+ {
+ log.warn(
+ "your remote repository is configured to download remote index trought a proxy we cannot find id:{}",
+ rif.getProxyId() );
+ }
+ }
+
+ DownloadRemoteIndexTaskRequest downloadRemoteIndexTaskRequest = new DownloadRemoteIndexTaskRequest() //
+ .setRemoteRepository( remoteRepo ) //
+ .setNetworkProxy( networkProxy ) //
+ .setFullDownload( fullDownload ) //
+ .setWagonFactory( wagonFactory ) //
+ .setIndexUpdater( indexUpdater ) //
+ .setIndexPacker( this.indexPacker );
+
+ if ( now )
+ {
+ log.info( "schedule download remote index for repository {}", remoteRepo.getId() );
+ // do it now
+ taskScheduler.schedule(
+ new DownloadRemoteIndexTask( downloadRemoteIndexTaskRequest, this.runningRemoteDownloadIds ),
+ new Date() );
+ }
+ else
+ {
+ log.info( "schedule download remote index for repository {} with cron expression {}",
+ remoteRepo.getId(), remoteRepo.getSchedulingDefinition());
+ try
+ {
+ CronTrigger cronTrigger = new CronTrigger( remoteRepo.getSchedulingDefinition());
+ taskScheduler.schedule(
+ new DownloadRemoteIndexTask( downloadRemoteIndexTaskRequest, this.runningRemoteDownloadIds ),
+ cronTrigger );
+ }
+ catch ( IllegalArgumentException e )
+ {
+ log.warn( "Unable to schedule remote index download: {}", e.getLocalizedMessage() );
+ }
+
+ if ( rif.isDownloadRemoteIndexOnStartup() )
+ {
+ log.info(
+ "remote repository {} configured with downloadRemoteIndexOnStartup schedule now a download",
+ remoteRepo.getId() );
+ taskScheduler.schedule(
+ new DownloadRemoteIndexTask( downloadRemoteIndexTaskRequest, this.runningRemoteDownloadIds ),
+ new Date() );
+ }
+ }
+
+ }
+
+ public TaskScheduler getTaskScheduler()
+ {
+ return taskScheduler;
+ }
+
+ public void setTaskScheduler( TaskScheduler taskScheduler )
+ {
+ this.taskScheduler = taskScheduler;
+ }
+
+ @Override
+ public List<String> getRunningRemoteDownloadIds()
+ {
+ return runningRemoteDownloadIds;
+ }
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/DefaultIndexUpdateSideEffect.java b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/DefaultIndexUpdateSideEffect.java
new file mode 100644
index 000000000..581dbb2da
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/DefaultIndexUpdateSideEffect.java
@@ -0,0 +1,47 @@
+package org.apache.archiva.scheduler.indexing.maven;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.index.context.IndexingContext;
+import org.apache.maven.index.updater.IndexUpdateSideEffect;
+import org.apache.maven.index_shaded.lucene.store.Directory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+/**
+ * Not doing much but required at least one implementation
+ *
+ * @since 3.0.0
+ */
+@Service
+public class DefaultIndexUpdateSideEffect
+ implements IndexUpdateSideEffect
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger( DefaultIndexUpdateSideEffect.class );
+
+ @Override
+ public void updateIndex( Directory directory, IndexingContext indexingContext, boolean b )
+ {
+ LOGGER.info( "updating index: {} with directory: {}", //
+ indexingContext.getId(), //
+ directory.toString() );
+ }
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/DownloadRemoteIndexTask.java b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/DownloadRemoteIndexTask.java
new file mode 100644
index 000000000..3f794019e
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/DownloadRemoteIndexTask.java
@@ -0,0 +1,380 @@
+package org.apache.archiva.scheduler.indexing.maven;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.proxy.maven.WagonFactory;
+import org.apache.archiva.proxy.maven.WagonFactoryRequest;
+import org.apache.archiva.proxy.model.NetworkProxy;
+import org.apache.archiva.repository.PasswordCredentials;
+import org.apache.archiva.repository.RemoteRepository;
+import org.apache.archiva.repository.RepositoryException;
+import org.apache.archiva.repository.RepositoryType;
+import org.apache.archiva.repository.features.RemoteIndexFeature;
+import org.apache.commons.lang.time.StopWatch;
+import org.apache.maven.index.context.IndexingContext;
+import org.apache.maven.index.updater.IndexUpdateRequest;
+import org.apache.maven.index.updater.IndexUpdateResult;
+import org.apache.maven.index.updater.IndexUpdater;
+import org.apache.maven.index.updater.ResourceFetcher;
+import org.apache.maven.index_shaded.lucene.index.IndexNotFoundException;
+import org.apache.maven.wagon.ResourceDoesNotExistException;
+import org.apache.maven.wagon.StreamWagon;
+import org.apache.maven.wagon.TransferFailedException;
+import org.apache.maven.wagon.Wagon;
+import org.apache.maven.wagon.authentication.AuthenticationInfo;
+import org.apache.maven.wagon.authorization.AuthorizationException;
+import org.apache.maven.wagon.events.TransferEvent;
+import org.apache.maven.wagon.events.TransferListener;
+import org.apache.maven.wagon.proxy.ProxyInfo;
+import org.apache.maven.wagon.repository.Repository;
+import org.apache.maven.wagon.shared.http.AbstractHttpClientWagon;
+import org.apache.maven.wagon.shared.http.HttpConfiguration;
+import org.apache.maven.wagon.shared.http.HttpMethodConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Olivier Lamy
+ * @since 1.4-M1
+ */
+public class DownloadRemoteIndexTask
+ implements Runnable
+{
+ private Logger log = LoggerFactory.getLogger( getClass() );
+
+ private RemoteRepository remoteRepository;
+
+ private WagonFactory wagonFactory;
+
+ private NetworkProxy networkProxy;
+
+ private boolean fullDownload;
+
+ private List<String> runningRemoteDownloadIds;
+
+ private IndexUpdater indexUpdater;
+
+
+ public DownloadRemoteIndexTask( DownloadRemoteIndexTaskRequest downloadRemoteIndexTaskRequest,
+ List<String> runningRemoteDownloadIds )
+ {
+ this.remoteRepository = downloadRemoteIndexTaskRequest.getRemoteRepository();
+ this.wagonFactory = downloadRemoteIndexTaskRequest.getWagonFactory();
+ this.networkProxy = downloadRemoteIndexTaskRequest.getNetworkProxy();
+ this.fullDownload = downloadRemoteIndexTaskRequest.isFullDownload();
+ this.runningRemoteDownloadIds = runningRemoteDownloadIds;
+ this.indexUpdater = downloadRemoteIndexTaskRequest.getIndexUpdater();
+ }
+
+ @Override
+ public void run()
+ {
+
+ // so short lock : not sure we need it
+ synchronized ( this.runningRemoteDownloadIds )
+ {
+ if ( this.runningRemoteDownloadIds.contains( this.remoteRepository.getId() ) )
+ {
+ // skip it as it's running
+ log.info( "skip download index remote for repo {} it's already running",
+ this.remoteRepository.getId() );
+ return;
+ }
+ this.runningRemoteDownloadIds.add( this.remoteRepository.getId() );
+ }
+ Path tempIndexDirectory = null;
+ StopWatch stopWatch = new StopWatch();
+ stopWatch.start();
+ try
+ {
+ log.info( "start download remote index for remote repository {}", this.remoteRepository.getId() );
+ if (this.remoteRepository.getIndexingContext()==null) {
+ throw new IndexNotFoundException("No index context set for repository "+remoteRepository.getId());
+ }
+ if (this.remoteRepository.getType()!= RepositoryType.MAVEN) {
+ throw new RepositoryException("Bad repository type");
+ }
+ if (!this.remoteRepository.supportsFeature(RemoteIndexFeature.class)) {
+ throw new RepositoryException("Repository does not support RemotIndexFeature "+remoteRepository.getId());
+ }
+ RemoteIndexFeature rif = this.remoteRepository.getFeature(RemoteIndexFeature.class).get();
+ IndexingContext indexingContext = this.remoteRepository.getIndexingContext().getBaseContext(IndexingContext.class);
+ // create a temp directory to download files
+ tempIndexDirectory = Paths.get(indexingContext.getIndexDirectoryFile().getParent(), ".tmpIndex" );
+ Path indexCacheDirectory = Paths.get( indexingContext.getIndexDirectoryFile().getParent(), ".indexCache" );
+ Files.createDirectories( indexCacheDirectory );
+ if ( Files.exists(tempIndexDirectory) )
+ {
+ org.apache.archiva.common.utils.FileUtils.deleteDirectory( tempIndexDirectory );
+ }
+ Files.createDirectories( tempIndexDirectory );
+ tempIndexDirectory.toFile().deleteOnExit();
+ String baseIndexUrl = indexingContext.getIndexUpdateUrl();
+
+ String wagonProtocol = this.remoteRepository.getLocation().getScheme();
+
+ final StreamWagon wagon = (StreamWagon) wagonFactory.getWagon(
+ new WagonFactoryRequest( wagonProtocol, this.remoteRepository.getExtraHeaders() ).networkProxy(
+ this.networkProxy )
+ );
+ // FIXME olamy having 2 config values
+ wagon.setReadTimeout( (int)rif.getDownloadTimeout().toMillis());
+ wagon.setTimeout( (int)remoteRepository.getTimeout().toMillis());
+
+ if ( wagon instanceof AbstractHttpClientWagon )
+ {
+ HttpConfiguration httpConfiguration = new HttpConfiguration();
+ HttpMethodConfiguration httpMethodConfiguration = new HttpMethodConfiguration();
+ httpMethodConfiguration.setUsePreemptive( true );
+ httpMethodConfiguration.setReadTimeout( (int)rif.getDownloadTimeout().toMillis() );
+ httpConfiguration.setGet( httpMethodConfiguration );
+ AbstractHttpClientWagon.class.cast( wagon ).setHttpConfiguration( httpConfiguration );
+ }
+
+ wagon.addTransferListener( new DownloadListener() );
+ ProxyInfo proxyInfo = null;
+ if ( this.networkProxy != null )
+ {
+ proxyInfo = new ProxyInfo();
+ proxyInfo.setType( this.networkProxy.getProtocol() );
+ proxyInfo.setHost( this.networkProxy.getHost() );
+ proxyInfo.setPort( this.networkProxy.getPort() );
+ proxyInfo.setUserName( this.networkProxy.getUsername() );
+ proxyInfo.setPassword( this.networkProxy.getPassword() );
+ }
+ AuthenticationInfo authenticationInfo = null;
+ if ( this.remoteRepository.getLoginCredentials()!=null && this.remoteRepository.getLoginCredentials() instanceof PasswordCredentials )
+ {
+ PasswordCredentials creds = (PasswordCredentials) this.remoteRepository.getLoginCredentials();
+ authenticationInfo = new AuthenticationInfo();
+ authenticationInfo.setUserName( creds.getUsername());
+ authenticationInfo.setPassword( new String(creds.getPassword()) );
+ }
+ log.debug("Connection to {}, authInfo={}", this.remoteRepository.getId(), authenticationInfo);
+ wagon.connect( new Repository( this.remoteRepository.getId(), baseIndexUrl ), authenticationInfo,
+ proxyInfo );
+
+ Path indexDirectory = indexingContext.getIndexDirectoryFile().toPath();
+ if ( !Files.exists(indexDirectory) )
+ {
+ Files.createDirectories( indexDirectory );
+ }
+ log.debug("Downloading index file to {}", indexDirectory);
+ log.debug("Index cache dir {}", indexCacheDirectory);
+
+ ResourceFetcher resourceFetcher =
+ new WagonResourceFetcher( log, tempIndexDirectory, wagon, remoteRepository );
+ IndexUpdateRequest request = new IndexUpdateRequest( indexingContext, resourceFetcher );
+ request.setForceFullUpdate( this.fullDownload );
+ request.setLocalIndexCacheDir( indexCacheDirectory.toFile() );
+
+ IndexUpdateResult result = this.indexUpdater.fetchAndUpdateIndex(request);
+ log.debug("Update result success: {}", result.isSuccessful());
+ stopWatch.stop();
+ log.info( "time update index from remote for repository {}: {}ms", this.remoteRepository.getId(),
+ ( stopWatch.getTime() ) );
+
+ // index packing optionnal ??
+ //IndexPackingRequest indexPackingRequest =
+ // new IndexPackingRequest( indexingContext, indexingContext.getIndexDirectoryFile() );
+ //indexPacker.packIndex( indexPackingRequest );
+ indexingContext.updateTimestamp( true );
+
+ }
+ catch ( Exception e )
+ {
+ log.error( e.getMessage(), e );
+ throw new RuntimeException( e.getMessage(), e );
+ }
+ finally
+ {
+ deleteDirectoryQuiet( tempIndexDirectory );
+ this.runningRemoteDownloadIds.remove( this.remoteRepository.getId() );
+ }
+ log.info( "end download remote index for remote repository {}", this.remoteRepository.getId() );
+ }
+
+ private void deleteDirectoryQuiet( Path f )
+ {
+ try
+ {
+ org.apache.archiva.common.utils.FileUtils.deleteDirectory( f );
+ }
+ catch ( IOException e )
+ {
+ log.warn( "skip error delete {} : {}", f, e.getMessage() );
+ }
+ }
+
+
+ private static final class DownloadListener
+ implements TransferListener
+ {
+ private Logger log = LoggerFactory.getLogger( getClass() );
+
+ private String resourceName;
+
+ private long startTime;
+
+ private int totalLength = 0;
+
+ @Override
+ public void transferInitiated( TransferEvent transferEvent )
+ {
+ startTime = System.currentTimeMillis();
+ resourceName = transferEvent.getResource().getName();
+ log.debug( "initiate transfer of {}", resourceName );
+ }
+
+ @Override
+ public void transferStarted( TransferEvent transferEvent )
+ {
+ this.totalLength = 0;
+ resourceName = transferEvent.getResource().getName();
+ log.info("Transferring: {}, {}", transferEvent.getResource().getContentLength(), transferEvent.getLocalFile().toString());
+ log.info( "start transfer of {}", transferEvent.getResource().getName() );
+ }
+
+ @Override
+ public void transferProgress( TransferEvent transferEvent, byte[] buffer, int length )
+ {
+ log.debug( "transfer of {} : {}/{}", transferEvent.getResource().getName(), buffer.length, length );
+ this.totalLength += length;
+ }
+
+ @Override
+ public void transferCompleted( TransferEvent transferEvent )
+ {
+ resourceName = transferEvent.getResource().getName();
+ long endTime = System.currentTimeMillis();
+ log.info( "end of transfer file {}: {}b, {}ms", transferEvent.getResource().getName(),
+ this.totalLength, ( endTime - startTime ) );
+ }
+
+ @Override
+ public void transferError( TransferEvent transferEvent )
+ {
+ log.info( "error of transfer file {}: {}", transferEvent.getResource().getName(),
+ transferEvent.getException().getMessage(), transferEvent.getException() );
+ }
+
+ @Override
+ public void debug( String message )
+ {
+ log.debug( "transfer debug {}", message );
+ }
+ }
+
+ private static class WagonResourceFetcher
+ implements ResourceFetcher
+ {
+
+ Logger log;
+
+ Path tempIndexDirectory;
+
+ Wagon wagon;
+
+ RemoteRepository remoteRepository;
+
+ private WagonResourceFetcher( Logger log, Path tempIndexDirectory, Wagon wagon,
+ RemoteRepository remoteRepository )
+ {
+ this.log = log;
+ this.tempIndexDirectory = tempIndexDirectory;
+ this.wagon = wagon;
+ this.remoteRepository = remoteRepository;
+ }
+
+ @Override
+ public void connect( String id, String url )
+ throws IOException
+ {
+ //no op
+ }
+
+ @Override
+ public void disconnect()
+ throws IOException
+ {
+ // no op
+ }
+
+ @Override
+ public InputStream retrieve( String name )
+ throws IOException, FileNotFoundException
+ {
+ try
+ {
+ log.info( "index update retrieve file, name:{}", name );
+ Path file = tempIndexDirectory.resolve( name );
+ Files.deleteIfExists( file );
+ file.toFile().deleteOnExit();
+ wagon.get( addParameters( name, this.remoteRepository ), file.toFile() );
+ return Files.newInputStream( file );
+ }
+ catch ( AuthorizationException | TransferFailedException e )
+ {
+ throw new IOException( e.getMessage(), e );
+ }
+ catch ( ResourceDoesNotExistException e )
+ {
+ FileNotFoundException fnfe = new FileNotFoundException( e.getMessage() );
+ fnfe.initCause( e );
+ throw fnfe;
+ }
+ }
+
+ // FIXME remove crappy copy/paste
+ protected String addParameters( String path, RemoteRepository remoteRepository )
+ {
+ if ( remoteRepository.getExtraParameters().isEmpty() )
+ {
+ return path;
+ }
+
+ boolean question = false;
+
+ StringBuilder res = new StringBuilder( path == null ? "" : path );
+
+ for ( Map.Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
+ {
+ if ( !question )
+ {
+ res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
+ }
+ }
+
+ return res.toString();
+ }
+
+ }
+
+
+}
+
diff --git a/archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/DownloadRemoteIndexTaskRequest.java b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/DownloadRemoteIndexTaskRequest.java
new file mode 100644
index 000000000..784709345
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/scheduler/indexing/maven/DownloadRemoteIndexTaskRequest.java
@@ -0,0 +1,116 @@
+package org.apache.archiva.scheduler.indexing.maven;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.proxy.maven.WagonFactory;
+import org.apache.archiva.proxy.model.NetworkProxy;
+import org.apache.archiva.repository.RemoteRepository;
+import org.apache.maven.index.packer.IndexPacker;
+import org.apache.maven.index.updater.IndexUpdater;
+
+/**
+ * @author Olivier Lamy
+ * @since 1.4-M1
+ */
+public class DownloadRemoteIndexTaskRequest
+{
+ private RemoteRepository remoteRepository;
+
+ private WagonFactory wagonFactory;
+
+ private NetworkProxy networkProxy;
+
+ private boolean fullDownload;
+
+ private IndexUpdater indexUpdater;
+
+ private IndexPacker indexPacker;
+
+ public DownloadRemoteIndexTaskRequest()
+ {
+ // no op
+ }
+
+ public RemoteRepository getRemoteRepository()
+ {
+ return remoteRepository;
+ }
+
+ public DownloadRemoteIndexTaskRequest setRemoteRepository( RemoteRepository remoteRepository )
+ {
+ this.remoteRepository = remoteRepository;
+ return this;
+ }
+
+
+ public WagonFactory getWagonFactory()
+ {
+ return wagonFactory;
+ }
+
+ public DownloadRemoteIndexTaskRequest setWagonFactory( WagonFactory wagonFactory )
+ {
+ this.wagonFactory = wagonFactory;
+ return this;
+ }
+
+ public NetworkProxy getNetworkProxy()
+ {
+ return networkProxy;
+ }
+
+ public DownloadRemoteIndexTaskRequest setNetworkProxy( NetworkProxy networkProxy )
+ {
+ this.networkProxy = networkProxy;
+ return this;
+ }
+
+ public boolean isFullDownload()
+ {
+ return fullDownload;
+ }
+
+ public DownloadRemoteIndexTaskRequest setFullDownload( boolean fullDownload )
+ {
+ this.fullDownload = fullDownload;
+ return this;
+ }
+
+ public IndexUpdater getIndexUpdater()
+ {
+ return indexUpdater;
+ }
+
+ public DownloadRemoteIndexTaskRequest setIndexUpdater( IndexUpdater indexUpdater )
+ {
+ this.indexUpdater = indexUpdater;
+ return this;
+ }
+
+ public IndexPacker getIndexPacker()
+ {
+ return indexPacker;
+ }
+
+ public DownloadRemoteIndexTaskRequest setIndexPacker( IndexPacker indexPacker )
+ {
+ this.indexPacker = indexPacker;
+ return this;
+ }
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/resources/META-INF/spring-context.xml b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/resources/META-INF/spring-context.xml
new file mode 100644
index 000000000..ddb6a1592
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/main/resources/META-INF/spring-context.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ 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.
+ -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+ http://www.springframework.org/schema/context
+ http://www.springframework.org/schema/context/spring-context-3.0.xsd"
+ default-lazy-init="false">
+
+ <context:annotation-config/>
+ <context:component-scan base-package="org.apache.archiva.scheduler.indexing,org.apache.maven.index"/>
+
+
+
+
+ <bean name="taskQueueExecutor#indexing" class="org.apache.archiva.redback.components.taskqueue.execution.ThreadedTaskQueueExecutor">
+ <property name="executor" ref="taskExecutor#indexing"/>
+ <property name="queue" ref="taskQueue#indexing"/>
+ <property name="name" value="indexing"/>
+ </bean>
+
+ <bean name="taskScheduler#indexDownloadRemote"
+ class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
+ <property name="poolSize" value="4"/>
+ <property name="threadGroupName" value="indexDownloadRemote"/>
+ </bean>
+
+
+</beans> \ No newline at end of file
diff --git a/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/index-updates/nexus-maven-repository-index.gz b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/index-updates/nexus-maven-repository-index.gz
new file mode 100644
index 000000000..1bf73c9dc
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/index-updates/nexus-maven-repository-index.gz
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/index-updates/nexus-maven-repository-index.properties b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/index-updates/nexus-maven-repository-index.properties
new file mode 100644
index 000000000..1d81aaf59
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/index-updates/nexus-maven-repository-index.properties
@@ -0,0 +1,36 @@
+#Sun Sep 25 09:15:00 CEST 2011
+nexus.index.id=test-repo
+nexus.index.chain-id=1316094851802
+nexus.index.timestamp=20110925071457.200 +0000
+nexus.index.incremental-19=39
+nexus.index.incremental-18=40
+nexus.index.incremental-17=41
+nexus.index.incremental-16=42
+nexus.index.incremental-15=43
+nexus.index.incremental-14=44
+nexus.index.incremental-13=45
+nexus.index.incremental-9=49
+nexus.index.incremental-12=46
+nexus.index.incremental-8=50
+nexus.index.incremental-11=47
+nexus.index.incremental-7=51
+nexus.index.incremental-10=48
+nexus.index.incremental-6=52
+nexus.index.incremental-5=53
+nexus.index.incremental-4=54
+nexus.index.incremental-3=55
+nexus.index.incremental-2=56
+nexus.index.last-incremental=58
+nexus.index.incremental-1=57
+nexus.index.incremental-0=58
+nexus.index.incremental-29=29
+nexus.index.incremental-28=30
+nexus.index.incremental-27=31
+nexus.index.incremental-26=32
+nexus.index.incremental-25=33
+nexus.index.incremental-24=34
+nexus.index.time=20110925071457.200 +0000
+nexus.index.incremental-23=35
+nexus.index.incremental-22=36
+nexus.index.incremental-21=37
+nexus.index.incremental-20=38
diff --git a/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/index-updates/nexus-maven-repository-index.zip b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/index-updates/nexus-maven-repository-index.zip
new file mode 100644
index 000000000..8f82a0dca
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/index-updates/nexus-maven-repository-index.zip
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/java/org/apache/archiva/scheduler/indexing/maven/ArchivaIndexingTaskExecutorTest.java b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/java/org/apache/archiva/scheduler/indexing/maven/ArchivaIndexingTaskExecutorTest.java
new file mode 100644
index 000000000..758568ba9
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/java/org/apache/archiva/scheduler/indexing/maven/ArchivaIndexingTaskExecutorTest.java
@@ -0,0 +1,352 @@
+package org.apache.archiva.scheduler.indexing.maven;
+
+/*
+ * 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.
+ */
+
+import junit.framework.TestCase;
+import org.apache.archiva.indexer.ArchivaIndexingContext;
+import org.apache.archiva.indexer.UnsupportedBaseContextException;
+import org.apache.archiva.repository.BasicManagedRepository;
+import org.apache.archiva.repository.ManagedRepository;
+import org.apache.archiva.repository.ReleaseScheme;
+import org.apache.archiva.repository.RepositoryRegistry;
+import org.apache.archiva.repository.features.IndexCreationFeature;
+import org.apache.archiva.scheduler.indexing.ArtifactIndexingTask;
+import org.apache.archiva.test.utils.ArchivaSpringJUnit4ClassRunner;
+import org.apache.maven.index.ArtifactInfo;
+import org.apache.maven.index.FlatSearchRequest;
+import org.apache.maven.index.FlatSearchResponse;
+import org.apache.maven.index.Indexer;
+import org.apache.maven.index.MAVEN;
+import org.apache.maven.index.context.IndexingContext;
+import org.apache.maven.index.expr.SourcedSearchExpression;
+import org.apache.maven.index.expr.StringSearchExpression;
+import org.apache.maven.index.updater.DefaultIndexUpdater;
+import org.apache.maven.index.updater.IndexUpdateRequest;
+import org.apache.maven.index.updater.IndexUpdater;
+import org.apache.maven.index_shaded.lucene.search.BooleanClause;
+import org.apache.maven.index_shaded.lucene.search.BooleanQuery;
+import org.apache.maven.index_shaded.lucene.search.IndexSearcher;
+import org.apache.maven.index_shaded.lucene.search.TopDocs;
+import org.assertj.core.api.Assertions;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Set;
+
+/**
+ * ArchivaIndexingTaskExecutorTest
+ */
+@RunWith( ArchivaSpringJUnit4ClassRunner.class )
+@ContextConfiguration( locations = { "classpath*:/META-INF/spring-context.xml", "classpath*:/spring-context.xml" } )
+public class ArchivaIndexingTaskExecutorTest
+ extends TestCase
+{
+ @Inject
+ private ArchivaIndexingTaskExecutor indexingExecutor;
+
+ @Inject
+ RepositoryRegistry repositoryRegistry;
+
+ @Inject
+ private IndexUpdater indexUpdater;
+
+ private ManagedRepository repo;
+
+ @Inject
+ private Indexer indexer;
+
+ @Before
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ super.setUp();
+
+ Path baseDir = Paths.get(System.getProperty("basedir"), "target/test-classes").toAbsolutePath();
+ BasicManagedRepository repositoryConfig = new BasicManagedRepository( "test-repo", "Test Repository", baseDir);
+ Path repoLocation = baseDir.resolve("test-repo" );
+ repositoryConfig.setLocation(repoLocation.toUri() );
+ repositoryConfig.setLayout( "default" );
+ repositoryConfig.setScanned( true );
+ repositoryConfig.addActiveReleaseScheme( ReleaseScheme.RELEASE );
+ repositoryConfig.removeActiveReleaseScheme( ReleaseScheme.SNAPSHOT );
+ repositoryRegistry.putRepository(repositoryConfig);
+ repo = repositoryRegistry.getManagedRepository( repositoryConfig.getId() );
+ }
+
+ @After
+ @Override
+ public void tearDown()
+ throws Exception
+ {
+
+ repositoryRegistry.destroy();
+ /*
+ removeIndexingContext with true cleanup files.
+ // 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();
+ }
+
+ protected IndexingContext getIndexingContext() throws UnsupportedBaseContextException {
+ assert repo != null;
+ ArchivaIndexingContext ctx = repo.getIndexingContext();
+ assert ctx != null;
+ return ctx.getBaseContext(IndexingContext.class);
+ }
+
+ @Test
+ public void testAddArtifactToIndex()
+ throws Exception
+ {
+ Path basePath = repo.getLocalPath();
+ Path artifactFile = basePath.resolve(
+ "org/apache/archiva/archiva-index-methods-jar-test/1.0/archiva-index-methods-jar-test-1.0.jar" );
+
+ ArtifactIndexingTask task =
+ new ArtifactIndexingTask( repo, artifactFile, ArtifactIndexingTask.Action.ADD,
+ repo.getIndexingContext());
+
+ indexingExecutor.executeTask( task );
+
+ task = new ArtifactIndexingTask( repo, null, ArtifactIndexingTask.Action.FINISH,
+ repo.getIndexingContext() );
+ indexingExecutor.executeTask( task );
+
+ BooleanQuery.Builder queryBuilder = new BooleanQuery.Builder( );
+ queryBuilder.add( indexer.constructQuery( MAVEN.GROUP_ID, new StringSearchExpression( "org.apache.archiva" ) ),
+ BooleanClause.Occur.SHOULD );
+ queryBuilder.add(
+ indexer.constructQuery( MAVEN.ARTIFACT_ID, new StringSearchExpression( "archiva-index-methods-jar-test" ) ),
+ BooleanClause.Occur.SHOULD );
+ BooleanQuery q = queryBuilder.build();
+
+ FlatSearchRequest request = new FlatSearchRequest( q , getIndexingContext());
+ FlatSearchResponse response = indexer.searchFlat( request );
+
+ assertTrue( Files.exists(basePath.resolve( ".indexer" )) );
+ assertTrue( Files.exists(basePath.resolve(".index" )) );
+ assertEquals( 1, response.getTotalHitsCount());
+
+ Set<ArtifactInfo> results = response.getResults();
+
+ ArtifactInfo artifactInfo = results.iterator().next();
+ assertEquals( "org.apache.archiva", artifactInfo.getGroupId() );
+ assertEquals( "archiva-index-methods-jar-test", artifactInfo.getArtifactId() );
+ assertEquals( "test-repo", artifactInfo.getRepository() );
+
+ }
+
+ @Test
+ public void testUpdateArtifactInIndex()
+ throws Exception
+ {
+ Path basePath = repo.getLocalPath();
+ Path artifactFile = basePath.resolve(
+ "org/apache/archiva/archiva-index-methods-jar-test/1.0/archiva-index-methods-jar-test-1.0.jar" );
+
+ ArtifactIndexingTask task =
+ new ArtifactIndexingTask( repo, artifactFile, ArtifactIndexingTask.Action.ADD,
+ repo.getIndexingContext() );
+
+ indexingExecutor.executeTask( task );
+ indexingExecutor.executeTask( task );
+
+ BooleanQuery.Builder qb = new BooleanQuery.Builder();
+ qb.add( indexer.constructQuery( MAVEN.GROUP_ID, new StringSearchExpression( "org.apache.archiva" ) ),
+ BooleanClause.Occur.SHOULD );
+ qb.add(
+ indexer.constructQuery( MAVEN.ARTIFACT_ID, new StringSearchExpression( "archiva-index-methods-jar-test" ) ),
+ BooleanClause.Occur.SHOULD );
+
+ IndexingContext ctx = getIndexingContext();
+
+ IndexSearcher searcher = ctx.acquireIndexSearcher();
+ TopDocs topDocs = searcher.search( qb.build(), 10 );
+
+ //searcher.close();
+ ctx.releaseIndexSearcher( searcher );
+
+ assertTrue( Files.exists(basePath.resolve(".indexer" )) );
+ assertTrue( Files.exists(basePath.resolve(".index" )) );
+
+ // should only return 1 hit!
+ assertEquals( 1, topDocs.totalHits );
+ }
+
+ @Test
+ public void testRemoveArtifactFromIndex()
+ throws Exception
+ {
+ Path basePath = repo.getLocalPath();
+ Path artifactFile = basePath.resolve(
+ "org/apache/archiva/archiva-index-methods-jar-test/1.0/archiva-index-methods-jar-test-1.0.jar" );
+
+ ArtifactIndexingTask task =
+ new ArtifactIndexingTask( repo, artifactFile, ArtifactIndexingTask.Action.ADD,
+ repo.getIndexingContext() );
+
+ // add artifact to index
+ indexingExecutor.executeTask( task );
+
+ BooleanQuery.Builder qb = new BooleanQuery.Builder();
+ qb.add( indexer.constructQuery( MAVEN.GROUP_ID, new SourcedSearchExpression( "org.apache.archiva" ) ),
+ BooleanClause.Occur.SHOULD );
+ //q.add(
+ // indexer.constructQuery( MAVEN.ARTIFACT_ID, new SourcedSearchExpression( "archiva-index-methods-jar-test" ) ),
+ // Occur.SHOULD );
+
+ IndexingContext ctx = repo.getIndexingContext( ).getBaseContext( IndexingContext.class );
+ FlatSearchRequest flatSearchRequest =
+ new FlatSearchRequest( qb.build(), ctx );
+
+ FlatSearchResponse response = indexer.searchFlat( flatSearchRequest );
+
+ assertTrue( Files.exists(basePath.resolve(".indexer" )) );
+ assertTrue( Files.exists(basePath.resolve( ".index" )) );
+
+ // should return 1 hit
+ assertEquals( 1, response.getTotalHitsCount() );
+
+ // remove added artifact from index
+ task = new ArtifactIndexingTask( repo, artifactFile, ArtifactIndexingTask.Action.DELETE,
+ repo.getIndexingContext());
+ indexingExecutor.executeTask( task );
+
+ task = new ArtifactIndexingTask( repo, artifactFile, ArtifactIndexingTask.Action.FINISH,
+ repo.getIndexingContext() );
+ indexingExecutor.executeTask( task );
+
+ qb = new BooleanQuery.Builder();
+ qb.add( indexer.constructQuery( MAVEN.GROUP_ID, new SourcedSearchExpression( "org.apache.archiva" ) ),
+ BooleanClause.Occur.SHOULD );
+ qb.add( indexer.constructQuery( MAVEN.ARTIFACT_ID,
+ new SourcedSearchExpression( "archiva-index-methods-jar-test" ) ),
+ BooleanClause.Occur.SHOULD );
+
+ assertTrue( Files.exists(basePath.resolve( ".indexer" )) );
+ assertTrue( Files.exists(basePath.resolve(".index" )) );
+
+ flatSearchRequest = new FlatSearchRequest( qb.build(), getIndexingContext() );
+
+ response = indexer.searchFlat( flatSearchRequest );
+ // artifact should have been removed from the index!
+ assertEquals( 0, response.getTotalHitsCount() );//.totalHits );
+
+ // TODO: test it was removed from the packaged index also
+ }
+
+ @Test
+ public void testPackagedIndex()
+ throws Exception
+ {
+
+ Path basePath = repo.getLocalPath();
+ IndexCreationFeature icf = repo.getFeature( IndexCreationFeature.class ).get();
+ Path packedIndexDirectory = icf.getLocalPackedIndexPath();
+ Path indexerDirectory = icf.getLocalIndexPath();
+
+ for (Path dir : new Path[] { packedIndexDirectory, indexerDirectory }) {
+ Files.list(dir).filter(path -> path.getFileName().toString().startsWith("nexus-maven-repository-index"))
+ .forEach(path ->
+ {
+ try {
+ System.err.println("Deleting " + path);
+ Files.delete(path);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ });
+ }
+
+
+
+
+ Path artifactFile = basePath.resolve(
+ "org/apache/archiva/archiva-index-methods-jar-test/1.0/archiva-index-methods-jar-test-1.0.jar" );
+ ArtifactIndexingTask task =
+ new ArtifactIndexingTask( repo, artifactFile, ArtifactIndexingTask.Action.ADD,
+ repo.getIndexingContext() );
+ task.setExecuteOnEntireRepo( false );
+
+ indexingExecutor.executeTask( task );
+
+ task = new ArtifactIndexingTask( repo, null, ArtifactIndexingTask.Action.FINISH,
+ repo.getIndexingContext() );
+
+ task.setExecuteOnEntireRepo( false );
+
+ indexingExecutor.executeTask( task );
+
+ assertTrue( Files.exists(packedIndexDirectory) );
+ assertTrue( Files.exists(indexerDirectory) );
+
+ // test packed index file creation
+ //no more zip
+ //Assertions.assertThat(new File( indexerDirectory, "nexus-maven-repository-index.zip" )).exists();
+ Assertions.assertThat( Files.exists(packedIndexDirectory.resolve("nexus-maven-repository-index.properties" ) ));
+ Assertions.assertThat( Files.exists(packedIndexDirectory.resolve("nexus-maven-repository-index.gz" ) ));
+ assertFalse( Files.exists(packedIndexDirectory.resolve("nexus-maven-repository-index.1.gz" ) ));
+
+ // unpack .zip index
+ //unzipIndex( indexerDirectory.getPath(), destDir.getPath() );
+
+ DefaultIndexUpdater.FileFetcher fetcher = new DefaultIndexUpdater.FileFetcher( packedIndexDirectory.toFile() );
+ IndexUpdateRequest updateRequest = new IndexUpdateRequest( getIndexingContext(), fetcher );
+ //updateRequest.setLocalIndexCacheDir( indexerDirectory );
+ indexUpdater.fetchAndUpdateIndex( updateRequest );
+
+ BooleanQuery.Builder qb = new BooleanQuery.Builder();
+ qb.add( indexer.constructQuery( MAVEN.GROUP_ID, new StringSearchExpression( "org.apache.archiva" ) ),
+ BooleanClause.Occur.SHOULD );
+ qb.add(
+ indexer.constructQuery( MAVEN.ARTIFACT_ID, new StringSearchExpression( "archiva-index-methods-jar-test" ) ),
+ BooleanClause.Occur.SHOULD );
+
+ FlatSearchRequest request = new FlatSearchRequest( qb.build(), getIndexingContext() );
+ FlatSearchResponse response = indexer.searchFlat( request );
+
+ assertEquals( 1, response.getTotalHitsCount() );
+ Set<ArtifactInfo> results = response.getResults();
+
+ ArtifactInfo artifactInfo = results.iterator().next();
+ assertEquals( "org.apache.archiva", artifactInfo.getGroupId() );
+ assertEquals( "archiva-index-methods-jar-test", artifactInfo.getArtifactId() );
+ assertEquals( "test-repo", artifactInfo.getRepository() );
+
+
+ }
+
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/java/org/apache/archiva/scheduler/indexing/maven/DownloadRemoteIndexTaskTest.java b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/java/org/apache/archiva/scheduler/indexing/maven/DownloadRemoteIndexTaskTest.java
new file mode 100644
index 000000000..01921e45d
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/java/org/apache/archiva/scheduler/indexing/maven/DownloadRemoteIndexTaskTest.java
@@ -0,0 +1,187 @@
+package org.apache.archiva.scheduler.indexing.maven;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.admin.model.beans.RemoteRepository;
+import org.apache.archiva.admin.model.remote.RemoteRepositoryAdmin;
+import org.apache.archiva.common.utils.FileUtils;
+import org.apache.archiva.repository.RepositoryRegistry;
+import org.apache.archiva.test.utils.ArchivaSpringJUnit4ClassRunner;
+import org.apache.maven.index.FlatSearchRequest;
+import org.apache.maven.index.FlatSearchResponse;
+import org.apache.maven.index.Indexer;
+import org.apache.maven.index.MAVEN;
+import org.apache.maven.index.context.IndexingContext;
+import org.apache.maven.index.expr.StringSearchExpression;
+import org.apache.maven.index_shaded.lucene.search.BooleanClause;
+import org.apache.maven.index_shaded.lucene.search.BooleanQuery;
+import org.assertj.core.api.Assertions;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.test.context.ContextConfiguration;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Olivier Lamy
+ */
+@RunWith( ArchivaSpringJUnit4ClassRunner.class )
+@ContextConfiguration( locations = { "classpath*:/META-INF/spring-context.xml", "classpath*:/spring-context.xml" } )
+public class DownloadRemoteIndexTaskTest
+{
+
+ private Server server;
+ private ServerConnector serverConnector;
+
+ private int port;
+
+ private Logger log = LoggerFactory.getLogger( getClass() );
+
+ @Inject
+ RemoteRepositoryAdmin remoteRepositoryAdmin;
+
+ @Inject
+ DefaultDownloadRemoteIndexScheduler downloadRemoteIndexScheduler;
+
+ @Inject
+ Indexer indexer;
+
+ @Inject
+ RepositoryRegistry repositoryRegistry;
+
+ @Before
+ public void initialize()
+ throws Exception
+ {
+ Path cfgFile = Paths.get("target/appserver-base/conf/archiva.xml");
+ if (Files.exists(cfgFile)) {
+ Files.delete(cfgFile);
+ }
+ try {
+ remoteRepositoryAdmin.deleteRemoteRepository("test-repo-re", null);
+ } catch (Exception e) {
+ // Ignore
+ }
+ server = new Server( );
+ serverConnector = new ServerConnector( server, new HttpConnectionFactory());
+ server.addConnector( serverConnector );
+ createContext( server, Paths.get( "src/test/" ) );
+ this.server.start();
+ this.port = serverConnector.getLocalPort();
+ log.info( "start server on port {}", this.port );
+ }
+
+ protected void createContext( Server server, Path repositoryDirectory )
+ throws IOException
+ {
+ ServletContextHandler context = new ServletContextHandler();
+ context.setResourceBase( repositoryDirectory.toAbsolutePath().toString() );
+ context.setContextPath( "/" );
+ ServletHolder sh = new ServletHolder( DefaultServlet.class );
+ context.addServlet( sh, "/" );
+ server.setHandler( context );
+
+ }
+
+ @After
+ public void tearDown()
+ throws Exception
+ {
+ if (server!=null) {
+ server.stop();
+ }
+ Path cfgFile = Paths.get("target/appserver-base/conf/archiva.xml");
+ if (Files.exists(cfgFile)) {
+ Files.delete(cfgFile);
+ }
+ }
+
+ @Test
+ public void downloadAndMergeRemoteIndexInEmptyIndex()
+ throws Exception
+ {
+ RemoteRepository remoteRepository = getRemoteRepository();
+
+ remoteRepositoryAdmin.addRemoteRepository( remoteRepository, null );
+
+ downloadRemoteIndexScheduler.startup();
+
+ downloadRemoteIndexScheduler.scheduleDownloadRemote( "test-repo-re", true, true );
+
+ ( (ThreadPoolTaskScheduler) downloadRemoteIndexScheduler.getTaskScheduler() ).getScheduledExecutor().awaitTermination(
+ 10, TimeUnit.SECONDS );
+
+ remoteRepositoryAdmin.deleteRemoteRepository( "test-repo-re", null );
+
+ // search
+ BooleanQuery.Builder iQuery = new BooleanQuery.Builder();
+ iQuery.add( indexer.constructQuery( MAVEN.GROUP_ID, new StringSearchExpression( "commons-logging" ) ),
+ BooleanClause.Occur.SHOULD );
+
+ remoteRepositoryAdmin.addRemoteRepository(remoteRepository, null);
+ FlatSearchRequest rq = new FlatSearchRequest( iQuery.build() );
+ rq.setContexts(
+ Arrays.asList( repositoryRegistry.getRemoteRepository(remoteRepository.getId()).getIndexingContext().getBaseContext(IndexingContext.class) ) );
+
+ FlatSearchResponse response = indexer.searchFlat(rq);
+
+ log.info( "returned hit count:{}", response.getReturnedHitsCount() );
+ Assertions.assertThat( response.getReturnedHitsCount() ).isEqualTo( 8 );
+ }
+
+
+ protected RemoteRepository getRemoteRepository() throws IOException
+ {
+ RemoteRepository remoteRepository = new RemoteRepository( Locale.getDefault());
+ Path indexDirectory =
+ Paths.get( FileUtils.getBasedir(), "target/index/test-" + Long.toString( System.currentTimeMillis() ) );
+ Files.createDirectories( indexDirectory );
+ indexDirectory.toFile().deleteOnExit();
+
+ remoteRepository.setName( "foo" );
+ remoteRepository.setIndexDirectory( indexDirectory.toAbsolutePath().toString() );
+ remoteRepository.setDownloadRemoteIndex( true );
+ remoteRepository.setId( "test-repo-re" );
+ remoteRepository.setUrl( "http://localhost:" + port );
+ remoteRepository.setRemoteIndexUrl( "http://localhost:" + port + "/index-updates/" );
+
+ return remoteRepository;
+ }
+
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/resources/log4j2-test.xml b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/resources/log4j2-test.xml
new file mode 100644
index 000000000..c7f86dd6b
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/resources/log4j2-test.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ ~ 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.
+ -->
+
+
+<configuration>
+ <appenders>
+ <Console name="console" target="SYSTEM_OUT">
+ <PatternLayout pattern="%d [%t] %-5p %c %x - %m%n"/>
+ </Console>
+ </appenders>
+ <loggers>
+
+ <logger name="org.springframework" level="error"/>
+
+ <logger name="org.apache.archiva.scheduler.indexing" level="info"/>
+ <!--
+ <logger name="org.apache.http" level="debug"/>
+ -->
+ <root level="info">
+ <appender-ref ref="console"/>
+ </root>
+ </loggers>
+</configuration>
+
+
diff --git a/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/resources/spring-context.xml b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/resources/spring-context.xml
new file mode 100644
index 000000000..3c9ef40d1
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/resources/spring-context.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+
+<!--
+ ~ 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.
+ -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans
+ http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
+ default-lazy-init="true">
+
+ <context:component-scan base-package="org.apache.archiva.repository.content.maven2,org.apache.archiva.indexer.maven" />
+
+ <bean name="scheduler" class="org.apache.archiva.redback.components.scheduler.DefaultScheduler">
+ <property name="properties">
+ <props>
+ <prop key="org.quartz.scheduler.instanceName">scheduler1</prop>
+ <prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>
+ <prop key="org.quartz.threadPool.threadCount">2</prop>
+ <prop key="org.quartz.threadPool.threadPriority">4</prop>
+ <prop key="org.quartz.jobStore.class">org.quartz.simpl.RAMJobStore</prop>
+ </props>
+ </property>
+ </bean>
+
+
+ <!-- wire up more basic configuration so it doesn't overwrite any config files -->
+ <bean name="archivaConfiguration#default" class="org.apache.archiva.configuration.DefaultArchivaConfiguration">
+ <property name="registry" ref="registry#default"/>
+ </bean>
+
+ <alias name="archivaConfiguration#default" alias="archivaConfiguration"/>
+
+ <bean name="registry#default" class="org.apache.archiva.redback.components.registry.commons.CommonsConfigurationRegistry">
+ <property name="properties">
+ <value>
+ <![CDATA[
+ <configuration>
+ <system/>
+ <xml fileName="${appserver.base}/conf/archiva.xml" config-forceCreate="true"
+ config-optional="true"
+ config-name="org.apache.archiva.base" config-at="org.apache.archiva"/>
+ </configuration>
+ ]]>
+ </value>
+ </property>
+ </bean>
+
+ <alias name="userConfiguration#redback" alias="userConfiguration#default"/>
+
+ <!-- ***
+ JPA settings
+ *** -->
+ <bean name="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
+ <property name="jpaVendorAdapter" >
+ <bean class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter" />
+ </property>
+ <property name="persistenceXmlLocation" value="classpath:META-INF/persistence-hsqldb.xml" />
+ <property name="jpaPropertyMap">
+ <map>
+ <entry key="openjpa.ConnectionURL" value="jdbc:hsqldb:mem:redback_database" />
+ <entry key="openjpa.ConnectionDriverName" value="org.hsqldb.jdbcDriver" />
+ <entry key="openjpa.ConnectionUserName" value="sa" />
+ <entry key="openjpa.ConnectionPassword" value="" />
+ <entry key="openjpa.Log" value="${openjpa.Log:DefaultLevel=INFO,Runtime=ERROR,Tool=ERROR,SQL=ERROR,Schema=ERROR,MetaData=ERROR}" />
+ <entry key="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)" />
+ <entry key="openjpa.jdbc.MappingDefaults"
+ value="ForeignKeyDeleteAction=restrict,JoinForeignKeyDeleteAction=restrict"/>
+ </map>
+ </property>
+
+ </bean>
+
+ <bean name="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager" >
+ <property name="entityManagerFactory" ref="entityManagerFactory" />
+ </bean>
+
+ <tx:annotation-driven />
+ <!-- ***
+ End of JPA settings
+ *** -->
+
+</beans> \ No newline at end of file