aboutsummaryrefslogtreecommitdiffstats
path: root/archiva-modules/archiva-maven/archiva-maven-indexer
diff options
context:
space:
mode:
Diffstat (limited to 'archiva-modules/archiva-maven/archiva-maven-indexer')
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/pom.xml294
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/maven/DefaultIndexUpdateSideEffect.java47
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/maven/MavenIndexContext.java137
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/maven/MavenIndexManager.java845
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/maven/merger/DefaultIndexMerger.java197
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearch.java775
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/DefaultMergedRemoteIndexesScheduler.java94
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/IndexMerger.java42
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/IndexMergerException.java32
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/IndexMergerRequest.java192
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/MergedRemoteIndexesTask.java89
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/MergedRemoteIndexesTaskRequest.java80
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/MergedRemoteIndexesTaskResult.java46
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/TemporaryGroupIndex.java119
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/TemporaryGroupIndexCleaner.java67
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/search/NoClassifierArtifactInfoFilter.java43
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/util/SearchUtil.java36
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/main/resources/META-INF/spring-context.xml41
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/MavenIndexManagerTest.java218
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/AbstractMavenRepositorySearch.java328
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchOSGITest.java94
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchPaginateTest.java123
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchTest.java941
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo-2/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.jarbin0 -> 2518 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo-2/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.pom28
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo-2/org/apache/archiva/archiva-search/1.1/archiva-search-1.1.jarbin0 -> 2518 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo-2/org/apache/archiva/archiva-search/1.1/archiva-search-1.1.pom28
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/artifactid-numeric/1.0/artifactid-numeric-1.0.jar0
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/artifactid-numeric/1.0/artifactid-numeric-1.0.pom17
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/artifactid-numeric123/1.0/artifactid-numeric123-1.0.jar0
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/artifactid-numeric123/1.0/artifactid-numeric123-1.0.pom17
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/classname-search/1.0/classname-search-1.0.jarbin0 -> 2220 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/classname-search/1.0/classname-search-1.0.pom18
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-search/1.0/archiva-search-1.0-patch1.jarbin0 -> 2518 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-search/1.0/archiva-search-1.0-sources.jarbin0 -> 2518 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.jarbin0 -> 2518 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.pom28
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-test/1.0/archiva-test-1.0.jarbin0 -> 2471 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-test/1.0/archiva-test-1.0.pom23
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-test/2.0/archiva-test-2.0.jarbin0 -> 2494 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-test/2.0/archiva-test-2.0.pom28
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-webapp/1.0/archiva-webapp-1.0.pom23
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-webapp/1.0/archiva-webapp-1.0.war0
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/test-webapp/1.0-SNAPSHOT/test-webapp-1.0-SNAPSHOT.warbin0 -> 4169 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/felix/org.apache.felix.bundlerepository/1.6.6/org.apache.felix.bundlerepository-1.6.6.jarbin0 -> 168279 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/felix/org.apache.felix.bundlerepository/1.6.6/org.apache.felix.bundlerepository-1.6.6.pom138
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.fdtbin0 -> 86211 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.fdxbin0 -> 114 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.fnmbin0 -> 2157 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.nvdbin0 -> 2330 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.nvmbin0 -> 261 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.sibin0 -> 371 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8_Lucene41_0.docbin0 -> 21619 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8_Lucene41_0.posbin0 -> 40037 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8_Lucene41_0.timbin0 -> 91607 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8_Lucene41_0.tipbin0 -> 1250 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/segments.genbin0 -> 36 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/segments_9bin0 -> 102 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/timestampbin0 -> 8 bytes
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/write.lock0
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/resources/log4j2-test.xml45
-rw-r--r--archiva-modules/archiva-maven/archiva-maven-indexer/src/test/resources/spring-context.xml81
62 files changed, 5354 insertions, 0 deletions
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/pom.xml b/archiva-modules/archiva-maven/archiva-maven-indexer/pom.xml
new file mode 100644
index 000000000..2cfb26370
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/pom.xml
@@ -0,0 +1,294 @@
+<?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/maven-v4_0_0.xsd">
+ <parent>
+ <groupId>org.apache.archiva.maven</groupId>
+ <artifactId>archiva-maven</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>archiva-maven-indexer</artifactId>
+ <name>Archiva :: Maven :: Indexer</name>
+
+ <properties>
+ <site.staging.base>${project.parent.parent.basedir}</site.staging.base>
+ </properties>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-repository-admin-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-repository-layer</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-proxy</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva.maven</groupId>
+ <artifactId>archiva-maven-common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context-support</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-utils</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </dependency>
+ <!--
+ <dependency>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-digest</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.codehaus.plexus</groupId>
+ <artifactId>plexus-component-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ -->
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.indexer</groupId>
+ <artifactId>indexer-reader</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.indexer</groupId>
+ <artifactId>indexer-core</artifactId>
+ <classifier>shaded-lucene</classifier>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.lucene</groupId>
+ <artifactId>lucene-queryparser</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.lucene</groupId>
+ <artifactId>lucene-analyzers-common</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.wagon</groupId>
+ <artifactId>wagon-http</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.sisu</groupId>
+ <artifactId>org.eclipse.sisu.plexus</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.google.inject</groupId>
+ <artifactId>guice</artifactId>
+ <classifier>no_aop</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-plexus-bridge</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-scheduler-repository</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.inject</groupId>
+ <artifactId>javax.inject</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-mock</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-repository-admin-default</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-test-utils</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.derby</groupId>
+ <artifactId>derby</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva.redback</groupId>
+ <artifactId>redback-keys-memory</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva.redback</groupId>
+ <artifactId>redback-rbac-cached</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva.redback</groupId>
+ <artifactId>redback-rbac-memory</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva.redback</groupId>
+ <artifactId>redback-users-memory</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva.redback</groupId>
+ <artifactId>redback-common-test-resources</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven.wagon</groupId>
+ <artifactId>wagon-file</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.maven.wagon</groupId>
+ <artifactId>wagon-http-lightweight</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>xerces</groupId>
+ <artifactId>xercesImpl</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hsqldb</groupId>
+ <artifactId>hsqldb</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-configuration</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-core</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.sonatype.sisu</groupId>
+ <artifactId>sisu-guava</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.sonatype.sisu</groupId>
+ <artifactId>sisu-inject</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.sonatype.sisu</groupId>
+ <artifactId>sisu-guice</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-slf4j-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-jcl</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/maven-search-test-repo*/**</exclude>
+ <exclude>src/test/repo-release*/**</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>
+ <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-indexer/src/main/java/org/apache/archiva/indexer/maven/DefaultIndexUpdateSideEffect.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/maven/DefaultIndexUpdateSideEffect.java
new file mode 100644
index 000000000..cb66cbb53
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/maven/DefaultIndexUpdateSideEffect.java
@@ -0,0 +1,47 @@
+package org.apache.archiva.indexer.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("archivaIndexUpdater")
+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-indexer/src/main/java/org/apache/archiva/indexer/maven/MavenIndexContext.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/maven/MavenIndexContext.java
new file mode 100644
index 000000000..e031922fb
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/maven/MavenIndexContext.java
@@ -0,0 +1,137 @@
+package org.apache.archiva.indexer.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.indexer.ArchivaIndexingContext;
+import org.apache.archiva.repository.Repository;
+import org.apache.maven.index.context.IndexingContext;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.sql.Date;
+import java.time.ZonedDateTime;
+import java.util.Set;
+
+/**
+ * Maven implementation of index context
+ */
+public class MavenIndexContext implements ArchivaIndexingContext {
+
+ private IndexingContext delegate;
+ private Repository repository;
+
+ MavenIndexContext(Repository repository, IndexingContext delegate) {
+ this.delegate = delegate;
+ this.repository = repository;
+
+ }
+
+ @Override
+ public String getId() {
+ return delegate.getId();
+ }
+
+ @Override
+ public Repository getRepository() {
+ return repository;
+ }
+
+ @Override
+ public URI getPath() {
+ return delegate.getIndexDirectoryFile().toURI();
+ }
+
+ @Override
+ public boolean isEmpty() throws IOException {
+ return Files.list(delegate.getIndexDirectoryFile().toPath()).count()==0;
+ }
+
+ @Override
+ public void commit() throws IOException {
+ delegate.commit();
+ }
+
+ @Override
+ public void rollback() throws IOException {
+ delegate.rollback();
+ }
+
+ @Override
+ public void optimize() throws IOException {
+ delegate.optimize();
+ }
+
+ @Override
+ public void close(boolean deleteFiles) throws IOException {
+ try {
+ delegate.close(deleteFiles);
+ } catch (NoSuchFileException e) {
+ // Ignore missing directory
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ delegate.close(false);
+ } catch (NoSuchFileException e) {
+ // Ignore missing directory
+ }
+ }
+
+ @Override
+ public void purge() throws IOException {
+ delegate.purge();
+ }
+
+ @Override
+ public boolean supports(Class<?> clazz) {
+ return IndexingContext.class.equals(clazz);
+ }
+
+ @SuppressWarnings( "unchecked" )
+ @Override
+ public <T> T getBaseContext(Class<T> clazz) throws UnsupportedOperationException {
+ if (IndexingContext.class.equals(clazz)) {
+ return (T) delegate;
+ } else {
+ throw new UnsupportedOperationException("The class "+clazz+" is not supported by the maven indexer");
+ }
+ }
+
+ @Override
+ public Set<String> getGroups() throws IOException {
+ return delegate.getAllGroups();
+ }
+
+ @Override
+ public void updateTimestamp(boolean save) throws IOException {
+ delegate.updateTimestamp(save);
+ }
+
+ @Override
+ public void updateTimestamp(boolean save, ZonedDateTime time) throws IOException {
+ delegate.updateTimestamp(save, Date.from(time.toInstant()));
+ }
+
+
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/maven/MavenIndexManager.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/maven/MavenIndexManager.java
new file mode 100644
index 000000000..375f726eb
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/maven/MavenIndexManager.java
@@ -0,0 +1,845 @@
+package org.apache.archiva.indexer.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.RepositoryAdminException;
+import org.apache.archiva.common.utils.FileUtils;
+import org.apache.archiva.common.utils.PathUtil;
+import org.apache.archiva.configuration.ArchivaConfiguration;
+import org.apache.archiva.indexer.ArchivaIndexManager;
+import org.apache.archiva.indexer.ArchivaIndexingContext;
+import org.apache.archiva.indexer.IndexCreationFailedException;
+import org.apache.archiva.indexer.IndexUpdateFailedException;
+import org.apache.archiva.indexer.UnsupportedBaseContextException;
+import org.apache.archiva.proxy.ProxyRegistry;
+import org.apache.archiva.proxy.maven.WagonFactory;
+import org.apache.archiva.proxy.maven.WagonFactoryException;
+import org.apache.archiva.proxy.maven.WagonFactoryRequest;
+import org.apache.archiva.proxy.model.NetworkProxy;
+import org.apache.archiva.repository.EditableRepository;
+import org.apache.archiva.repository.ManagedRepository;
+import org.apache.archiva.repository.PasswordCredentials;
+import org.apache.archiva.repository.RemoteRepository;
+import org.apache.archiva.repository.Repository;
+import org.apache.archiva.repository.RepositoryType;
+import org.apache.archiva.repository.UnsupportedRepositoryTypeException;
+import org.apache.archiva.repository.features.IndexCreationFeature;
+import org.apache.archiva.repository.features.RemoteIndexFeature;
+import org.apache.commons.lang.StringUtils;
+import org.apache.maven.index.ArtifactContext;
+import org.apache.maven.index.ArtifactContextProducer;
+import org.apache.maven.index.DefaultScannerListener;
+import org.apache.maven.index.Indexer;
+import org.apache.maven.index.IndexerEngine;
+import org.apache.maven.index.Scanner;
+import org.apache.maven.index.ScanningRequest;
+import org.apache.maven.index.ScanningResult;
+import org.apache.maven.index.context.IndexCreator;
+import org.apache.maven.index.context.IndexingContext;
+import org.apache.maven.index.packer.IndexPacker;
+import org.apache.maven.index.packer.IndexPackingRequest;
+import org.apache.maven.index.updater.IndexUpdateRequest;
+import org.apache.maven.index.updater.IndexUpdater;
+import org.apache.maven.index.updater.ResourceFetcher;
+import org.apache.maven.index_shaded.lucene.index.IndexFormatTooOldException;
+import org.apache.maven.wagon.ConnectionException;
+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.AuthenticationException;
+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.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 org.springframework.stereotype.Service;
+
+import javax.inject.Inject;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.stream.Collectors;
+
+/**
+ * Maven implementation of index manager.
+ * The index manager is a singleton, so we try to make sure, that index operations are not running
+ * parallel by synchronizing on the index path.
+ * A update operation waits for parallel running methods to finish before starting, but after a certain
+ * time of retries a IndexUpdateFailedException is thrown.
+ */
+@Service( "archivaIndexManager#maven" )
+public class MavenIndexManager implements ArchivaIndexManager {
+
+ private static final Logger log = LoggerFactory.getLogger( MavenIndexManager.class );
+
+ @Inject
+ private Indexer indexer;
+
+ @Inject
+ private IndexerEngine indexerEngine;
+
+ @Inject
+ private List<? extends IndexCreator> indexCreators;
+
+ @Inject
+ private IndexPacker indexPacker;
+
+ @Inject
+ private Scanner scanner;
+
+ @Inject
+ private ArchivaConfiguration archivaConfiguration;
+
+ @Inject
+ private WagonFactory wagonFactory;
+
+ @Inject
+ private IndexUpdater indexUpdater;
+
+ @Inject
+ private ArtifactContextProducer artifactContextProducer;
+
+ @Inject
+ private ProxyRegistry proxyRegistry;
+
+
+ public static final String DEFAULT_INDEXER_DIR = ".indexer";
+ public static final String DEFAULT_PACKED_INDEX_DIR = ".index";
+
+ private ConcurrentSkipListSet<Path> activeContexts = new ConcurrentSkipListSet<>( );
+
+ private static final int WAIT_TIME = 100;
+ private static final int MAX_WAIT = 10;
+
+
+ public static IndexingContext getMvnContext( ArchivaIndexingContext context ) throws UnsupportedBaseContextException
+ {
+ if ( !context.supports( IndexingContext.class ) )
+ {
+ log.error( "The provided archiva index context does not support the maven IndexingContext" );
+ throw new UnsupportedBaseContextException( "The context does not support the Maven IndexingContext" );
+ }
+ return context.getBaseContext( IndexingContext.class );
+ }
+
+ private Path getIndexPath( ArchivaIndexingContext ctx )
+ {
+ return PathUtil.getPathFromUri( ctx.getPath( ) );
+ }
+
+ @FunctionalInterface
+ interface IndexUpdateConsumer
+ {
+
+ void accept( IndexingContext indexingContext ) throws IndexUpdateFailedException;
+ }
+
+ /*
+ * This method is used to do some actions around the update execution code. And to make sure, that no other
+ * method is running on the same index.
+ */
+ private void executeUpdateFunction( ArchivaIndexingContext context, IndexUpdateConsumer function ) throws IndexUpdateFailedException
+ {
+ IndexingContext indexingContext = null;
+ try
+ {
+ indexingContext = getMvnContext( context );
+ }
+ catch ( UnsupportedBaseContextException e )
+ {
+ throw new IndexUpdateFailedException( "Maven index is not supported by this context", e );
+ }
+ final Path ctxPath = getIndexPath( context );
+ int loop = MAX_WAIT;
+ boolean active = false;
+ while ( loop-- > 0 && !active )
+ {
+ active = activeContexts.add( ctxPath );
+ try
+ {
+ Thread.currentThread( ).sleep( WAIT_TIME );
+ }
+ catch ( InterruptedException e )
+ {
+ // Ignore this
+ }
+ }
+ if ( active )
+ {
+ try
+ {
+ function.accept( indexingContext );
+ }
+ finally
+ {
+ activeContexts.remove( ctxPath );
+ }
+ }
+ else
+ {
+ throw new IndexUpdateFailedException( "Timeout while waiting for index release on context " + context.getId( ) );
+ }
+ }
+
+ @Override
+ public void pack( final ArchivaIndexingContext context ) throws IndexUpdateFailedException
+ {
+ executeUpdateFunction( context, indexingContext -> {
+ try
+ {
+ IndexPackingRequest request = new IndexPackingRequest( indexingContext,
+ indexingContext.acquireIndexSearcher( ).getIndexReader( ),
+ indexingContext.getIndexDirectoryFile( ) );
+ indexPacker.packIndex( request );
+ indexingContext.updateTimestamp( true );
+ }
+ catch ( IOException e )
+ {
+ log.error( "IOException while packing index of context " + context.getId( ) + ( StringUtils.isNotEmpty( e.getMessage( ) ) ? ": " + e.getMessage( ) : "" ) );
+ throw new IndexUpdateFailedException( "IOException during update of " + context.getId( ), e );
+ }
+ }
+ );
+
+ }
+
+ @Override
+ public void scan(final ArchivaIndexingContext context) throws IndexUpdateFailedException
+ {
+ executeUpdateFunction( context, indexingContext -> {
+ DefaultScannerListener listener = new DefaultScannerListener( indexingContext, indexerEngine, true, null );
+ ScanningRequest request = new ScanningRequest( indexingContext, 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 )
+ );
+ }
+
+ } );
+ }
+
+ @Override
+ public void update(final ArchivaIndexingContext context, final boolean fullUpdate) throws IndexUpdateFailedException
+ {
+ log.info( "start download remote index for remote repository {}", context.getRepository( ).getId( ) );
+ URI remoteUpdateUri;
+ if ( !( context.getRepository( ) instanceof RemoteRepository ) || !(context.getRepository().supportsFeature(RemoteIndexFeature.class)) )
+ {
+ throw new IndexUpdateFailedException( "The context is not associated to a remote repository with remote index " + context.getId( ) );
+ } else {
+ RemoteIndexFeature rif = context.getRepository().getFeature(RemoteIndexFeature.class).get();
+ remoteUpdateUri = context.getRepository().getLocation().resolve(rif.getIndexUri());
+ }
+ final RemoteRepository remoteRepository = (RemoteRepository) context.getRepository( );
+
+ executeUpdateFunction( context,
+ indexingContext -> {
+ try
+ {
+ // create a temp directory to download files
+ Path 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 = remoteUpdateUri.toURL( ).getProtocol( );
+
+ NetworkProxy networkProxy = null;
+ if ( remoteRepository.supportsFeature( RemoteIndexFeature.class ) )
+ {
+ RemoteIndexFeature rif = remoteRepository.getFeature( RemoteIndexFeature.class ).get( );
+ 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( ) );
+ }
+ }
+
+ final StreamWagon wagon = (StreamWagon) wagonFactory.getWagon(
+ new WagonFactoryRequest( wagonProtocol, remoteRepository.getExtraHeaders( ) ).networkProxy(
+ networkProxy )
+ );
+ int readTimeout = (int) rif.getDownloadTimeout( ).toMillis( ) * 1000;
+ wagon.setReadTimeout( readTimeout );
+ wagon.setTimeout( (int) remoteRepository.getTimeout( ).toMillis( ) * 1000 );
+
+ if ( wagon instanceof AbstractHttpClientWagon )
+ {
+ HttpConfiguration httpConfiguration = new HttpConfiguration( );
+ HttpMethodConfiguration httpMethodConfiguration = new HttpMethodConfiguration( );
+ httpMethodConfiguration.setUsePreemptive( true );
+ httpMethodConfiguration.setReadTimeout( readTimeout );
+ httpConfiguration.setGet( httpMethodConfiguration );
+ AbstractHttpClientWagon.class.cast( wagon ).setHttpConfiguration( httpConfiguration );
+ }
+
+ wagon.addTransferListener( new DownloadListener( ) );
+ ProxyInfo proxyInfo = null;
+ if ( networkProxy != null )
+ {
+ proxyInfo = new ProxyInfo( );
+ proxyInfo.setType( networkProxy.getProtocol( ) );
+ proxyInfo.setHost( networkProxy.getHost( ) );
+ proxyInfo.setPort( networkProxy.getPort( ) );
+ proxyInfo.setUserName( networkProxy.getUsername( ) );
+ proxyInfo.setPassword( networkProxy.getPassword( ) );
+ }
+ AuthenticationInfo authenticationInfo = null;
+ if ( remoteRepository.getLoginCredentials( ) != null && ( remoteRepository.getLoginCredentials( ) instanceof PasswordCredentials ) )
+ {
+ PasswordCredentials creds = (PasswordCredentials) remoteRepository.getLoginCredentials( );
+ authenticationInfo = new AuthenticationInfo( );
+ authenticationInfo.setUserName( creds.getUsername( ) );
+ authenticationInfo.setPassword( new String( creds.getPassword( ) ) );
+ }
+ wagon.connect( new org.apache.maven.wagon.repository.Repository( remoteRepository.getId( ), baseIndexUrl ), authenticationInfo,
+ proxyInfo );
+
+ Path indexDirectory = indexingContext.getIndexDirectoryFile( ).toPath( );
+ if ( !Files.exists( indexDirectory ) )
+ {
+ Files.createDirectories( indexDirectory );
+ }
+
+ ResourceFetcher resourceFetcher =
+ new WagonResourceFetcher( log, tempIndexDirectory, wagon, remoteRepository );
+ IndexUpdateRequest request = new IndexUpdateRequest( indexingContext, resourceFetcher );
+ request.setForceFullUpdate( fullUpdate );
+ request.setLocalIndexCacheDir( indexCacheDirectory.toFile( ) );
+
+ indexUpdater.fetchAndUpdateIndex( request );
+
+ indexingContext.updateTimestamp( true );
+ }
+
+ }
+ catch ( AuthenticationException e )
+ {
+ log.error( "Could not login to the remote proxy for updating index of {}", remoteRepository.getId( ), e );
+ throw new IndexUpdateFailedException( "Login in to proxy failed while updating remote repository " + remoteRepository.getId( ), e );
+ }
+ catch ( ConnectionException e )
+ {
+ log.error( "Connection error during index update for remote repository {}", remoteRepository.getId( ), e );
+ throw new IndexUpdateFailedException( "Connection error during index update for remote repository " + remoteRepository.getId( ), e );
+ }
+ catch ( MalformedURLException e )
+ {
+ log.error( "URL for remote index update of remote repository {} is not correct {}", remoteRepository.getId( ), remoteUpdateUri, e );
+ throw new IndexUpdateFailedException( "URL for remote index update of repository is not correct " + remoteUpdateUri, e );
+ }
+ catch ( IOException e )
+ {
+ log.error( "IOException during index update of remote repository {}: {}", remoteRepository.getId( ), e.getMessage( ), e );
+ throw new IndexUpdateFailedException( "IOException during index update of remote repository " + remoteRepository.getId( )
+ + ( StringUtils.isNotEmpty( e.getMessage( ) ) ? ": " + e.getMessage( ) : "" ), e );
+ }
+ catch ( WagonFactoryException e )
+ {
+ log.error( "Wagon for remote index download of {} could not be created: {}", remoteRepository.getId( ), e.getMessage( ), e );
+ throw new IndexUpdateFailedException( "Error while updating the remote index of " + remoteRepository.getId( ), e );
+ }
+ } );
+
+ }
+
+ @Override
+ public void addArtifactsToIndex( final ArchivaIndexingContext context, final Collection<URI> artifactReference ) throws IndexUpdateFailedException
+ {
+ final URI ctxUri = context.getPath();
+ executeUpdateFunction(context, indexingContext -> {
+ Collection<ArtifactContext> artifacts = artifactReference.stream().map(r -> artifactContextProducer.getArtifactContext(indexingContext, Paths.get(ctxUri.resolve(r)).toFile())).collect(Collectors.toList());
+ try {
+ indexer.addArtifactsToIndex(artifacts, indexingContext);
+ } catch (IOException e) {
+ log.error("IOException while adding artifact {}", e.getMessage(), e);
+ throw new IndexUpdateFailedException("Error occured while adding artifact to index of "+context.getId()
+ + (StringUtils.isNotEmpty(e.getMessage()) ? ": "+e.getMessage() : ""));
+ }
+ });
+ }
+
+ @Override
+ public void removeArtifactsFromIndex( ArchivaIndexingContext context, Collection<URI> artifactReference ) throws IndexUpdateFailedException
+ {
+ final URI ctxUri = context.getPath();
+ executeUpdateFunction(context, indexingContext -> {
+ Collection<ArtifactContext> artifacts = artifactReference.stream().map(r -> artifactContextProducer.getArtifactContext(indexingContext, Paths.get(ctxUri.resolve(r)).toFile())).collect(Collectors.toList());
+ try {
+ indexer.deleteArtifactsFromIndex(artifacts, indexingContext);
+ } catch (IOException e) {
+ log.error("IOException while removing artifact {}", e.getMessage(), e);
+ throw new IndexUpdateFailedException("Error occured while removing artifact from index of "+context.getId()
+ + (StringUtils.isNotEmpty(e.getMessage()) ? ": "+e.getMessage() : ""));
+ }
+ });
+
+ }
+
+ @Override
+ public boolean supportsRepository( RepositoryType type )
+ {
+ return type == RepositoryType.MAVEN;
+ }
+
+ @Override
+ public ArchivaIndexingContext createContext( Repository repository ) throws IndexCreationFailedException
+ {
+ log.debug("Creating context for repo {}, type: {}", repository.getId(), repository.getType());
+ if ( repository.getType( ) != RepositoryType.MAVEN )
+ {
+ throw new UnsupportedRepositoryTypeException( repository.getType( ) );
+ }
+ IndexingContext mvnCtx = null;
+ try
+ {
+ if ( repository instanceof RemoteRepository )
+ {
+ mvnCtx = createRemoteContext( (RemoteRepository) repository );
+ }
+ else if ( repository instanceof ManagedRepository )
+ {
+ mvnCtx = createManagedContext( (ManagedRepository) repository );
+ }
+ }
+ catch ( IOException e )
+ {
+ log.error( "IOException during context creation " + e.getMessage( ), e );
+ throw new IndexCreationFailedException( "Could not create index context for repository " + repository.getId( )
+ + ( StringUtils.isNotEmpty( e.getMessage( ) ) ? ": " + e.getMessage( ) : "" ), e );
+ }
+ MavenIndexContext context = new MavenIndexContext( repository, mvnCtx );
+
+ return context;
+ }
+
+ @Override
+ public ArchivaIndexingContext reset(ArchivaIndexingContext context) throws IndexUpdateFailedException {
+ ArchivaIndexingContext ctx;
+ executeUpdateFunction(context, indexingContext -> {
+ try {
+ indexingContext.close(true);
+ } catch (IOException e) {
+ log.warn("Index close failed");
+ }
+ try {
+ FileUtils.deleteDirectory(Paths.get(context.getPath()));
+ } catch (IOException e) {
+ throw new IndexUpdateFailedException("Could not delete index files");
+ }
+ });
+ try {
+ Repository repo = context.getRepository();
+ ctx = createContext(context.getRepository());
+ if (repo instanceof EditableRepository) {
+ ((EditableRepository)repo).setIndexingContext(ctx);
+ }
+ } catch (IndexCreationFailedException e) {
+ throw new IndexUpdateFailedException("Could not create index");
+ }
+ return ctx;
+ }
+
+ @Override
+ public ArchivaIndexingContext move(ArchivaIndexingContext context, Repository repo) throws IndexCreationFailedException {
+ if (context==null) {
+ return null;
+ }
+ if (context.supports(IndexingContext.class)) {
+ try {
+ Path newPath = getIndexPath(repo);
+ IndexingContext ctx = context.getBaseContext(IndexingContext.class);
+ Path oldPath = ctx.getIndexDirectoryFile().toPath();
+ if (oldPath.equals(newPath)) {
+ // Nothing to do, if path does not change
+ return context;
+ }
+ if (!Files.exists(oldPath)) {
+ return createContext(repo);
+ } else if (context.isEmpty()) {
+ context.close();
+ return createContext(repo);
+ } else {
+ context.close(false);
+ Files.move(oldPath, newPath);
+ return createContext(repo);
+ }
+ } catch (IOException e) {
+ log.error("IOException while moving index directory {}", e.getMessage(), e);
+ throw new IndexCreationFailedException("Could not recreated the index.", e);
+ } catch (UnsupportedBaseContextException e) {
+ throw new IndexCreationFailedException("The given context, is not a maven context.");
+ }
+ } else {
+ throw new IndexCreationFailedException("Bad context type. This is not a maven context.");
+ }
+ }
+
+ @Override
+ public void updateLocalIndexPath(Repository repo) {
+ if (repo.supportsFeature(IndexCreationFeature.class)) {
+ IndexCreationFeature icf = repo.getFeature(IndexCreationFeature.class).get();
+ try {
+ icf.setLocalIndexPath(getIndexPath(repo));
+ icf.setLocalPackedIndexPath(getPackedIndexPath(repo));
+ } catch (IOException e) {
+ log.error("Could not set local index path for {}. New URI: {}", repo.getId(), icf.getIndexPath());
+ }
+ }
+ }
+
+ private Path getIndexPath(Repository repo) throws IOException {
+ IndexCreationFeature icf = repo.getFeature(IndexCreationFeature.class).get();
+ Path repoDir = repo.getLocalPath();
+ URI indexDir = icf.getIndexPath();
+ Path indexDirectory = null;
+ if ( ! StringUtils.isEmpty(indexDir.toString( ) ) )
+ {
+
+ indexDirectory = PathUtil.getPathFromUri( indexDir );
+ // not absolute so create it in repository directory
+ if ( !indexDirectory.isAbsolute( ) )
+ {
+ indexDirectory = repoDir.resolve( indexDirectory );
+ }
+ }
+ else
+ {
+ indexDirectory = repoDir.resolve( DEFAULT_INDEXER_DIR );
+ }
+
+ if ( !Files.exists( indexDirectory ) )
+ {
+ Files.createDirectories( indexDirectory );
+ }
+ return indexDirectory;
+ }
+
+ private Path getPackedIndexPath(Repository repo) throws IOException {
+ IndexCreationFeature icf = repo.getFeature(IndexCreationFeature.class).get();
+ Path repoDir = repo.getLocalPath();
+ URI indexDir = icf.getPackedIndexPath();
+ Path indexDirectory = null;
+ if ( ! StringUtils.isEmpty(indexDir.toString( ) ) )
+ {
+
+ indexDirectory = PathUtil.getPathFromUri( indexDir );
+ // not absolute so create it in repository directory
+ if ( !indexDirectory.isAbsolute( ) )
+ {
+ indexDirectory = repoDir.resolve( indexDirectory );
+ }
+ }
+ else
+ {
+ indexDirectory = repoDir.resolve( DEFAULT_PACKED_INDEX_DIR );
+ }
+
+ if ( !Files.exists( indexDirectory ) )
+ {
+ Files.createDirectories( indexDirectory );
+ }
+ return indexDirectory;
+ }
+
+ private IndexingContext createRemoteContext(RemoteRepository remoteRepository ) throws IOException
+ {
+ Path appServerBase = archivaConfiguration.getAppServerBaseDir( );
+
+ String contextKey = "remote-" + remoteRepository.getId( );
+
+
+ // create remote repository path
+ Path repoDir = remoteRepository.getLocalPath();
+ if ( !Files.exists( repoDir ) )
+ {
+ Files.createDirectories( repoDir );
+ }
+
+ Path indexDirectory = null;
+
+ // is there configured indexDirectory ?
+ if ( remoteRepository.supportsFeature( RemoteIndexFeature.class ) )
+ {
+ RemoteIndexFeature rif = remoteRepository.getFeature( RemoteIndexFeature.class ).get( );
+ indexDirectory = getIndexPath(remoteRepository);
+ String remoteIndexUrl = calculateIndexRemoteUrl( remoteRepository.getLocation( ), rif );
+ try
+ {
+
+ return getIndexingContext( remoteRepository, contextKey, repoDir, indexDirectory, remoteIndexUrl );
+ }
+ catch ( IndexFormatTooOldException e )
+ {
+ // existing index with an old lucene format so we need to delete it!!!
+ // delete it first then recreate it.
+ log.warn( "the index of repository {} is too old we have to delete and recreate it", //
+ remoteRepository.getId( ) );
+ org.apache.archiva.common.utils.FileUtils.deleteDirectory( indexDirectory );
+ return getIndexingContext( remoteRepository, contextKey, repoDir, indexDirectory, remoteIndexUrl );
+
+ }
+ }
+ else
+ {
+ throw new IOException( "No remote index defined" );
+ }
+ }
+
+ private IndexingContext getIndexingContext( Repository repository, String contextKey, Path repoDir, Path indexDirectory, String indexUrl ) throws IOException
+ {
+ return indexer.createIndexingContext( contextKey, repository.getId( ), repoDir.toFile( ), indexDirectory.toFile( ),
+ repository.getLocation( ) == null ? null : repository.getLocation( ).toString( ),
+ indexUrl,
+ true, false,
+ indexCreators );
+ }
+
+ private IndexingContext createManagedContext( ManagedRepository repository ) throws IOException
+ {
+
+ IndexingContext context;
+ // take care first about repository location as can be relative
+ Path repositoryDirectory = repository.getLocalPath();
+
+ if ( !Files.exists( repositoryDirectory ) )
+ {
+ try
+ {
+ Files.createDirectories( repositoryDirectory );
+ }
+ catch ( IOException e )
+ {
+ log.error( "Could not create directory {}", repositoryDirectory );
+ }
+ }
+
+ Path indexDirectory = null;
+
+ if ( repository.supportsFeature( IndexCreationFeature.class ) )
+ {
+ indexDirectory = getIndexPath(repository);
+
+ String indexUrl = repositoryDirectory.toUri( ).toURL( ).toExternalForm( );
+ try
+ {
+ context = getIndexingContext( repository, repository.getId( ), repositoryDirectory, indexDirectory, indexUrl );
+ context.setSearchable( repository.isScanned( ) );
+ }
+ catch ( IndexFormatTooOldException e )
+ {
+ // existing index with an old lucene format so we need to delete it!!!
+ // delete it first then recreate it.
+ log.warn( "the index of repository {} is too old we have to delete and recreate it", //
+ repository.getId( ) );
+ org.apache.archiva.common.utils.FileUtils.deleteDirectory( indexDirectory );
+ context = getIndexingContext( repository, repository.getId( ), repositoryDirectory, indexDirectory, indexUrl );
+ context.setSearchable( repository.isScanned( ) );
+ }
+ return context;
+ }
+ else
+ {
+ throw new IOException( "No repository index defined" );
+ }
+ }
+
+ private String calculateIndexRemoteUrl( URI baseUri, RemoteIndexFeature rif )
+ {
+ if ( rif.getIndexUri( ) == null )
+ {
+ return baseUri.resolve( DEFAULT_INDEXER_DIR ).toString( );
+ }
+ else
+ {
+ return baseUri.resolve( rif.getIndexUri( ) ).toString( );
+ }
+ }
+
+ 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( "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 {} {} kb: {}s", transferEvent.getResource( ).getName( ),
+ this.totalLength / 1024, ( endTime - startTime ) / 1000 );
+ }
+
+ @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, 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-indexer/src/main/java/org/apache/archiva/indexer/maven/merger/DefaultIndexMerger.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/maven/merger/DefaultIndexMerger.java
new file mode 100644
index 000000000..1f4648025
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/maven/merger/DefaultIndexMerger.java
@@ -0,0 +1,197 @@
+package org.apache.archiva.indexer.maven.merger;
+/*
+ * 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.common.utils.FileUtils;
+import org.apache.archiva.indexer.UnsupportedBaseContextException;
+import org.apache.archiva.indexer.merger.IndexMerger;
+import org.apache.archiva.indexer.merger.IndexMergerException;
+import org.apache.archiva.indexer.merger.IndexMergerRequest;
+import org.apache.archiva.indexer.merger.TemporaryGroupIndex;
+import org.apache.archiva.repository.RepositoryRegistry;
+import org.apache.archiva.repository.RepositoryType;
+import org.apache.commons.lang.time.StopWatch;
+import org.apache.maven.index.Indexer;
+import org.apache.maven.index.context.ContextMemberProvider;
+import org.apache.maven.index.context.IndexCreator;
+import org.apache.maven.index.context.IndexingContext;
+import org.apache.maven.index.context.StaticContextMemberProvider;
+import org.apache.maven.index.packer.IndexPacker;
+import org.apache.maven.index.packer.IndexPackingRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.stream.Collectors;
+
+/**
+ * @author Olivier Lamy
+ * @since 1.4-M2
+ */
+@Service("indexMerger#default")
+public class DefaultIndexMerger
+ implements IndexMerger
+{
+
+ @Inject
+ RepositoryRegistry repositoryRegistry;
+
+ private Logger log = LoggerFactory.getLogger( getClass() );
+
+
+ private final IndexPacker indexPacker;
+
+ private Indexer indexer;
+
+ private final List<IndexCreator> indexCreators;
+
+ private List<TemporaryGroupIndex> temporaryGroupIndexes = new CopyOnWriteArrayList<>();
+
+ private List<IndexingContext> temporaryContextes = new CopyOnWriteArrayList<>( );
+
+ private List<String> runningGroups = new CopyOnWriteArrayList<>();
+
+ @Inject
+ public DefaultIndexMerger( Indexer indexer, IndexPacker indexPacker, List<IndexCreator> indexCreators )
+ {
+ this.indexer = indexer;
+ this.indexPacker = indexPacker;
+ this.indexCreators = indexCreators;
+ }
+
+ @Override
+ public IndexingContext buildMergedIndex( IndexMergerRequest indexMergerRequest )
+ throws IndexMergerException
+ {
+ String groupId = indexMergerRequest.getGroupId();
+
+ if ( runningGroups.contains( groupId ) )
+ {
+ log.info( "skip build merge remote indexes for id: '{}' as already running", groupId );
+ return null;
+ }
+
+ runningGroups.add( groupId );
+
+ StopWatch stopWatch = new StopWatch();
+ stopWatch.reset();
+ stopWatch.start();
+
+ Path mergedIndexDirectory = indexMergerRequest.getMergedIndexDirectory();
+
+ String tempRepoId = mergedIndexDirectory.getFileName().toString();
+
+ try
+ {
+ Path indexLocation = mergedIndexDirectory.resolve( indexMergerRequest.getMergedIndexPath() );
+
+ List<IndexingContext> members = indexMergerRequest.getRepositoriesIds( ).stream( ).map( id ->
+ repositoryRegistry.getRepository( id ) ).filter( repo -> repo.getType().equals( RepositoryType.MAVEN ) )
+ .map( repo -> {
+ try
+ {
+ return repo.getIndexingContext().getBaseContext( IndexingContext.class );
+ }
+ catch ( UnsupportedBaseContextException e )
+ {
+ return null;
+ // Ignore
+ }
+ } ).filter( Objects::nonNull ).collect( Collectors.toList() );
+ ContextMemberProvider memberProvider = new StaticContextMemberProvider(members);
+ IndexingContext mergedCtx = indexer.createMergedIndexingContext( tempRepoId, tempRepoId, mergedIndexDirectory.toFile(),
+ indexLocation.toFile(), true, memberProvider);
+ mergedCtx.optimize();
+
+ if ( indexMergerRequest.isPackIndex() )
+ {
+ IndexPackingRequest request = new IndexPackingRequest( mergedCtx, //
+ mergedCtx.acquireIndexSearcher().getIndexReader(), //
+ indexLocation.toFile() );
+ indexPacker.packIndex( request );
+ }
+
+ if ( indexMergerRequest.isTemporary() )
+ {
+ temporaryGroupIndexes.add( new TemporaryGroupIndex( mergedIndexDirectory, tempRepoId, groupId,
+ indexMergerRequest.getMergedIndexTtl() ) );
+ temporaryContextes.add(mergedCtx);
+ }
+ stopWatch.stop();
+ log.info( "merged index for repos {} in {} s", indexMergerRequest.getRepositoriesIds(),
+ stopWatch.getTime() );
+ return mergedCtx;
+ }
+ catch ( IOException e)
+ {
+ throw new IndexMergerException( e.getMessage(), e );
+ }
+ finally
+ {
+ runningGroups.remove( groupId );
+ }
+ }
+
+ @Async
+ @Override
+ public void cleanTemporaryGroupIndex( TemporaryGroupIndex temporaryGroupIndex )
+ {
+ if ( temporaryGroupIndex == null )
+ {
+ return;
+ }
+
+ try
+ {
+
+ Optional<IndexingContext> ctxOpt = temporaryContextes.stream( ).filter( ctx -> ctx.getId( ).equals( temporaryGroupIndex.getIndexId( ) ) ).findFirst( );
+ if (ctxOpt.isPresent()) {
+ IndexingContext ctx = ctxOpt.get();
+ indexer.closeIndexingContext( ctx, true );
+ temporaryGroupIndexes.remove( temporaryGroupIndex );
+ temporaryContextes.remove( ctx );
+ Path directory = temporaryGroupIndex.getDirectory();
+ if ( directory != null && Files.exists(directory) )
+ {
+ FileUtils.deleteDirectory( directory );
+ }
+ }
+ }
+ catch ( IOException e )
+ {
+ log.warn( "fail to delete temporary group index {}", temporaryGroupIndex.getIndexId(), e );
+ }
+ }
+
+ @Override
+ public Collection<TemporaryGroupIndex> getTemporaryGroupIndexes()
+ {
+ return this.temporaryGroupIndexes;
+ }
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearch.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearch.java
new file mode 100644
index 000000000..6a7c9fd83
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearch.java
@@ -0,0 +1,775 @@
+package org.apache.archiva.indexer.maven.search;
+
+/*
+ * 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.RepositoryAdminException;
+import org.apache.archiva.admin.model.beans.ProxyConnector;
+import org.apache.archiva.admin.model.proxyconnector.ProxyConnectorAdmin;
+import org.apache.archiva.indexer.UnsupportedBaseContextException;
+import org.apache.archiva.indexer.search.ArtifactInfoFilter;
+import org.apache.archiva.indexer.search.NoClassifierArtifactInfoFilter;
+import org.apache.archiva.indexer.search.RepositorySearch;
+import org.apache.archiva.indexer.search.RepositorySearchException;
+import org.apache.archiva.indexer.search.SearchFields;
+import org.apache.archiva.indexer.search.SearchResultHit;
+import org.apache.archiva.indexer.search.SearchResultLimits;
+import org.apache.archiva.indexer.search.SearchResults;
+import org.apache.archiva.indexer.util.SearchUtil;
+import org.apache.archiva.model.ArchivaArtifactModel;
+import org.apache.archiva.repository.RemoteRepository;
+import org.apache.archiva.repository.Repository;
+import org.apache.archiva.repository.RepositoryRegistry;
+import org.apache.archiva.repository.RepositoryType;
+import org.apache.commons.lang.StringUtils;
+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.OSGI;
+import org.apache.maven.index.QueryCreator;
+import org.apache.maven.index.SearchType;
+import org.apache.maven.index.context.IndexingContext;
+import org.apache.maven.index.expr.SearchExpression;
+import org.apache.maven.index.expr.SearchTyped;
+import org.apache.maven.index.expr.SourcedSearchExpression;
+import org.apache.maven.index.expr.UserInputSearchExpression;
+import org.apache.maven.index_shaded.lucene.search.BooleanClause;
+import org.apache.maven.index_shaded.lucene.search.BooleanClause.Occur;
+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.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * RepositorySearch implementation which uses the Maven Indexer for searching.
+ */
+@Service( "repositorySearch#maven" )
+public class MavenRepositorySearch
+ implements RepositorySearch
+{
+ private Logger log = LoggerFactory.getLogger( getClass() );
+
+ private Indexer indexer;
+
+ private QueryCreator queryCreator;
+
+
+ RepositoryRegistry repositoryRegistry;
+
+ private ProxyConnectorAdmin proxyConnectorAdmin;
+
+ protected MavenRepositorySearch()
+ {
+ // for test purpose
+ }
+
+ @Inject
+ public MavenRepositorySearch( Indexer nexusIndexer, RepositoryRegistry repositoryRegistry,
+
+ ProxyConnectorAdmin proxyConnectorAdmin, QueryCreator queryCreator )
+ {
+ this.indexer = nexusIndexer;
+ this.queryCreator = queryCreator;
+ this.repositoryRegistry = repositoryRegistry;
+ this.proxyConnectorAdmin = proxyConnectorAdmin;
+ }
+
+ /**
+ * @see RepositorySearch#search(String, List, String, SearchResultLimits, List)
+ */
+ @Override
+ public SearchResults search(String principal, List<String> selectedRepos, String term, SearchResultLimits limits,
+ List<String> previousSearchTerms )
+ throws RepositorySearchException
+ {
+ List<String> indexingContextIds = addIndexingContexts( selectedRepos );
+
+ // since upgrade to nexus 2.0.0, query has changed from g:[QUERIED TERM]* to g:*[QUERIED TERM]*
+ // resulting to more wildcard searches so we need to increase max clause count
+ BooleanQuery.setMaxClauseCount( Integer.MAX_VALUE );
+ BooleanQuery.Builder qb = new BooleanQuery.Builder();
+
+ if ( previousSearchTerms == null || previousSearchTerms.isEmpty() )
+ {
+ constructQuery( term, qb );
+ }
+ else
+ {
+ for ( String previousTerm : previousSearchTerms )
+ {
+ BooleanQuery.Builder iQuery = new BooleanQuery.Builder();
+ constructQuery( previousTerm, iQuery );
+
+ qb.add( iQuery.build(), BooleanClause.Occur.MUST );
+ }
+
+ BooleanQuery.Builder iQuery = new BooleanQuery.Builder();
+ constructQuery( term, iQuery );
+ qb.add( iQuery.build(), BooleanClause.Occur.MUST );
+ }
+
+ // we retun only artifacts without classifier in quick search, olamy cannot find a way to say with this field empty
+ // FIXME cannot find a way currently to setup this in constructQuery !!!
+ return search( limits, qb.build(), indexingContextIds, NoClassifierArtifactInfoFilter.LIST, selectedRepos, true );
+
+ }
+
+ /**
+ * @see RepositorySearch#search(String, SearchFields, SearchResultLimits)
+ */
+ @SuppressWarnings( "deprecation" )
+ @Override
+ public SearchResults search( String principal, SearchFields searchFields, SearchResultLimits limits )
+ throws RepositorySearchException
+ {
+ if ( searchFields.getRepositories() == null )
+ {
+ throw new RepositorySearchException( "Repositories cannot be null." );
+ }
+
+ List<String> indexingContextIds = addIndexingContexts( searchFields.getRepositories() );
+
+ // if no index found in the specified ones return an empty search result instead of doing a search on all index
+ // olamy: IMHO doesn't make sense
+ if ( !searchFields.getRepositories().isEmpty() && ( indexingContextIds == null
+ || indexingContextIds.isEmpty() ) )
+ {
+ return new SearchResults();
+ }
+
+ BooleanQuery.Builder qb = new BooleanQuery.Builder();
+ if ( StringUtils.isNotBlank( searchFields.getGroupId() ) )
+ {
+ qb.add( indexer.constructQuery( MAVEN.GROUP_ID, searchFields.isExactSearch() ? new SourcedSearchExpression(
+ searchFields.getGroupId() ) : new UserInputSearchExpression( searchFields.getGroupId() ) ),
+ BooleanClause.Occur.MUST );
+ }
+
+ if ( StringUtils.isNotBlank( searchFields.getArtifactId() ) )
+ {
+ qb.add( indexer.constructQuery( MAVEN.ARTIFACT_ID,
+ searchFields.isExactSearch()
+ ? new SourcedSearchExpression( searchFields.getArtifactId() )
+ : new UserInputSearchExpression( searchFields.getArtifactId() ) ),
+ BooleanClause.Occur.MUST );
+ }
+
+ if ( StringUtils.isNotBlank( searchFields.getVersion() ) )
+ {
+ qb.add( indexer.constructQuery( MAVEN.VERSION, searchFields.isExactSearch() ? new SourcedSearchExpression(
+ searchFields.getVersion() ) : new SourcedSearchExpression( searchFields.getVersion() ) ),
+ BooleanClause.Occur.MUST );
+ }
+
+ if ( StringUtils.isNotBlank( searchFields.getPackaging() ) )
+ {
+ qb.add( indexer.constructQuery( MAVEN.PACKAGING, searchFields.isExactSearch() ? new SourcedSearchExpression(
+ searchFields.getPackaging() ) : new UserInputSearchExpression( searchFields.getPackaging() ) ),
+ BooleanClause.Occur.MUST );
+ }
+
+ if ( StringUtils.isNotBlank( searchFields.getClassName() ) )
+ {
+ qb.add( indexer.constructQuery( MAVEN.CLASSNAMES,
+ new UserInputSearchExpression( searchFields.getClassName() ) ),
+ BooleanClause.Occur.MUST );
+ }
+
+ if ( StringUtils.isNotBlank( searchFields.getBundleSymbolicName() ) )
+ {
+ qb.add( indexer.constructQuery( OSGI.SYMBOLIC_NAME,
+ new UserInputSearchExpression( searchFields.getBundleSymbolicName() ) ),
+ BooleanClause.Occur.MUST );
+ }
+
+ if ( StringUtils.isNotBlank( searchFields.getBundleVersion() ) )
+ {
+ qb.add( indexer.constructQuery( OSGI.VERSION,
+ new UserInputSearchExpression( searchFields.getBundleVersion() ) ),
+ BooleanClause.Occur.MUST );
+ }
+
+ if ( StringUtils.isNotBlank( searchFields.getBundleExportPackage() ) )
+ {
+ qb.add( indexer.constructQuery( OSGI.EXPORT_PACKAGE,
+ new UserInputSearchExpression( searchFields.getBundleExportPackage() ) ),
+ Occur.MUST );
+ }
+
+ if ( StringUtils.isNotBlank( searchFields.getBundleExportService() ) )
+ {
+ qb.add( indexer.constructQuery( OSGI.EXPORT_SERVICE,
+ new UserInputSearchExpression( searchFields.getBundleExportService() ) ),
+ Occur.MUST );
+ }
+
+ if ( StringUtils.isNotBlank( searchFields.getBundleImportPackage() ) )
+ {
+ qb.add( indexer.constructQuery( OSGI.IMPORT_PACKAGE,
+ new UserInputSearchExpression( searchFields.getBundleImportPackage() ) ),
+ Occur.MUST );
+ }
+
+ if ( StringUtils.isNotBlank( searchFields.getBundleName() ) )
+ {
+ qb.add( indexer.constructQuery( OSGI.NAME, new UserInputSearchExpression( searchFields.getBundleName() ) ),
+ Occur.MUST );
+ }
+
+ if ( StringUtils.isNotBlank( searchFields.getBundleImportPackage() ) )
+ {
+ qb.add( indexer.constructQuery( OSGI.IMPORT_PACKAGE,
+ new UserInputSearchExpression( searchFields.getBundleImportPackage() ) ),
+ Occur.MUST );
+ }
+
+ if ( StringUtils.isNotBlank( searchFields.getBundleRequireBundle() ) )
+ {
+ qb.add( indexer.constructQuery( OSGI.REQUIRE_BUNDLE,
+ new UserInputSearchExpression( searchFields.getBundleRequireBundle() ) ),
+ Occur.MUST );
+ }
+
+ if ( StringUtils.isNotBlank( searchFields.getClassifier() ) )
+ {
+ qb.add( indexer.constructQuery( MAVEN.CLASSIFIER, searchFields.isExactSearch() ? new SourcedSearchExpression(
+ searchFields.getClassifier() ) : new UserInputSearchExpression( searchFields.getClassifier() ) ),
+ Occur.MUST );
+ }
+ else if ( searchFields.isExactSearch() )
+ {
+ //TODO improvement in case of exact search and no classifier we must query for classifier with null value
+ // currently it's done in DefaultSearchService with some filtering
+ }
+
+ BooleanQuery qu = qb.build();
+ if ( qu.clauses() == null || qu.clauses().size() <= 0 )
+ {
+ throw new RepositorySearchException( "No search fields set." );
+ }
+ if (qu.clauses()!=null) {
+ log.debug("CLAUSES ", qu.clauses());
+ for (BooleanClause cl : qu.clauses()) {
+ log.debug("Clause ",cl);
+ }
+ }
+
+ return search( limits, qu, indexingContextIds, Collections.<ArtifactInfoFilter>emptyList(),
+ searchFields.getRepositories(), searchFields.isIncludePomArtifacts() );
+ }
+
+ private static class NullSearch
+ implements SearchTyped, SearchExpression
+ {
+ private static final NullSearch INSTANCE = new NullSearch();
+
+ @Override
+ public String getStringValue()
+ {
+ return "[[NULL_VALUE]]";
+ }
+
+ @Override
+ public SearchType getSearchType()
+ {
+ return SearchType.EXACT;
+ }
+ }
+
+ private SearchResults search( SearchResultLimits limits, BooleanQuery q, List<String> indexingContextIds,
+ List<? extends ArtifactInfoFilter> filters, List<String> selectedRepos,
+ boolean includePoms )
+ throws RepositorySearchException
+ {
+
+ try
+ {
+ FlatSearchRequest request = new FlatSearchRequest( q );
+
+ request.setContexts( getIndexingContexts( indexingContextIds ) );
+ if ( limits != null )
+ {
+ // we apply limits only when first page asked
+ if ( limits.getSelectedPage() == 0 )
+ {
+ request.setCount( limits.getPageSize() * ( Math.max( 1, limits.getSelectedPage() ) ) );
+ }
+ }
+
+ FlatSearchResponse response = indexer.searchFlat( request );
+
+ if ( response == null || response.getTotalHitsCount() == 0 )
+ {
+ SearchResults results = new SearchResults();
+ results.setLimits( limits );
+ return results;
+ }
+
+ return convertToSearchResults( response, limits, filters, selectedRepos, includePoms );
+ }
+ catch ( IOException e )
+ {
+ throw new RepositorySearchException( e.getMessage(), e );
+ }
+ catch ( RepositoryAdminException e )
+ {
+ throw new RepositorySearchException( e.getMessage(), e );
+ }
+
+ }
+
+ private IndexingContext getIndexingContext(String id) {
+ String repoId;
+ if (StringUtils.startsWith(id, "remote-")) {
+ repoId = StringUtils.substringAfter(id, "remote-");
+ } else {
+ repoId = id;
+ }
+ Repository repo = repositoryRegistry.getRepository(repoId);
+ if (repo==null) {
+ return null;
+ } else {
+ if (repo.getIndexingContext()!=null) {
+ try {
+ return repo.getIndexingContext().getBaseContext(IndexingContext.class);
+ } catch (UnsupportedBaseContextException e) {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+ }
+
+ private List<IndexingContext> getIndexingContexts( List<String> ids )
+ {
+ List<IndexingContext> contexts = new ArrayList<>( ids.size() );
+
+ for ( String id : ids )
+ {
+ IndexingContext context = getIndexingContext(id);
+ if ( context != null )
+ {
+ contexts.add( context );
+ }
+ else
+ {
+ log.warn( "context with id {} not exists", id );
+ }
+ }
+
+ return contexts;
+ }
+
+ private void constructQuery( String term, BooleanQuery.Builder q )
+ {
+ q.add( indexer.constructQuery( MAVEN.GROUP_ID, new UserInputSearchExpression( term ) ), Occur.SHOULD );
+ q.add( indexer.constructQuery( MAVEN.ARTIFACT_ID, new UserInputSearchExpression( term ) ), Occur.SHOULD );
+ q.add( indexer.constructQuery( MAVEN.VERSION, new UserInputSearchExpression( term ) ), Occur.SHOULD );
+ q.add( indexer.constructQuery( MAVEN.PACKAGING, new UserInputSearchExpression( term ) ), Occur.SHOULD );
+ q.add( indexer.constructQuery( MAVEN.CLASSNAMES, new UserInputSearchExpression( term ) ), Occur.SHOULD );
+
+ //Query query =
+ // new WildcardQuery( new Term( MAVEN.CLASSNAMES.getFieldName(), "*" ) );
+ //q.add( query, Occur.MUST_NOT );
+ // olamy IMHO we could set this option as at least one must match
+ //q.setMinimumNumberShouldMatch( 1 );
+ }
+
+
+ /**
+ * @param selectedRepos
+ * @return indexing contextId used
+ */
+ private List<String> addIndexingContexts( List<String> selectedRepos )
+ {
+ Set<String> indexingContextIds = new HashSet<>();
+ for ( String repo : selectedRepos )
+ {
+ try
+ {
+ Repository rRepo = repositoryRegistry.getRepository(repo);
+
+ if ( rRepo != null )
+ {
+
+ if (rRepo.getType().equals(RepositoryType.MAVEN)) {
+ assert rRepo.getIndexingContext() != null;
+ IndexingContext context = rRepo.getIndexingContext().getBaseContext(IndexingContext.class);
+ if (context.isSearchable()) {
+ indexingContextIds.addAll(getRemoteIndexingContextIds(repo));
+ indexingContextIds.add(context.getId());
+ } else {
+ log.warn("indexingContext with id {} not searchable", rRepo.getId());
+ }
+ }
+
+ }
+ else
+ {
+ log.warn( "Repository '{}' not found in configuration.", repo );
+ }
+ }
+ catch ( RepositorySearchException e )
+ {
+ log.warn( "RepositorySearchException occured while accessing index of repository '{}' : {}", repo,
+ e.getMessage() );
+ continue;
+ } catch (UnsupportedBaseContextException e) {
+ log.error("Fatal situation: Maven repository without IndexingContext found.");
+ continue;
+ }
+ }
+
+ return new ArrayList<>( indexingContextIds );
+ }
+
+
+ @Override
+ public Set<String> getRemoteIndexingContextIds( String managedRepoId )
+ throws RepositorySearchException
+ {
+ Set<String> ids = new HashSet<>();
+
+ List<ProxyConnector> proxyConnectors = null;
+ try
+ {
+ proxyConnectors = proxyConnectorAdmin.getProxyConnectorAsMap().get( managedRepoId );
+ }
+ catch ( RepositoryAdminException e )
+ {
+ throw new RepositorySearchException( e.getMessage(), e );
+ }
+
+ if ( proxyConnectors == null || proxyConnectors.isEmpty() )
+ {
+ return ids;
+ }
+
+ for ( ProxyConnector proxyConnector : proxyConnectors )
+ {
+ String remoteId = "remote-" + proxyConnector.getTargetRepoId();
+ RemoteRepository repo = repositoryRegistry.getRemoteRepository(proxyConnector.getTargetRepoId());
+ if (repo.getType()==RepositoryType.MAVEN) {
+ try {
+ IndexingContext context = repo.getIndexingContext() != null ? repo.getIndexingContext().getBaseContext(IndexingContext.class) : null;
+ if (context!=null && context.isSearchable()) {
+ ids.add(remoteId);
+ }
+ } catch (UnsupportedBaseContextException e) {
+ // Ignore this one
+ }
+ }
+ }
+
+ return ids;
+ }
+
+ @Override
+ public Collection<String> getAllGroupIds( String principal, List<String> selectedRepos )
+ throws RepositorySearchException
+ {
+ List<IndexingContext> indexContexts = getIndexingContexts( selectedRepos );
+
+ if ( indexContexts == null || indexContexts.isEmpty() )
+ {
+ return Collections.emptyList();
+ }
+
+ try
+ {
+ Set<String> allGroupIds = new HashSet<>();
+ for ( IndexingContext indexingContext : indexContexts )
+ {
+ allGroupIds.addAll( indexingContext.getAllGroups() );
+ }
+ return allGroupIds;
+ }
+ catch ( IOException e )
+ {
+ throw new RepositorySearchException( e.getMessage(), e );
+ }
+
+ }
+
+ private SearchResults convertToSearchResults( FlatSearchResponse response, SearchResultLimits limits,
+ List<? extends ArtifactInfoFilter> artifactInfoFilters,
+ List<String> selectedRepos, boolean includePoms )
+ throws RepositoryAdminException
+ {
+ SearchResults results = new SearchResults();
+ Set<ArtifactInfo> artifactInfos = response.getResults();
+
+ for ( ArtifactInfo artifactInfo : artifactInfos )
+ {
+ if ( StringUtils.equalsIgnoreCase( "pom", artifactInfo.getFileExtension() ) && !includePoms )
+ {
+ continue;
+ }
+ String id = SearchUtil.getHitId( artifactInfo.getGroupId(), //
+ artifactInfo.getArtifactId(), //
+ artifactInfo.getClassifier(), //
+ artifactInfo.getPackaging() );
+ Map<String, SearchResultHit> hitsMap = results.getHitsMap();
+
+
+ if ( !applyArtifactInfoFilters( artifactInfo, artifactInfoFilters, hitsMap ) )
+ {
+ continue;
+ }
+
+ SearchResultHit hit = hitsMap.get( id );
+ if ( hit != null )
+ {
+ if ( !hit.getVersions().contains( artifactInfo.getVersion() ) )
+ {
+ hit.addVersion( artifactInfo.getVersion() );
+ }
+ }
+ else
+ {
+ hit = new SearchResultHit();
+ hit.setArtifactId( artifactInfo.getArtifactId() );
+ hit.setGroupId( artifactInfo.getGroupId() );
+ hit.setRepositoryId( artifactInfo.getRepository() );
+ hit.addVersion( artifactInfo.getVersion() );
+ hit.setBundleExportPackage( artifactInfo.getBundleExportPackage() );
+ hit.setBundleExportService( artifactInfo.getBundleExportService() );
+ hit.setBundleSymbolicName( artifactInfo.getBundleSymbolicName() );
+ hit.setBundleVersion( artifactInfo.getBundleVersion() );
+ hit.setBundleDescription( artifactInfo.getBundleDescription() );
+ hit.setBundleDocUrl( artifactInfo.getBundleDocUrl() );
+ hit.setBundleRequireBundle( artifactInfo.getBundleRequireBundle() );
+ hit.setBundleImportPackage( artifactInfo.getBundleImportPackage() );
+ hit.setBundleLicense( artifactInfo.getBundleLicense() );
+ hit.setBundleName( artifactInfo.getBundleName() );
+ hit.setContext( artifactInfo.getContext() );
+ hit.setGoals( artifactInfo.getGoals() );
+ hit.setPrefix( artifactInfo.getPrefix() );
+ hit.setPackaging( artifactInfo.getPackaging() );
+ hit.setClassifier( artifactInfo.getClassifier() );
+ hit.setFileExtension( artifactInfo.getFileExtension() );
+ hit.setUrl( getBaseUrl( artifactInfo, selectedRepos ) );
+ }
+
+ results.addHit( id, hit );
+ }
+
+ results.setTotalHits( response.getTotalHitsCount() );
+ results.setTotalHitsMapSize( results.getHitsMap().values().size() );
+ results.setReturnedHitsCount( response.getReturnedHitsCount() );
+ results.setLimits( limits );
+
+ if ( limits == null || limits.getSelectedPage() == SearchResultLimits.ALL_PAGES )
+ {
+ return results;
+ }
+ else
+ {
+ return paginate( results );
+ }
+ }
+
+ /**
+ * calculate baseUrl without the context and base Archiva Url
+ *
+ * @param artifactInfo
+ * @return
+ */
+ protected String getBaseUrl( ArtifactInfo artifactInfo, List<String> selectedRepos )
+ throws RepositoryAdminException
+ {
+ StringBuilder sb = new StringBuilder();
+ if ( StringUtils.startsWith( artifactInfo.getContext(), "remote-" ) )
+ {
+ // it's a remote index result we search a managed which proxying this remote and on which
+ // current user has read karma
+ String managedRepoId =
+ getManagedRepoId( StringUtils.substringAfter( artifactInfo.getContext(), "remote-" ), selectedRepos );
+ if ( managedRepoId != null )
+ {
+ sb.append( '/' ).append( managedRepoId );
+ artifactInfo.setContext( managedRepoId );
+ }
+ }
+ else
+ {
+ sb.append( '/' ).append( artifactInfo.getContext() );
+ }
+
+ sb.append( '/' ).append( StringUtils.replaceChars( artifactInfo.getGroupId(), '.', '/' ) );
+ sb.append( '/' ).append( artifactInfo.getArtifactId() );
+ sb.append( '/' ).append( artifactInfo.getVersion() );
+ sb.append( '/' ).append( artifactInfo.getArtifactId() );
+ sb.append( '-' ).append( artifactInfo.getVersion() );
+ if ( StringUtils.isNotBlank( artifactInfo.getClassifier() ) )
+ {
+ sb.append( '-' ).append( artifactInfo.getClassifier() );
+ }
+ // maven-plugin packaging is a jar
+ if ( StringUtils.equals( "maven-plugin", artifactInfo.getPackaging() ) )
+ {
+ sb.append( "jar" );
+ }
+ else
+ {
+ sb.append( '.' ).append( artifactInfo.getPackaging() );
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * return a managed repo for a remote result
+ *
+ * @param remoteRepo
+ * @param selectedRepos
+ * @return
+ * @throws RepositoryAdminException
+ */
+ private String getManagedRepoId( String remoteRepo, List<String> selectedRepos )
+ throws RepositoryAdminException
+ {
+ Map<String, List<ProxyConnector>> proxyConnectorMap = proxyConnectorAdmin.getProxyConnectorAsMap();
+ if ( proxyConnectorMap == null || proxyConnectorMap.isEmpty() )
+ {
+ return null;
+ }
+ if ( selectedRepos != null && !selectedRepos.isEmpty() )
+ {
+ for ( Map.Entry<String, List<ProxyConnector>> entry : proxyConnectorMap.entrySet() )
+ {
+ if ( selectedRepos.contains( entry.getKey() ) )
+ {
+ for ( ProxyConnector proxyConnector : entry.getValue() )
+ {
+ if ( StringUtils.equals( remoteRepo, proxyConnector.getTargetRepoId() ) )
+ {
+ return proxyConnector.getSourceRepoId();
+ }
+ }
+ }
+ }
+ }
+
+ // we don't find in search selected repos so return the first one
+ for ( Map.Entry<String, List<ProxyConnector>> entry : proxyConnectorMap.entrySet() )
+ {
+
+ for ( ProxyConnector proxyConnector : entry.getValue() )
+ {
+ if ( StringUtils.equals( remoteRepo, proxyConnector.getTargetRepoId() ) )
+ {
+ return proxyConnector.getSourceRepoId();
+ }
+ }
+
+ }
+ return null;
+ }
+
+ private boolean applyArtifactInfoFilters( ArtifactInfo artifactInfo,
+ List<? extends ArtifactInfoFilter> artifactInfoFilters,
+ Map<String, SearchResultHit> currentResult )
+ {
+ if ( artifactInfoFilters == null || artifactInfoFilters.isEmpty() )
+ {
+ return true;
+ }
+
+ ArchivaArtifactModel artifact = new ArchivaArtifactModel();
+ artifact.setArtifactId( artifactInfo.getArtifactId() );
+ artifact.setClassifier( artifactInfo.getClassifier() );
+ artifact.setGroupId( artifactInfo.getGroupId() );
+ artifact.setRepositoryId( artifactInfo.getRepository() );
+ artifact.setVersion( artifactInfo.getVersion() );
+ artifact.setChecksumMD5( artifactInfo.getMd5() );
+ artifact.setChecksumSHA1( artifactInfo.getSha1() );
+ for ( ArtifactInfoFilter filter : artifactInfoFilters )
+ {
+ if ( !filter.addArtifactInResult( artifact, currentResult ) )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ protected SearchResults paginate( SearchResults results )
+ {
+ SearchResultLimits limits = results.getLimits();
+ SearchResults paginated = new SearchResults();
+
+ // ( limits.getPageSize() * ( Math.max( 1, limits.getSelectedPage() ) ) );
+
+ int fetchCount = limits.getPageSize();
+ int offset = ( limits.getSelectedPage() * limits.getPageSize() );
+
+ if ( fetchCount > results.getTotalHits() )
+ {
+ fetchCount = results.getTotalHits();
+ }
+
+ // Goto offset.
+ if ( offset < results.getTotalHits() )
+ {
+ // only process if the offset is within the hit count.
+ for ( int i = 0; i < fetchCount; i++ )
+ {
+ // Stop fetching if we are past the total # of available hits.
+ if ( offset + i >= results.getHits().size() )
+ {
+ break;
+ }
+
+ SearchResultHit hit = results.getHits().get( ( offset + i ) );
+ if ( hit != null )
+ {
+ String id = SearchUtil.getHitId( hit.getGroupId(), hit.getArtifactId(), hit.getClassifier(),
+ hit.getPackaging() );
+ paginated.addHit( id, hit );
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ paginated.setTotalHits( results.getTotalHits() );
+ paginated.setReturnedHitsCount( paginated.getHits().size() );
+ paginated.setTotalHitsMapSize( results.getTotalHitsMapSize() );
+ paginated.setLimits( limits );
+
+ return paginated;
+ }
+
+
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/DefaultMergedRemoteIndexesScheduler.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/DefaultMergedRemoteIndexesScheduler.java
new file mode 100644
index 000000000..f0fbcbd31
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/DefaultMergedRemoteIndexesScheduler.java
@@ -0,0 +1,94 @@
+package org.apache.archiva.indexer.merger;
+
+/*
+ * 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.RepositoryGroup;
+import org.apache.archiva.scheduler.MergedRemoteIndexesScheduler;
+import org.apache.commons.lang.StringUtils;
+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.inject.Inject;
+import javax.inject.Named;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledFuture;
+
+/**
+ * @author Olivier Lamy
+ * @since 2.0.0
+ */
+@Service( "mergedRemoteIndexesScheduler#default" )
+public class DefaultMergedRemoteIndexesScheduler
+ implements MergedRemoteIndexesScheduler
+{
+
+ private Logger logger = LoggerFactory.getLogger( getClass() );
+
+ @Inject
+ @Named( value = "taskScheduler#mergeRemoteIndexes" )
+ private TaskScheduler taskScheduler;
+
+ @Inject
+ private IndexMerger indexMerger;
+
+ private Map<String, ScheduledFuture> scheduledFutureMap = new ConcurrentHashMap<>();
+
+ @Override
+ public void schedule( RepositoryGroup repositoryGroup, Path directory )
+ {
+ if ( StringUtils.isEmpty( repositoryGroup.getCronExpression() ) )
+ {
+ return;
+ }
+ CronTrigger cronTrigger = new CronTrigger( repositoryGroup.getCronExpression() );
+
+ List<String> repositories = repositoryGroup.getRepositories();
+
+ IndexMergerRequest indexMergerRequest =
+ new IndexMergerRequest( repositories, true, repositoryGroup.getId(), repositoryGroup.getMergedIndexPath(),
+ repositoryGroup.getMergedIndexTtl() ).mergedIndexDirectory( directory );
+
+ MergedRemoteIndexesTaskRequest taskRequest =
+ new MergedRemoteIndexesTaskRequest( indexMergerRequest, indexMerger );
+
+ logger.info( "schedule merge remote index for group {} with cron {}", repositoryGroup.getId(),
+ repositoryGroup.getCronExpression() );
+
+ ScheduledFuture scheduledFuture =
+ taskScheduler.schedule( new MergedRemoteIndexesTask( taskRequest ), cronTrigger );
+ scheduledFutureMap.put( repositoryGroup.getId(), scheduledFuture );
+ }
+
+ @Override
+ public void unschedule( RepositoryGroup repositoryGroup )
+ {
+ ScheduledFuture scheduledFuture = scheduledFutureMap.remove( repositoryGroup.getId() );
+ if ( scheduledFuture != null )
+ {
+ scheduledFuture.cancel( true );
+ }
+ }
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/IndexMerger.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/IndexMerger.java
new file mode 100644
index 000000000..756819e4f
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/IndexMerger.java
@@ -0,0 +1,42 @@
+package org.apache.archiva.indexer.merger;
+/*
+ * 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 java.util.Collection;
+
+/**
+ * @author Olivier Lamy
+ * @since 1.4-M2
+ */
+public interface IndexMerger
+{
+ /**
+ * @param indexMergerRequest
+ * @return a temporary directory with a merge index (directory marked deleteOnExit)
+ * @throws IndexMergerException
+ */
+ IndexingContext buildMergedIndex( IndexMergerRequest indexMergerRequest )
+ throws IndexMergerException;
+
+ void cleanTemporaryGroupIndex( TemporaryGroupIndex temporaryGroupIndex );
+
+ Collection<TemporaryGroupIndex> getTemporaryGroupIndexes();
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/IndexMergerException.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/IndexMergerException.java
new file mode 100644
index 000000000..3930831d6
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/IndexMergerException.java
@@ -0,0 +1,32 @@
+package org.apache.archiva.indexer.merger;
+/*
+ * 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.
+ */
+
+/**
+ * @author Olivier Lamy
+ * @since 1.4-M2
+ */
+public class IndexMergerException
+ extends Exception
+{
+ public IndexMergerException( String message, Throwable t )
+ {
+ super( message, t );
+ }
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/IndexMergerRequest.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/IndexMergerRequest.java
new file mode 100644
index 000000000..0b3803f48
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/IndexMergerRequest.java
@@ -0,0 +1,192 @@
+package org.apache.archiva.indexer.merger;
+/*
+ * 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 java.nio.file.Path;
+import java.util.Collection;
+
+/**
+ * @author Olivier Lamy
+ */
+public class IndexMergerRequest
+{
+ /**
+ * repositories Ids to merge content
+ */
+ private Collection<String> repositoriesIds;
+
+ /**
+ * will generate a downloadable index
+ */
+ private boolean packIndex;
+
+ /**
+ * original groupId (repositoryGroup id)
+ */
+ private String groupId;
+
+ private String mergedIndexPath = ".indexer";
+
+ private int mergedIndexTtl;
+
+ private Path mergedIndexDirectory;
+
+ private boolean temporary;
+
+ public IndexMergerRequest( Collection<String> repositoriesIds, boolean packIndex, String groupId )
+ {
+ this.repositoriesIds = repositoriesIds;
+ this.packIndex = packIndex;
+ this.groupId = groupId;
+ }
+
+ /**
+ * @since 1.4-M4
+ */
+ public IndexMergerRequest( Collection<String> repositoriesIds, boolean packIndex, String groupId,
+ String mergedIndexPath, int mergedIndexTtl )
+ {
+ this.repositoriesIds = repositoriesIds;
+ this.packIndex = packIndex;
+ this.groupId = groupId;
+ this.mergedIndexPath = mergedIndexPath;
+ this.mergedIndexTtl = mergedIndexTtl;
+ }
+
+ public Collection<String> getRepositoriesIds()
+ {
+ return repositoriesIds;
+ }
+
+ public void setRepositoriesIds( Collection<String> repositoriesIds )
+ {
+ this.repositoriesIds = repositoriesIds;
+ }
+
+ public boolean isPackIndex()
+ {
+ return packIndex;
+ }
+
+ public void setPackIndex( boolean packIndex )
+ {
+ this.packIndex = packIndex;
+ }
+
+ public String getGroupId()
+ {
+ return groupId;
+ }
+
+ public void setGroupId( String groupId )
+ {
+ this.groupId = groupId;
+ }
+
+ public String getMergedIndexPath()
+ {
+ return mergedIndexPath;
+ }
+
+ public void setMergedIndexPath( String mergedIndexPath )
+ {
+ this.mergedIndexPath = mergedIndexPath;
+ }
+
+ public int getMergedIndexTtl()
+ {
+ return mergedIndexTtl;
+ }
+
+ public void setMergedIndexTtl( int mergedIndexTtl )
+ {
+ this.mergedIndexTtl = mergedIndexTtl;
+ }
+
+ public Path getMergedIndexDirectory()
+ {
+ return mergedIndexDirectory;
+ }
+
+ public void setMergedIndexDirectory( Path mergedIndexDirectory )
+ {
+ this.mergedIndexDirectory = mergedIndexDirectory;
+ }
+
+ public IndexMergerRequest mergedIndexDirectory( Path mergedIndexDirectory )
+ {
+ this.mergedIndexDirectory = mergedIndexDirectory;
+ return this;
+ }
+
+ public boolean isTemporary()
+ {
+ return temporary;
+ }
+
+ public void setTemporary( boolean temporary )
+ {
+ this.temporary = temporary;
+ }
+
+
+ public IndexMergerRequest temporary( boolean temporary )
+ {
+ this.temporary = temporary;
+ return this;
+ }
+
+ @Override
+ public String toString()
+ {
+ final StringBuilder sb = new StringBuilder( "IndexMergerRequest{" );
+ sb.append( "repositoriesIds=" ).append( repositoriesIds );
+ sb.append( ", packIndex=" ).append( packIndex );
+ sb.append( ", groupId='" ).append( groupId ).append( '\'' );
+ sb.append( ", mergedIndexPath='" ).append( mergedIndexPath ).append( '\'' );
+ sb.append( ", mergedIndexTtl=" ).append( mergedIndexTtl );
+ sb.append( ", mergedIndexDirectory=" ).append( mergedIndexDirectory );
+ sb.append( ", temporary=" ).append( temporary );
+ sb.append( '}' );
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals( Object o )
+ {
+ if ( this == o )
+ {
+ return true;
+ }
+ if ( o == null || getClass() != o.getClass() )
+ {
+ return false;
+ }
+
+ IndexMergerRequest that = (IndexMergerRequest) o;
+
+ return groupId.equals( that.groupId );
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return groupId.hashCode();
+ }
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/MergedRemoteIndexesTask.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/MergedRemoteIndexesTask.java
new file mode 100644
index 000000000..c67d31286
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/MergedRemoteIndexesTask.java
@@ -0,0 +1,89 @@
+package org.apache.archiva.indexer.merger;
+
+/*
+ * 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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Olivier Lamy
+ * @since 2.0.0
+ */
+public class MergedRemoteIndexesTask
+ implements Runnable
+{
+
+ private Logger logger = LoggerFactory.getLogger( getClass() );
+
+ private MergedRemoteIndexesTaskRequest mergedRemoteIndexesTaskRequest;
+
+ public MergedRemoteIndexesTask( MergedRemoteIndexesTaskRequest mergedRemoteIndexesTaskRequest )
+ {
+ this.mergedRemoteIndexesTaskRequest = mergedRemoteIndexesTaskRequest;
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ this.execute();
+ }
+ catch ( IndexMergerException e )
+ {
+ logger.error( e.getMessage(), e );
+ }
+ }
+
+ public MergedRemoteIndexesTaskResult execute()
+ throws IndexMergerException
+ {
+ IndexMerger indexMerger = mergedRemoteIndexesTaskRequest.getIndexMerger();
+
+ IndexingContext indexingContext =
+ indexMerger.buildMergedIndex( mergedRemoteIndexesTaskRequest.getIndexMergerRequest() );
+
+ return new MergedRemoteIndexesTaskResult( indexingContext );
+ }
+
+ @Override
+ public boolean equals( Object o )
+ {
+ if ( this == o )
+ {
+ return true;
+ }
+ if ( !( o instanceof MergedRemoteIndexesTask ) )
+ {
+ return false;
+ }
+
+ MergedRemoteIndexesTask that = (MergedRemoteIndexesTask) o;
+
+ return mergedRemoteIndexesTaskRequest.equals( that.mergedRemoteIndexesTaskRequest );
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return mergedRemoteIndexesTaskRequest.hashCode();
+ }
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/MergedRemoteIndexesTaskRequest.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/MergedRemoteIndexesTaskRequest.java
new file mode 100644
index 000000000..bc0663dbc
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/MergedRemoteIndexesTaskRequest.java
@@ -0,0 +1,80 @@
+package org.apache.archiva.indexer.merger;
+
+/*
+ * 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.
+ */
+
+/**
+ * @author Olivier Lamy
+ * @since 2.0.0
+ */
+public class MergedRemoteIndexesTaskRequest
+{
+ private IndexMergerRequest indexMergerRequest;
+
+ private IndexMerger indexMerger;
+
+ public MergedRemoteIndexesTaskRequest( IndexMergerRequest indexMergerRequest, IndexMerger indexMerger )
+ {
+ this.indexMergerRequest = indexMergerRequest;
+ this.indexMerger = indexMerger;
+ }
+
+ public IndexMergerRequest getIndexMergerRequest()
+ {
+ return indexMergerRequest;
+ }
+
+ public void setIndexMergerRequest( IndexMergerRequest indexMergerRequest )
+ {
+ this.indexMergerRequest = indexMergerRequest;
+ }
+
+ public IndexMerger getIndexMerger()
+ {
+ return indexMerger;
+ }
+
+ public void setIndexMerger( IndexMerger indexMerger )
+ {
+ this.indexMerger = indexMerger;
+ }
+
+ @Override
+ public boolean equals( Object o )
+ {
+ if ( this == o )
+ {
+ return true;
+ }
+ if ( o == null || getClass() != o.getClass() )
+ {
+ return false;
+ }
+
+ MergedRemoteIndexesTaskRequest that = (MergedRemoteIndexesTaskRequest) o;
+
+ return indexMergerRequest.equals( that.indexMergerRequest );
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return indexMergerRequest.hashCode();
+ }
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/MergedRemoteIndexesTaskResult.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/MergedRemoteIndexesTaskResult.java
new file mode 100644
index 000000000..b75a29219
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/MergedRemoteIndexesTaskResult.java
@@ -0,0 +1,46 @@
+package org.apache.archiva.indexer.merger;
+
+/*
+ * 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;
+
+/**
+ * @author Olivier Lamy
+ * @since 2.0.0
+ */
+public class MergedRemoteIndexesTaskResult
+{
+ private IndexingContext indexingContext;
+
+ public MergedRemoteIndexesTaskResult( IndexingContext indexingContext )
+ {
+ this.indexingContext = indexingContext;
+ }
+
+ public IndexingContext getIndexingContext()
+ {
+ return indexingContext;
+ }
+
+ public void setIndexingContext( IndexingContext indexingContext )
+ {
+ this.indexingContext = indexingContext;
+ }
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/TemporaryGroupIndex.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/TemporaryGroupIndex.java
new file mode 100644
index 000000000..d0b576b74
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/TemporaryGroupIndex.java
@@ -0,0 +1,119 @@
+package org.apache.archiva.indexer.merger;
+/*
+ * 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 java.io.Serializable;
+import java.nio.file.Path;
+import java.util.Date;
+
+/**
+ * @author Olivier Lamy
+ */
+public class TemporaryGroupIndex
+ implements Serializable
+{
+ private long creationTime = new Date().getTime();
+
+ private Path directory;
+
+ private String indexId;
+
+ private String groupId;
+
+ private int mergedIndexTtl;
+
+ public TemporaryGroupIndex(Path directory, String indexId, String groupId, int mergedIndexTtl)
+ {
+ this.directory = directory;
+ this.indexId = indexId;
+ this.groupId = groupId;
+ this.mergedIndexTtl = mergedIndexTtl;
+ }
+
+ public long getCreationTime()
+ {
+ return creationTime;
+ }
+
+ public TemporaryGroupIndex setCreationTime( long creationTime )
+ {
+ this.creationTime = creationTime;
+ return this;
+ }
+
+ public Path getDirectory()
+ {
+ return directory;
+ }
+
+ public TemporaryGroupIndex setDirectory( Path directory )
+ {
+ this.directory = directory;
+ return this;
+ }
+
+ public String getIndexId()
+ {
+ return indexId;
+ }
+
+ public TemporaryGroupIndex setIndexId( String indexId )
+ {
+ this.indexId = indexId;
+ return this;
+ }
+
+ public String getGroupId()
+ {
+ return groupId;
+ }
+
+ public void setGroupId( String groupId )
+ {
+ this.groupId = groupId;
+ }
+
+ public int getMergedIndexTtl() {
+ return mergedIndexTtl;
+ }
+
+ public void setMergedIndexTtl(int mergedIndexTtl) {
+ this.mergedIndexTtl = mergedIndexTtl;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Long.toString( creationTime ).hashCode();
+ }
+
+ @Override
+ public boolean equals( Object o )
+ {
+ if ( this == o )
+ {
+ return true;
+ }
+ if ( !( o instanceof TemporaryGroupIndex ) )
+ {
+ return false;
+ }
+ return this.creationTime == ( (TemporaryGroupIndex) o ).creationTime;
+ }
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/TemporaryGroupIndexCleaner.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/TemporaryGroupIndexCleaner.java
new file mode 100644
index 000000000..583ddd2c8
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/merger/TemporaryGroupIndexCleaner.java
@@ -0,0 +1,67 @@
+package org.apache.archiva.indexer.merger;
+/*
+ * 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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import javax.inject.Inject;
+import java.util.Date;
+
+/**
+ * @author Olivier Lamy
+ * @since 1.4-M2
+ */
+@Service
+public class TemporaryGroupIndexCleaner
+{
+ private Logger log = LoggerFactory.getLogger( getClass() );
+
+ @Inject
+ private IndexMerger indexMerger;
+
+
+ public TemporaryGroupIndexCleaner( )
+ {
+
+ }
+
+ // 900000
+ @Scheduled(fixedDelay = 900000)
+ public void cleanTemporaryIndex()
+ {
+
+ indexMerger.getTemporaryGroupIndexes()
+ .stream()
+ .forEach( temporaryGroupIndex ->
+ {
+ // cleanup files older than the ttl
+ if ( new Date().getTime() - temporaryGroupIndex.getCreationTime() >
+ temporaryGroupIndex.getMergedIndexTtl() )
+ {
+ log.info( "cleanTemporaryIndex for groupId {}", temporaryGroupIndex.getGroupId() );
+ indexMerger.cleanTemporaryGroupIndex( temporaryGroupIndex );
+
+ }
+ }
+ );
+ }
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/search/NoClassifierArtifactInfoFilter.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/search/NoClassifierArtifactInfoFilter.java
new file mode 100644
index 000000000..f37c87407
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/search/NoClassifierArtifactInfoFilter.java
@@ -0,0 +1,43 @@
+package org.apache.archiva.indexer.search;
+/*
+ * 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.model.ArchivaArtifactModel;
+import org.apache.commons.lang.StringUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Olivier Lamy
+ */
+public class NoClassifierArtifactInfoFilter
+ implements ArtifactInfoFilter
+{
+ public static final NoClassifierArtifactInfoFilter INSTANCE = new NoClassifierArtifactInfoFilter();
+
+ public static final List<? extends ArtifactInfoFilter> LIST = Arrays.asList( INSTANCE );
+
+ @Override
+ public boolean addArtifactInResult( ArchivaArtifactModel artifact, Map<String, SearchResultHit> currentResult )
+ {
+ return StringUtils.isBlank( artifact.getClassifier() );
+ }
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/util/SearchUtil.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/util/SearchUtil.java
new file mode 100644
index 000000000..0a0c922ee
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/java/org/apache/archiva/indexer/util/SearchUtil.java
@@ -0,0 +1,36 @@
+package org.apache.archiva.indexer.util;
+
+/*
+ * 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.commons.lang.StringUtils;
+
+/**
+ * SearchUtil - utility class for search.
+ */
+public class SearchUtil
+{
+ public static String getHitId( String groupId, String artifactId, String classifier, String packaging )
+ {
+ return ( StringUtils.isBlank( groupId ) ? "" : StringUtils.trim( groupId ) ) + ":" //
+ + ( StringUtils.isBlank( artifactId ) ? "" : StringUtils.trim( artifactId ) ) + ":" //
+ + ( StringUtils.isBlank( classifier ) ? "" : StringUtils.trim( classifier ) ) + ":" //
+ + ( StringUtils.isBlank( packaging ) ? "" : StringUtils.trim( packaging ) );
+ }
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/resources/META-INF/spring-context.xml b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/resources/META-INF/spring-context.xml
new file mode 100644
index 000000000..870a13b56
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/main/resources/META-INF/spring-context.xml
@@ -0,0 +1,41 @@
+<?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: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.indexer.maven,org.apache.maven.index"/>
+
+
+ <bean name="taskScheduler#mergeRemoteIndexes"
+ class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
+ <property name="poolSize" value="4"/>
+ <property name="threadGroupName" value="mergeRemoteIndexes"/>
+ </bean>
+
+</beans> \ No newline at end of file
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/MavenIndexManagerTest.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/MavenIndexManagerTest.java
new file mode 100644
index 000000000..71363dc33
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/MavenIndexManagerTest.java
@@ -0,0 +1,218 @@
+package org.apache.archiva.indexer.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.common.utils.FileUtils;
+import org.apache.archiva.indexer.ArchivaIndexingContext;
+import org.apache.archiva.indexer.IndexCreationFailedException;
+import org.apache.archiva.repository.RepositoryRegistry;
+import org.apache.archiva.repository.RepositoryType;
+import org.apache.archiva.repository.features.IndexCreationFeature;
+import org.apache.archiva.repository.features.RemoteIndexFeature;
+import org.apache.archiva.repository.maven2.MavenManagedRepository;
+import org.apache.archiva.repository.maven2.MavenRemoteRepository;
+import org.apache.archiva.test.utils.ArchivaSpringJUnit4ClassRunner;
+import org.apache.maven.index.MAVEN;
+import org.apache.maven.index.QueryCreator;
+import org.apache.maven.index.context.IndexingContext;
+import org.apache.maven.index.expr.UserInputSearchExpression;
+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.Query;
+import org.junit.After;
+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.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.*;
+
+@RunWith( ArchivaSpringJUnit4ClassRunner.class )
+@ContextConfiguration( locations = { "classpath*:/META-INF/spring-context.xml", "classpath:/spring-context.xml" } )
+public class MavenIndexManagerTest {
+
+ @Inject
+ RepositoryRegistry repositoryRegistry;
+
+
+ private Path indexPath;
+ private MavenManagedRepository repository;
+ private ArchivaIndexingContext ctx;
+ private MavenRemoteRepository repositoryRemote;
+
+ @Inject
+ MavenIndexManager mavenIndexManager;
+
+ @Inject
+ QueryCreator queryCreator;
+
+
+ @After
+ public void tearDown() {
+ repositoryRegistry.destroy();
+ if (ctx!=null) {
+ try {
+ ctx.close(true);
+ } catch (IOException e) {
+ //
+ }
+ }
+ if (indexPath!=null && Files.exists(indexPath)) {
+ FileUtils.deleteQuietly(indexPath);
+ }
+
+ }
+
+ @Test
+ public void pack() throws Exception {
+ createTestContext();
+ Path destDir = repository.getLocalPath().resolve("org/apache/archiva/archiva-webapp/1.0");
+ Path srcDir = Paths.get("src/test/maven-search-test-repo/org/apache/archiva/archiva-webapp/1.0");
+ org.apache.commons.io.FileUtils.copyDirectory(srcDir.toFile(),destDir.toFile());
+ mavenIndexManager.scan(ctx);
+ mavenIndexManager.pack(ctx);
+ assertTrue(Files.list(indexPath).filter(path -> {
+ try {
+ return path.getFileName().toString().endsWith(".gz") && Files.size(path) > 0;
+ } catch (IOException e) {
+ return false;
+ }
+ }).findAny().isPresent());
+ }
+
+ @Test
+ public void scan() throws Exception {
+ createTestContext();
+ Path destDir = repository.getLocalPath().resolve("org/apache/archiva/archiva-webapp/1.0");
+ Path srcDir = Paths.get("src/test/maven-search-test-repo/org/apache/archiva/archiva-webapp/1.0");
+ org.apache.commons.io.FileUtils.copyDirectory(srcDir.toFile(),destDir.toFile());
+ mavenIndexManager.scan(ctx);
+
+ IndexingContext mvnCtx = mavenIndexManager.getMvnContext(ctx);
+ String term = "org.apache.archiva";
+ Query q = new BooleanQuery.Builder().add( queryCreator.constructQuery( MAVEN.GROUP_ID, new UserInputSearchExpression( term ) ),
+ BooleanClause.Occur.SHOULD ).build();
+ assertEquals(4, mvnCtx.acquireIndexSearcher().count(q));
+ }
+
+ /*
+ * Does only a index update via file uri, no HTTP uri
+ */
+ @Test
+ public void update() throws Exception {
+ createTestContext();
+ mavenIndexManager.pack(ctx);
+ ctx.close(false);
+ createTestContextForRemote();
+ mavenIndexManager.update(ctx, true);
+ }
+
+ @Test
+ public void addArtifactsToIndex() throws Exception {
+
+ ArchivaIndexingContext ctx = createTestContext();
+ Path destDir = repository.getLocalPath().resolve("org/apache/archiva/archiva-search/1.0");
+ Path srcDir = Paths.get("src/test/maven-search-test-repo/org/apache/archiva/archiva-search/1.0");
+ org.apache.commons.io.FileUtils.copyDirectory(srcDir.toFile(), destDir.toFile());
+ List<URI> uriList = new ArrayList<>();
+ uriList.add(destDir.resolve("archiva-search-1.0.jar").toUri());
+ uriList.add(destDir.resolve("archiva-search-1.0-sources.jar").toUri());
+ mavenIndexManager.addArtifactsToIndex(ctx, uriList);
+
+ IndexingContext mvnCtx = mavenIndexManager.getMvnContext(ctx);
+ String term = "org.apache.archiva";
+ Query q = new BooleanQuery.Builder().add( queryCreator.constructQuery( MAVEN.GROUP_ID, new UserInputSearchExpression( term ) ),
+ BooleanClause.Occur.SHOULD ).build();
+ assertEquals(2, mvnCtx.acquireIndexSearcher().count(q));
+ }
+
+ @Test
+ public void removeArtifactsFromIndex() throws Exception {
+ ArchivaIndexingContext ctx = createTestContext();
+ Path destDir = repository.getLocalPath().resolve("org/apache/archiva/archiva-search/1.0");
+ Path srcDir = Paths.get("src/test/maven-search-test-repo/org/apache/archiva/archiva-search/1.0");
+ org.apache.commons.io.FileUtils.copyDirectory(srcDir.toFile(), destDir.toFile());
+ List<URI> uriList = new ArrayList<>();
+ uriList.add(destDir.resolve("archiva-search-1.0.jar").toUri());
+ uriList.add(destDir.resolve("archiva-search-1.0-sources.jar").toUri());
+ mavenIndexManager.addArtifactsToIndex(ctx, uriList);
+
+ IndexingContext mvnCtx = mavenIndexManager.getMvnContext(ctx);
+ String term = "org.apache.archiva";
+ Query q = new BooleanQuery.Builder().add( queryCreator.constructQuery( MAVEN.GROUP_ID, new UserInputSearchExpression( term ) ),
+ BooleanClause.Occur.SHOULD ).build();
+ assertEquals(2, mvnCtx.acquireIndexSearcher().count(q));
+ uriList.remove(0);
+ mavenIndexManager.removeArtifactsFromIndex(ctx, uriList);
+ assertEquals(1, mvnCtx.acquireIndexSearcher().count(q));
+ }
+
+ @Test
+ public void supportsRepository() throws Exception {
+ assertTrue(mavenIndexManager.supportsRepository(RepositoryType.MAVEN));
+ assertFalse(mavenIndexManager.supportsRepository(RepositoryType.NPM));
+ }
+
+ private ArchivaIndexingContext createTestContext() throws URISyntaxException, IndexCreationFailedException, IOException {
+ indexPath = Paths.get("target/repositories/test-repo/.index-test");
+ FileUtils.deleteDirectory(indexPath);
+ repository = new MavenManagedRepository("test-repo", "Test Repo", Paths.get("target/repositories"));
+ repository.setLocation(new URI("test-repo"));
+ IndexCreationFeature icf = repository.getFeature(IndexCreationFeature.class).get();
+ icf.setIndexPath(new URI(".index-test"));
+ ctx = mavenIndexManager.createContext(repository);
+ return ctx;
+ }
+
+ private ArchivaIndexingContext createTestContextForRemote() throws URISyntaxException, IndexCreationFailedException, IOException {
+ indexPath = Paths.get("target/repositories/test-repo/.index-test");
+ Path repoPath = Paths.get("target/repositories").toAbsolutePath();
+ repositoryRemote = new MavenRemoteRepository("test-repo", "Test Repo", repoPath);
+ repositoryRemote.setLocation(repoPath.resolve("test-repo").toUri());
+ RemoteIndexFeature icf = repositoryRemote.getFeature(RemoteIndexFeature.class).get();
+ icf.setIndexUri(new URI(".index-test"));
+ ctx = mavenIndexManager.createContext(repositoryRemote);
+ return ctx;
+ }
+
+ @Test
+ public void createContext() throws Exception {
+ ArchivaIndexingContext ctx = createTestContext();
+ assertNotNull(ctx);
+ assertEquals(repository, ctx.getRepository());
+ assertEquals("test-repo", ctx.getId());
+ assertEquals(indexPath.toAbsolutePath(), Paths.get(ctx.getPath()).toAbsolutePath());
+ assertTrue(Files.exists(indexPath));
+ List<Path> li = Files.list(indexPath).collect(Collectors.toList());
+ assertTrue(li.size()>0);
+
+ }
+
+} \ No newline at end of file
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/AbstractMavenRepositorySearch.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/AbstractMavenRepositorySearch.java
new file mode 100644
index 000000000..cbf5eee87
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/AbstractMavenRepositorySearch.java
@@ -0,0 +1,328 @@
+package org.apache.archiva.indexer.maven.search;
+
+/*
+* 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.admin.repository.proxyconnector.DefaultProxyConnectorAdmin;
+import org.apache.archiva.common.utils.FileUtils;
+import org.apache.archiva.configuration.ArchivaConfiguration;
+import org.apache.archiva.configuration.Configuration;
+import org.apache.archiva.configuration.ConfigurationListener;
+import org.apache.archiva.configuration.ManagedRepositoryConfiguration;
+import org.apache.archiva.indexer.search.SearchResultHit;
+import org.apache.archiva.indexer.search.SearchResults;
+import org.apache.archiva.repository.Repository;
+import org.apache.archiva.repository.RepositoryRegistry;
+import org.apache.archiva.repository.features.IndexCreationFeature;
+import org.apache.archiva.test.utils.ArchivaSpringJUnit4ClassRunner;
+import org.apache.commons.lang.SystemUtils;
+import org.apache.maven.index.ArtifactContext;
+import org.apache.maven.index.ArtifactContextProducer;
+import org.apache.maven.index.ArtifactScanningListener;
+import org.apache.maven.index.DefaultScannerListener;
+import org.apache.maven.index.Indexer;
+import org.apache.maven.index.IndexerEngine;
+import org.apache.maven.index.QueryCreator;
+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.easymock.EasyMock;
+import org.easymock.IMocksControl;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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.List;
+import java.util.Locale;
+
+/**
+ * @author Olivier Lamy
+ */
+@RunWith( ArchivaSpringJUnit4ClassRunner.class )
+@ContextConfiguration( locations = { "classpath*:/META-INF/spring-context.xml", "classpath:/spring-context.xml" } )
+public abstract class AbstractMavenRepositorySearch
+ extends TestCase
+{
+
+ protected Logger log = LoggerFactory.getLogger( getClass() );
+
+ public static String TEST_REPO_1 = "maven-search-test-repo";
+
+ public static String TEST_REPO_2 = "maven-search-test-repo-2";
+
+
+ public static String REPO_RELEASE = "repo-release";
+
+ MavenRepositorySearch search;
+
+ ArchivaConfiguration archivaConfig;
+
+ @Inject
+ ArtifactContextProducer artifactContextProducer;
+
+ @Inject
+ RepositoryRegistry repositoryRegistry;
+
+ @Inject
+ private IndexerEngine indexerEngine;
+
+ IMocksControl archivaConfigControl;
+
+ Configuration config;
+
+ @Inject
+ Indexer indexer;
+
+ @Inject
+ Scanner scanner;
+
+ @Inject
+ QueryCreator queryCreator;
+
+ @Before
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ super.setUp();
+
+ FileUtils.deleteDirectory( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "/target/repos/" + TEST_REPO_1 + "/.indexer" ) );
+ assertFalse( Files.exists(Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "/target/repos/" + TEST_REPO_1 + "/.indexer" )) );
+
+ FileUtils.deleteDirectory( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "/target/repos/" + TEST_REPO_2 + "/.indexer" ) );
+ assertFalse( Files.exists(Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "/target/repos/" + TEST_REPO_2 + "/.indexer" )) );
+
+ archivaConfigControl = EasyMock.createControl();
+
+ archivaConfig = archivaConfigControl.createMock( ArchivaConfiguration.class );
+
+ DefaultProxyConnectorAdmin defaultProxyConnectorAdmin = new DefaultProxyConnectorAdmin();
+ defaultProxyConnectorAdmin.setArchivaConfiguration( archivaConfig );
+ repositoryRegistry.setArchivaConfiguration( archivaConfig );
+
+ search = new MavenRepositorySearch( indexer, repositoryRegistry, defaultProxyConnectorAdmin,
+ queryCreator );
+
+ assertNotNull( repositoryRegistry );
+
+ config = new Configuration();
+ config.addManagedRepository( createRepositoryConfig( TEST_REPO_1 ) );
+ config.addManagedRepository( createRepositoryConfig( TEST_REPO_2 ) );
+ config.addManagedRepository( createRepositoryConfig( REPO_RELEASE ) );
+
+ archivaConfig.addListener( EasyMock.anyObject( ConfigurationListener.class ) );
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration() ).andReturn(config).anyTimes();
+ archivaConfig.save(EasyMock.anyObject(Configuration.class));
+ EasyMock.expectLastCall().anyTimes();
+ archivaConfigControl.replay();
+ repositoryRegistry.reload();
+ archivaConfigControl.reset();
+ }
+
+ @After
+ @Override
+ public void tearDown()
+ throws Exception
+ {
+ archivaConfigControl.reset();
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration() ).andReturn(config).anyTimes();
+ archivaConfig.save(EasyMock.anyObject(Configuration.class));
+ EasyMock.expectLastCall().anyTimes();
+ archivaConfigControl.replay();
+ repositoryRegistry.removeRepository(TEST_REPO_1);
+ repositoryRegistry.removeRepository(TEST_REPO_2);
+ repositoryRegistry.removeRepository(REPO_RELEASE);
+ repositoryRegistry.destroy();
+ FileUtils.deleteDirectory( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "/target/repos/" + TEST_REPO_1 ) );
+ assertFalse( Files.exists(Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "/target/repos/" + TEST_REPO_1 )) );
+
+ FileUtils.deleteDirectory( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "/target/repos/" + TEST_REPO_2 ) );
+ assertFalse( Files.exists(Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "/target/repos/" + TEST_REPO_2 )) );
+
+ super.tearDown();
+ }
+
+ protected ManagedRepositoryConfiguration createRepositoryConfig( String repository )
+ {
+ ManagedRepositoryConfiguration repositoryConfig = new ManagedRepositoryConfiguration();
+ repositoryConfig.setId( repository );
+ repositoryConfig.setLocation( org.apache.archiva.common.utils.FileUtils.getBasedir() + "/target/repos/" + repository );
+ Path f = Paths.get( repositoryConfig.getLocation() );
+ if ( !Files.exists(f) )
+ {
+ try
+ {
+ Files.createDirectories( f );
+ }
+ catch ( IOException e )
+ {
+ log.error("Could not create directories for {}", f);
+ }
+ }
+ repositoryConfig.setLayout( "default" );
+ repositoryConfig.setName( repository );
+ repositoryConfig.setScanned( true );
+ repositoryConfig.setSnapshots( false );
+ repositoryConfig.setReleases( true );
+ repositoryConfig.setIndexDir(".indexer");
+
+ return repositoryConfig;
+ }
+
+ protected void createIndex( String repository, List<Path> filesToBeIndexed, boolean scan) throws Exception {
+ createIndex(repository, filesToBeIndexed, scan, null);
+ }
+
+ protected void createIndex( String repository, List<Path> filesToBeIndexed, boolean scan, Path indexDir)
+ throws Exception
+ {
+ Repository rRepo = repositoryRegistry.getRepository(repository);
+ IndexCreationFeature icf = rRepo.getFeature(IndexCreationFeature.class).get();
+
+
+ IndexingContext context = rRepo.getIndexingContext().getBaseContext(IndexingContext.class);
+
+ if ( context != null )
+ {
+ context.close(true);
+ }
+
+ Path repoDir = Paths.get(org.apache.archiva.common.utils.FileUtils.getBasedir()).resolve("target").resolve("repos").resolve(repository);
+
+ Path indexerDirectory = repoDir.resolve(".indexer" );
+
+ if ( Files.exists(indexerDirectory) )
+ {
+ FileUtils.deleteDirectory( indexerDirectory );
+ }
+
+ assertFalse( Files.exists(indexerDirectory) );
+
+ Path lockFile = repoDir.resolve(".indexer/write.lock" );
+ if ( Files.exists(lockFile) )
+ {
+ Files.delete(lockFile);
+ }
+
+ assertFalse( Files.exists(lockFile) );
+
+ Path repo = Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "src/test/" + repository );
+ assertTrue( Files.exists(repo) );
+ org.apache.commons.io.FileUtils.copyDirectory(repo.toFile(), repoDir.toFile());
+
+ if (indexDir==null) {
+ Path indexDirectory =
+ Paths.get(org.apache.archiva.common.utils.FileUtils.getBasedir(), "target/index/test-" + Long.toString(System.currentTimeMillis()));
+ indexDirectory.toFile().deleteOnExit();
+ FileUtils.deleteDirectory(indexDirectory);
+ icf.setIndexPath(indexDirectory.toUri());
+ } else {
+
+ icf.setIndexPath(indexDir.toUri());
+ }
+ context = rRepo.getIndexingContext().getBaseContext(IndexingContext.class);
+
+
+ // minimize datas in memory
+// context.getIndexWriter().setMaxBufferedDocs( -1 );
+// context.getIndexWriter().setRAMBufferSizeMB( 1 );
+ for ( Path artifactFile : filesToBeIndexed )
+ {
+ assertTrue( "file not exists " + artifactFile, Files.exists(artifactFile) );
+ ArtifactContext ac = artifactContextProducer.getArtifactContext( context, artifactFile.toFile() );
+
+ if ( artifactFile.toString().endsWith( ".pom" ) )
+ {
+ ac.getArtifactInfo().setFileExtension( "pom" );
+ ac.getArtifactInfo().setPackaging( "pom" );
+ ac.getArtifactInfo().setClassifier( "pom" );
+ }
+ indexer.addArtifactToIndex( ac, context );
+ context.updateTimestamp( true );
+ }
+
+ if ( scan )
+ {
+ DefaultScannerListener listener = new DefaultScannerListener( context, indexerEngine, true, new ArtifactScanListener());
+ ScanningRequest req = new ScanningRequest(context, listener );
+ scanner.scan( req );
+ context.commit();
+ }
+ // force flushing
+ context.commit();
+ // context.getIndexWriter().commit();
+ context.setSearchable( true );
+
+ }
+
+ static class ArtifactScanListener
+ implements ArtifactScanningListener
+ {
+ protected Logger log = LoggerFactory.getLogger( getClass() );
+
+ @Override
+ public void scanningStarted( IndexingContext ctx )
+ {
+ //
+ }
+
+ @Override
+ public void scanningFinished( IndexingContext ctx, ScanningResult result )
+ {
+ // no op
+ }
+
+ @Override
+ public void artifactError( ArtifactContext ac, Exception e )
+ {
+ log.debug( "artifactError {}", ac.getArtifact().getPath(), e );
+ }
+
+ @Override
+ public void artifactDiscovered( ArtifactContext ac )
+ {
+ log.debug( "artifactDiscovered {}:{}", //
+ ac.getArtifact() == null ? "" : ac.getArtifact().getPath(), //
+ ac.getArtifact() == null ? "" : ac.getArtifactInfo() );
+ }
+ }
+
+ public String niceDisplay( SearchResults searchResults )
+ throws Exception
+ {
+ StringBuilder sb = new StringBuilder();
+ for ( SearchResultHit hit : searchResults.getHits() )
+ {
+ sb.append( hit.toString() ).append( SystemUtils.LINE_SEPARATOR );
+ }
+ return sb.toString();
+ }
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchOSGITest.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchOSGITest.java
new file mode 100644
index 000000000..066b2af92
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchOSGITest.java
@@ -0,0 +1,94 @@
+package org.apache.archiva.indexer.maven.search;
+
+/*
+ * 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.indexer.search.SearchFields;
+import org.apache.archiva.indexer.search.SearchResultHit;
+import org.apache.archiva.indexer.search.SearchResults;
+import org.apache.archiva.repository.RepositoryRegistry;
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Test;
+
+import javax.inject.Inject;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * @author Olivier Lamy
+ */
+public class MavenRepositorySearchOSGITest
+ extends AbstractMavenRepositorySearch
+{
+
+ @Inject
+ RepositoryRegistry repositoryRegistry;
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ repositoryRegistry.destroy();
+ }
+
+ @Test
+ public void searchFelixWithSymbolicName()
+ throws Exception
+ {
+
+ createIndex( TEST_REPO_1, Collections.<Path>emptyList(), true );
+
+ List<String> selectedRepos = Arrays.asList( TEST_REPO_1 );
+
+ // search artifactId
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ SearchFields searchFields = new SearchFields();
+ searchFields.setBundleSymbolicName( "org.apache.felix.bundlerepository" );
+ searchFields.setBundleVersion( "1.6.6" );
+ searchFields.setRepositories( selectedRepos );
+
+ SearchResults results = search.search( "user", searchFields, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+ assertEquals( 1, results.getTotalHits() );
+
+ SearchResultHit hit = results.getHits().get( 0 );
+ assertEquals( "org.apache.felix", hit.getGroupId() );
+ assertEquals( "org.apache.felix.bundlerepository", hit.getArtifactId() );
+ assertEquals( "1.6.6", hit.getVersions().get( 0 ) );
+
+ assertEquals( "org.apache.felix.bundlerepository;uses:=\"org.osgi.framework\";version=\"2.0\"",
+ hit.getBundleExportPackage() );
+ assertEquals( "org.apache.felix.bundlerepository.RepositoryAdmin,org.osgi.service.obr.RepositoryAdmin",
+ hit.getBundleExportService() );
+ assertEquals( "org.apache.felix.bundlerepository", hit.getBundleSymbolicName() );
+ assertEquals( "1.6.6", hit.getBundleVersion() );
+ }
+
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchPaginateTest.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchPaginateTest.java
new file mode 100644
index 000000000..3390cc47e
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchPaginateTest.java
@@ -0,0 +1,123 @@
+package org.apache.archiva.indexer.maven.search;
+/*
+ * 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.search.SearchResultHit;
+import org.apache.archiva.indexer.search.SearchResultLimits;
+import org.apache.archiva.indexer.search.SearchResults;
+import org.apache.archiva.indexer.util.SearchUtil;
+import org.apache.archiva.repository.RepositoryRegistry;
+import org.apache.archiva.test.utils.ArchivaSpringJUnit4ClassRunner;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+
+import java.util.Arrays;
+
+/**
+ * @author Olivier Lamy
+ */
+@RunWith( ArchivaSpringJUnit4ClassRunner.class )
+@ContextConfiguration( locations = { "classpath*:/META-INF/spring-context.xml", "classpath:/spring-context.xml" } )
+public class MavenRepositorySearchPaginateTest
+ extends TestCase
+{
+
+ @Autowired
+ RepositoryRegistry repositoryRegistry;
+
+ @After
+ public void endTests() {
+ assert repositoryRegistry!=null;
+ repositoryRegistry.destroy();
+ }
+
+ @Test
+ public void nonPaginatedResult()
+ throws Exception
+ {
+ MavenRepositorySearch search = new MavenRepositorySearch();
+
+ SearchResults searchResults = build( 10, new SearchResultLimits( 0 ) );
+
+ searchResults = search.paginate( searchResults );
+
+ assertEquals( 10, searchResults.getReturnedHitsCount() );
+
+ }
+
+ @Test
+ public void nonPaginatedHugeResult()
+ throws Exception
+ {
+ MavenRepositorySearch search = new MavenRepositorySearch();
+
+ SearchResults origSearchResults = build( 63, new SearchResultLimits( 0 ) );
+
+ SearchResults searchResults = search.paginate( origSearchResults );
+
+ assertEquals( 30, searchResults.getReturnedHitsCount() );
+
+ origSearchResults = build( 63, new SearchResultLimits( 1 ) );
+
+ searchResults = search.paginate( origSearchResults );
+
+ assertEquals( 30, searchResults.getReturnedHitsCount() );
+
+ }
+
+ @Test
+ public void paginatedResult()
+ throws Exception
+ {
+ MavenRepositorySearch search = new MavenRepositorySearch();
+
+ SearchResults searchResults = build( 32, new SearchResultLimits( 1 ) );
+
+ searchResults = search.paginate( searchResults );
+
+ assertEquals( 2, searchResults.getReturnedHitsCount() );
+
+ }
+
+
+ SearchResults build( int number, SearchResultLimits limits )
+ {
+ SearchResults searchResults = new SearchResults();
+ searchResults.setLimits( limits );
+ for ( int i = 0; i < number; i++ )
+ {
+ SearchResultHit hit = new SearchResultHit();
+ hit.setGroupId( "commons-foo" );
+ hit.setArtifactId( "commons-bar-" + i );
+ hit.setPackaging( "jar" );
+ hit.setVersions( Arrays.asList( "1.0" ) );
+ String id =
+ SearchUtil.getHitId( hit.getGroupId(), hit.getArtifactId(), hit.getClassifier(), hit.getPackaging() );
+ searchResults.addHit( id, hit );
+ }
+
+ searchResults.setTotalHits( number );
+ return searchResults;
+
+ }
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchTest.java b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchTest.java
new file mode 100644
index 000000000..cb43a1a08
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchTest.java
@@ -0,0 +1,941 @@
+package org.apache.archiva.indexer.maven.search;
+
+/*
+ * 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.indexer.search.RepositorySearchException;
+import org.apache.archiva.indexer.search.SearchFields;
+import org.apache.archiva.indexer.search.SearchResultHit;
+import org.apache.archiva.indexer.search.SearchResultLimits;
+import org.apache.archiva.indexer.search.SearchResults;
+import org.apache.archiva.indexer.util.SearchUtil;
+import org.apache.archiva.test.utils.ArchivaSpringJUnit4ClassRunner;
+import org.apache.maven.index_shaded.lucene.index.IndexUpgrader;
+import org.codehaus.plexus.util.FileUtils;
+import org.easymock.EasyMock;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.test.context.ContextConfiguration;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+
+@RunWith( ArchivaSpringJUnit4ClassRunner.class )
+@ContextConfiguration( locations = { "classpath*:/META-INF/spring-context.xml", "classpath:/spring-context.xml" } )
+public class MavenRepositorySearchTest
+ extends AbstractMavenRepositorySearch
+{
+
+
+ private void createSimpleIndex( boolean scan )
+ throws Exception
+ {
+ List<Path> files = new ArrayList<>();
+ files.add( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "target/repos", TEST_REPO_1,
+ "/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.jar" ) );
+ files.add( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "target/repos", TEST_REPO_1,
+ "/org/apache/archiva/archiva-test/1.0/archiva-test-1.0.jar" ));
+ files.add( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "target/repos", TEST_REPO_1,
+ "org/apache/archiva/archiva-test/2.0/archiva-test-2.0.jar" ));
+
+ createIndex( TEST_REPO_1, files, scan );
+ }
+
+ private void createIndexContainingMoreArtifacts( boolean scan )
+ throws Exception
+ {
+ List<Path> files = new ArrayList<>();
+ files.add( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "target/repos/" + TEST_REPO_1
+ + "/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.jar" ) );
+ files.add( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "target/repos/" + TEST_REPO_1
+ + "/org/apache/archiva/archiva-test/1.0/archiva-test-1.0.jar" ) );
+ files.add( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "target/repos/" + TEST_REPO_1
+ + "/org/apache/archiva/archiva-test/2.0/archiva-test-2.0.jar" ) );
+ files.add( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "target/repos/" + TEST_REPO_1
+ + "/org/apache/archiva/archiva-webapp/1.0/archiva-webapp-1.0.war" ) );
+ files.add( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(),
+ "target/repos/" + TEST_REPO_1 + "/com/artifactid-numeric/1.0/artifactid-numeric-1.0.jar" ) );
+ files.add( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "target/repos/" + TEST_REPO_1
+ + "/com/artifactid-numeric123/1.0/artifactid-numeric123-1.0.jar" ) );
+ files.add( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(),
+ "target/repos/" + TEST_REPO_1 + "/com/classname-search/1.0/classname-search-1.0.jar" ) );
+
+ createIndex( TEST_REPO_1, files, scan );
+ }
+
+ private void createIndexContainingMultipleArtifactsSameVersion( boolean scan )
+ throws Exception
+ {
+ List<Path> files = new ArrayList<>();
+
+ files.add( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "target/repos/" + TEST_REPO_1
+ + "/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.jar" ) );
+
+ files.add( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "target/repos/" + TEST_REPO_1
+ + "/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.pom" ) );
+
+ files.add( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "target/repos/" + TEST_REPO_1
+ + "/org/apache/archiva/archiva-search/1.0/archiva-search-1.0-sources.jar" ) );
+
+ createIndex( TEST_REPO_1, files, scan );
+ }
+
+ @Test
+ public void testQuickSearch()
+ throws Exception
+ {
+ createSimpleIndex( false );
+
+ List<String> selectedRepos = Arrays.asList( TEST_REPO_1 );
+
+ // search artifactId
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ SearchResults results = search.search( "user", selectedRepos, "archiva-search", null, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+
+ SearchResultHit hit =
+ results.getSearchResultHit( SearchUtil.getHitId( "org.apache.archiva", "archiva-search", null, "jar" ) );
+ assertNotNull( "hit null in result " + results.getHits(), hit );
+ assertEquals( "org.apache.archiva", hit.getGroupId() );
+ assertEquals( "archiva-search", hit.getArtifactId() );
+ assertEquals( "1.0", hit.getVersions().get( 0 ) );
+
+ archivaConfigControl.reset();
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ // search groupId
+ archivaConfigControl.replay();
+
+ results = search.search( "user", selectedRepos, "org.apache.archiva", null, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+ assertEquals( "total hints not 3", 3, results.getTotalHits() );
+
+ //TODO: search for class & package names
+ }
+
+ @Test
+ public void testQuickSearchNotWithClassifier()
+ throws Exception
+ {
+ createSimpleIndex( true );
+
+ List<String> selectedRepos = Arrays.asList( TEST_REPO_1 );
+
+ // search artifactId
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ SearchResults results = search.search( "user", selectedRepos, "archiva-search", null, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+
+ SearchResultHit hit =
+ results.getSearchResultHit( SearchUtil.getHitId( "org.apache.archiva", "archiva-search", null, "jar" ) );
+ assertNotNull( "hit null in result " + results.getHits(), hit );
+ assertEquals( "org.apache.archiva", hit.getGroupId() );
+ assertEquals( "archiva-search", hit.getArtifactId() );
+ assertEquals( "1.0", hit.getVersions().get( 0 ) );
+
+ archivaConfigControl.reset();
+
+ // search groupId
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ results = search.search( "user", selectedRepos, "archiva-search", null, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+ assertEquals( "total hints not 3 hits " + results.getHits(), 3, results.getTotalHits() );
+
+ //TODO: search for class & package names
+ }
+
+ @Test
+ public void testQuickSearchMultipleArtifactsSameVersion()
+ throws Exception
+ {
+ createIndexContainingMultipleArtifactsSameVersion( false );
+
+ List<String> selectedRepos = new ArrayList<>();
+ selectedRepos.add( TEST_REPO_1 );
+
+ // search artifactId
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ SearchResults results = search.search( "user", selectedRepos, "archiva-search", null, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+ assertEquals( 3, results.getTotalHits() );
+
+ SearchResultHit hit = results.getHits().get( 0 );
+ assertEquals( "org.apache.archiva", hit.getGroupId() );
+ assertEquals( "archiva-search", hit.getArtifactId() );
+ assertEquals( "1.0", hit.getVersions().get( 0 ) );
+
+ //only 1 version of 1.0 is retrieved
+ assertEquals( 1, hit.getVersions().size() );
+ }
+
+ @Test
+ public void testMultipleArtifactsSameVersionWithClassifier()
+ throws Exception
+ {
+ createIndexContainingMultipleArtifactsSameVersion( true );
+
+ List<String> selectedRepos = new ArrayList<>();
+ selectedRepos.add( TEST_REPO_1 );
+
+ // search artifactId
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ SearchFields searchFields = new SearchFields();
+ searchFields.setGroupId( "org.apache.archiva" );
+ searchFields.setArtifactId( "archiva-search" );
+ searchFields.setClassifier( "sources" );
+ searchFields.setRepositories( selectedRepos );
+
+ SearchResults results = search.search( "user", searchFields, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+ assertEquals( 1, results.getTotalHits() );
+
+ SearchResultHit hit = results.getHits().get( 0 );
+ assertEquals( "org.apache.archiva", hit.getGroupId() );
+ assertEquals( "archiva-search", hit.getArtifactId() );
+ assertEquals( "1.0", hit.getVersions().get( 0 ) );
+
+ //only 1 version of 1.0 is retrieved
+ assertEquals( 1, hit.getVersions().size() );
+ }
+
+ // search for existing artifact using multiple keywords
+ @Test
+ public void testQuickSearchWithMultipleKeywords()
+ throws Exception
+ {
+ createSimpleIndex( false );
+
+ List<String> selectedRepos = new ArrayList<>();
+ selectedRepos.add( TEST_REPO_1 );
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+ archivaConfigControl.replay();
+
+ SearchResults results = search.search( "user", selectedRepos, "archiva search", null, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+ assertEquals( 1, results.getTotalHits() );
+ }
+
+ @Test
+ public void testQuickSearchWithPagination()
+ throws Exception
+ {
+ createSimpleIndex( true );
+
+ List<String> selectedRepos = new ArrayList<>();
+ selectedRepos.add( TEST_REPO_1 );
+
+ // page 1
+ SearchResultLimits limits = new SearchResultLimits( 0 );
+ limits.setPageSize( 1 );
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ SearchResults results = search.search( "user", selectedRepos, "org", limits, Collections.emptyList() );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+ assertEquals( 1, results.getHits().size() );
+ assertEquals( "total hits not 9 for page1 " + results, 9, results.getTotalHits() );
+ assertEquals( "returned hits not 1 for page1 " + results, 1, results.getReturnedHitsCount() );
+ assertEquals( limits, results.getLimits() );
+
+ archivaConfigControl.reset();
+
+ // page 2
+ limits = new SearchResultLimits( 1 );
+ limits.setPageSize( 1 );
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ results = search.search( "user", selectedRepos, "org", limits, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+
+ assertEquals( "hits not 1", 1, results.getHits().size() );
+ assertEquals( "total hits not 9 for page 2 " + results, 9, results.getTotalHits() );
+ assertEquals( "returned hits not 1 for page2 " + results, 1, results.getReturnedHitsCount() );
+ assertEquals( limits, results.getLimits() );
+ }
+
+ @Test
+ public void testArtifactFoundInMultipleRepositories()
+ throws Exception
+ {
+ createSimpleIndex( true );
+
+ List<Path> files = new ArrayList<>();
+ files.add( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "target/repos/" + TEST_REPO_2
+ + "/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.jar" ) );
+ files.add( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "target/repos/" + TEST_REPO_2
+ + "/org/apache/archiva/archiva-search/1.1/archiva-search-1.1.jar" ) );
+ createIndex( TEST_REPO_2, files, false );
+
+ List<String> selectedRepos = new ArrayList<>();
+ selectedRepos.add( TEST_REPO_1 );
+ selectedRepos.add( TEST_REPO_2 );
+
+ config.addManagedRepository( createRepositoryConfig( TEST_REPO_2 ) );
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ // wait lucene flush.....
+ Thread.sleep( 2000 );
+
+ SearchResults results = search.search( "user", selectedRepos, "archiva-search", null, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+
+ SearchResultHit hit =
+ results.getSearchResultHit( SearchUtil.getHitId( "org.apache.archiva", "archiva-search", null, "jar" ) );
+ assertNotNull(hit);
+ assertEquals( "org.apache.archiva", hit.getGroupId() );
+ assertEquals( "archiva-search", hit.getArtifactId() );
+ assertEquals( "not 2 version for hit " + hit + "::" + niceDisplay( results ), 2, hit.getVersions().size() );
+ assertTrue( hit.getVersions().contains( "1.0" ) );
+ assertTrue( hit.getVersions().contains( "1.1" ) );
+
+ archivaConfigControl.reset();
+
+ // TODO: [BROWSE] in artifact info from browse, display all the repositories where the artifact is found
+ }
+
+ @Test
+ public void testNoMatchFound()
+ throws Exception
+ {
+ createSimpleIndex( false );
+
+ List<String> selectedRepos = new ArrayList<>();
+ selectedRepos.add( TEST_REPO_1 );
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ SearchResults results = search.search( "user", selectedRepos, "dfghdfkweriuasndsaie", null, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+ assertEquals( 0, results.getTotalHits() );
+ }
+
+ @Test
+ public void testNoIndexFound()
+ throws Exception
+ {
+ List<String> selectedRepos = new ArrayList<>();
+ selectedRepos.add( TEST_REPO_1 );
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ SearchResults results = search.search( "user", selectedRepos, "org.apache.archiva", null, null );
+ assertNotNull( results );
+ assertEquals( 0, results.getTotalHits() );
+
+ archivaConfigControl.verify();
+ }
+
+ @Test
+ public void testRepositoryNotFound()
+ throws Exception
+ {
+ List<String> selectedRepos = new ArrayList<>();
+ selectedRepos.add( "non-existing-repo" );
+
+ archivaConfigControl.replay();
+
+ SearchResults results = search.search( "user", selectedRepos, "org.apache.archiva", null, null );
+ assertNotNull( results );
+ assertEquals( 0, results.getTotalHits() );
+
+ archivaConfigControl.verify();
+ }
+
+ @Test
+ public void testSearchWithinSearchResults()
+ throws Exception
+ {
+ createSimpleIndex( true );
+
+ List<String> selectedRepos = new ArrayList<>();
+ selectedRepos.add( TEST_REPO_1 );
+
+ List<String> previousSearchTerms = new ArrayList<>();
+ previousSearchTerms.add( "archiva-test" );
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ SearchResults results = search.search( "user", selectedRepos, "1.0", null, previousSearchTerms );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+ assertEquals( "total hints not 1", 1, results.getTotalHits() );
+
+ SearchResultHit hit = results.getHits().get( 0 );
+ assertEquals( "org.apache.archiva", hit.getGroupId() );
+ assertEquals( "archiva-test", hit.getArtifactId() );
+ assertEquals( "versions not 1", 1, hit.getVersions().size() );
+ assertEquals( "1.0", hit.getVersions().get( 0 ) );
+ }
+
+ // tests for advanced search
+ @Test
+ public void testAdvancedSearch()
+ throws Exception
+ {
+ List<Path> files = new ArrayList<>();
+ files.add( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "target/repos/" + TEST_REPO_2
+ + "/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.jar" ) );
+ files.add( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "target/repos/" + TEST_REPO_2
+ + "/org/apache/archiva/archiva-search/1.1/archiva-search-1.1.jar" ) );
+ createIndex( TEST_REPO_2, files, false );
+
+ List<String> selectedRepos = new ArrayList<>();
+ selectedRepos.add( TEST_REPO_2 );
+
+ SearchFields searchFields = new SearchFields();
+ searchFields.setGroupId( "org.apache.archiva" );
+ searchFields.setVersion( "1.0" );
+ searchFields.setRepositories( selectedRepos );
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ SearchResults results = search.search( "user", searchFields, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+ assertEquals( 1, results.getTotalHits() );
+
+ SearchResultHit hit = results.getHits().get( 0 );
+ assertEquals( "org.apache.archiva", hit.getGroupId() );
+ assertEquals( "archiva-search", hit.getArtifactId() );
+ assertEquals( "1.0", hit.getVersions().get( 0 ) );
+ }
+
+ @Test
+ public void testAdvancedSearchWithPagination()
+ throws Exception
+ {
+ createIndexContainingMoreArtifacts( false );
+
+ List<String> selectedRepos = new ArrayList<>();
+ selectedRepos.add( TEST_REPO_1 );
+
+ SearchFields searchFields = new SearchFields();
+ searchFields.setGroupId( "org.apache.archiva" );
+ searchFields.setRepositories( selectedRepos );
+
+ // page 1
+
+ SearchResultLimits limits = new SearchResultLimits( 0 );
+ limits.setPageSize( 1 );
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ SearchResults results = search.search( "user", searchFields, limits );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+ assertEquals( 4, results.getTotalHits() );
+ assertEquals( 1, results.getHits().size() );
+
+ // page 2
+ archivaConfigControl.reset();
+
+ limits = new SearchResultLimits( 1 );
+ limits.setPageSize( 1 );
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ results = search.search( "user", searchFields, limits );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+ assertEquals( 4, results.getTotalHits() );
+ assertEquals( 1, results.getHits().size() );
+ }
+
+ // MRM-981 - artifactIds with numeric characters aren't found in advanced search
+ @Test
+ public void testAdvancedSearchArtifactIdHasNumericChar()
+ throws Exception
+ {
+ List<Path> files = new ArrayList<>();
+ files.add( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(),
+ "target/repos/" + TEST_REPO_1 + "/com/artifactid-numeric/1.0/artifactid-numeric-1.0.jar" ) );
+ files.add( Paths.get( org.apache.archiva.common.utils.FileUtils.getBasedir(), "target/repos/" + TEST_REPO_1
+ + "/com/artifactid-numeric123/1.0/artifactid-numeric123-1.0.jar" ) );
+ createIndex( TEST_REPO_1, files, true );
+
+ List<String> selectedRepos = new ArrayList<>();
+ selectedRepos.add( TEST_REPO_1 );
+
+ SearchFields searchFields = new SearchFields();
+ searchFields.setArtifactId( "artifactid-numeric" );
+ searchFields.setRepositories( selectedRepos );
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ SearchResults results = search.search( "user", searchFields, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+ assertEquals( 2, results.getTotalHits() );
+ }
+
+ @Test
+ public void testAdvancedSearchNoRepositoriesConfigured()
+ throws Exception
+ {
+ SearchFields searchFields = new SearchFields();
+ searchFields.setArtifactId( "archiva" );
+ searchFields.setRepositories( null );
+
+ try
+ {
+ search.search( "user", searchFields, null );
+ fail( "A RepositorySearchExcecption should have been thrown." );
+ }
+ catch ( RepositorySearchException e )
+ {
+ assertEquals( "Repositories cannot be null.", e.getMessage() );
+ }
+ }
+
+ @Test
+ public void testAdvancedSearchSearchFieldsAreNull()
+ throws Exception
+ {
+ List<String> selectedRepos = new ArrayList<>();
+ selectedRepos.add( TEST_REPO_1 );
+
+ SearchFields searchFields = new SearchFields();
+ searchFields.setRepositories( selectedRepos );
+
+ try
+ {
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ search.search( "user", searchFields, null );
+
+ archivaConfigControl.verify();
+
+ fail( "A RepositorySearchExcecption should have been thrown." );
+ }
+ catch ( RepositorySearchException e )
+ {
+ assertEquals( "No search fields set.", e.getMessage() );
+ }
+ }
+
+ @Test
+ public void testAdvancedSearchSearchFieldsAreBlank()
+ throws Exception
+ {
+ List<String> selectedRepos = new ArrayList<>();
+ selectedRepos.add( TEST_REPO_1 );
+
+ SearchFields searchFields = new SearchFields();
+ searchFields.setGroupId( "" );
+ searchFields.setArtifactId( "" );
+ searchFields.setVersion( "" );
+ searchFields.setPackaging( "" );
+ searchFields.setClassName( "" );
+
+ searchFields.setRepositories( selectedRepos );
+
+ try
+ {
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+ archivaConfigControl.replay();
+
+ search.search( "user", searchFields, null );
+
+ archivaConfigControl.verify();
+
+ fail( "A RepositorySearchException should have been thrown." );
+ }
+ catch ( RepositorySearchException e )
+ {
+ assertEquals( "No search fields set.", e.getMessage() );
+ }
+ }
+
+ @Test
+ public void testAdvancedSearchAllSearchCriteriaSpecified()
+ throws Exception
+ {
+ createSimpleIndex( true );
+
+ List<String> selectedRepos = new ArrayList<>();
+ selectedRepos.add( TEST_REPO_1 );
+
+ SearchFields searchFields = new SearchFields();
+ searchFields.setGroupId( "org.apache.archiva" );
+ searchFields.setArtifactId( "archiva-test" );
+ searchFields.setVersion( "2.0" );
+ searchFields.setPackaging( "jar" );
+ searchFields.setClassName( "org.apache.archiva.test.App" );
+ searchFields.setRepositories( selectedRepos );
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ SearchResults results = search.search( "user", searchFields, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+
+ assertEquals( "total hints not 1" + results, 1, results.getTotalHits() );
+
+ SearchResultHit hit = results.getHits().get( 0 );
+ assertEquals( "org.apache.archiva", hit.getGroupId() );
+ assertEquals( "archiva-test", hit.getArtifactId() );
+ assertEquals( "version not 2.0", "2.0", hit.getVersions().get( 0 ) );
+ }
+
+ @Test
+ public void testAdvancedSearchJarArtifacts()
+ throws Exception
+ {
+ createIndexContainingMoreArtifacts( true );
+
+ List<String> selectedRepos = new ArrayList<>();
+ selectedRepos.add( TEST_REPO_1 );
+
+ SearchFields searchFields = new SearchFields();
+ searchFields.setPackaging( "jar" );
+ searchFields.setRepositories( selectedRepos );
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ SearchResults results = search.search( "user", searchFields, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+ assertEquals( "not 8 but " + results.getTotalHits() + ":" + niceDisplay( results ), 8, results.getTotalHits() );
+ }
+
+ @Test
+ public void testAdvancedSearchWithIncorrectPackaging()
+ throws Exception
+ {
+ createSimpleIndex( true );
+
+ List<String> selectedRepos = new ArrayList<>();
+ selectedRepos.add( TEST_REPO_1 );
+
+ SearchFields searchFields = new SearchFields();
+ searchFields.setGroupId( "org.apache.archiva" );
+ searchFields.setArtifactId( "archiva-test" );
+ searchFields.setVersion( "2.0" );
+ searchFields.setPackaging( "war" );
+ searchFields.setRepositories( selectedRepos );
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ SearchResults results = search.search( "user", searchFields, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+ assertEquals( 0, results.getTotalHits() );
+ }
+
+ @Test
+ public void testAdvancedSearchClassname()
+ throws Exception
+ {
+ createIndexContainingMoreArtifacts( true );
+
+ List<String> selectedRepos = Arrays.asList( TEST_REPO_1 );
+
+ SearchFields searchFields = new SearchFields();
+ searchFields.setClassName( "com.classname.search.App" );
+ searchFields.setRepositories( selectedRepos );
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ SearchResults results = search.search( "user", searchFields, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+ assertEquals( "totalHits not 1 results " + results, 1, results.getTotalHits() );
+
+ SearchResultHit hit = results.getHits().get( 0 );
+ assertEquals( "groupId not com", "com", hit.getGroupId() );
+ assertEquals( "arttifactId not classname-search", "classname-search", hit.getArtifactId() );
+ assertEquals( " hits.version(0) not 1.0", "1.0", hit.getVersions().get( 0 ) );
+ }
+
+ @Test
+ public void testAdvancedSearchNoIndexFound()
+ throws Exception
+ {
+ List<String> selectedRepos = new ArrayList<>();
+ selectedRepos.add( TEST_REPO_1 );
+
+ SearchFields searchFields = new SearchFields();
+ searchFields.setGroupId( "org.apache.archiva" );
+ searchFields.setRepositories( selectedRepos );
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ SearchResults results = search.search( "user", searchFields, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+ assertEquals( 0, results.getTotalHits() );
+ }
+
+ @Test
+ public void testAdvancedSearchClassNameInWar()
+ throws Exception
+ {
+ createIndexContainingMoreArtifacts( true );
+
+ List<String> selectedRepos = Arrays.asList( TEST_REPO_1 );
+
+ SearchFields searchFields = new SearchFields();
+ searchFields.setClassName( "SomeClass" );
+ searchFields.setRepositories( selectedRepos );
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ SearchResults results = search.search( "user", searchFields, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+ assertEquals( 1, results.getHits().size() );
+ assertEquals( "test-webapp", results.getHits().get( 0 ).getArtifactId() );
+ }
+
+ @Test
+ public void getAllGroupIds()
+ throws Exception
+ {
+ createIndexContainingMoreArtifacts( true );
+
+ List<String> selectedRepos = Arrays.asList( TEST_REPO_1 );
+
+ EasyMock.expect( archivaConfig.getConfiguration() ).andReturn( config ).times( 0, 2 );
+
+ archivaConfigControl.replay();
+
+ Collection<String> groupIds = search.getAllGroupIds( "user", selectedRepos );
+
+ archivaConfigControl.verify();
+
+ log.info( "groupIds: {}", groupIds );
+
+ assertEquals( 3, groupIds.size() );
+ assertTrue( groupIds.contains( "com" ) );
+ assertTrue( groupIds.contains( "org.apache.felix" ) );
+ assertTrue( groupIds.contains( "org.apache.archiva" ) );
+ }
+
+ @Test
+ public void testSearchWithUnknownRepo()
+ throws Exception
+ {
+ createIndexContainingMoreArtifacts( true );
+
+ List<String> selectedRepos = Arrays.asList( "foo" );
+
+ SearchFields searchFields = new SearchFields();
+ searchFields.setClassName( "SomeClass" );
+ searchFields.setRepositories( selectedRepos );
+
+ archivaConfigControl.replay();
+
+ SearchResults results = search.search( "user", searchFields, null );
+
+ archivaConfigControl.verify();
+
+ assertNotNull( results );
+ assertEquals( 0, results.getHits().size() );
+ }
+
+ @Test
+ public void nolimitedResult()
+ throws Exception
+ {
+
+ Path repo = Paths.get( "target/repo-release" );
+ FileUtils.deleteDirectory(repo.toFile());
+ Path indexDirectory = repo.resolve(".index" );
+ FileUtils.copyDirectoryStructure( Paths.get( "src/test/repo-release" ).toFile(), repo.toFile() );
+
+ IndexUpgrader.main( new String[]{ indexDirectory.toAbsolutePath().toString() } );
+
+ createIndex(REPO_RELEASE, Collections.emptyList(), false, indexDirectory );
+
+// indexer.addIndexingContext( REPO_RELEASE, REPO_RELEASE, repo.toFile(), indexDirectory.toFile(),
+// repo.toUri().toURL().toExternalForm(),
+// indexDirectory.toUri().toURL().toString(), indexCreators );
+
+
+
+ SearchResultLimits limits = new SearchResultLimits( SearchResultLimits.ALL_PAGES );
+ limits.setPageSize( 300 );
+
+ EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( Locale.getDefault( ) ).anyTimes();
+ EasyMock.expect( archivaConfig.getConfiguration()).andReturn(config).anyTimes();
+
+ archivaConfigControl.replay();
+
+ SearchResults searchResults = search.search( null, Arrays.asList( REPO_RELEASE ), //
+ "org.example", limits, //
+ Collections.emptyList() );
+
+ log.info( "results: {}", searchResults.getHits().size() );
+
+ assertEquals( 255, searchResults.getHits().size() );
+
+ SearchFields searchFields = new SearchFields();
+ searchFields.setGroupId( "org.example" );
+ searchFields.setRepositories( Arrays.asList( REPO_RELEASE ) );
+
+ searchResults = search.search( null, searchFields, limits );
+
+ log.info( "results: {}", searchResults.getHits().size() );
+
+ assertEquals( 255, searchResults.getHits().size() );
+
+ archivaConfigControl.verify();
+ }
+}
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo-2/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.jar b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo-2/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.jar
new file mode 100644
index 000000000..9c16c3ced
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo-2/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.jar
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo-2/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.pom b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo-2/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.pom
new file mode 100644
index 000000000..b0d983fde
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo-2/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.pom
@@ -0,0 +1,28 @@
+<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/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-search</artifactId>
+ <packaging>jar</packaging>
+ <version>1.0</version>
+ <name>Archiva Search</name>
+ <url>http://archiva.apache.org</url>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ <version>2.3</version>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.8</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo-2/org/apache/archiva/archiva-search/1.1/archiva-search-1.1.jar b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo-2/org/apache/archiva/archiva-search/1.1/archiva-search-1.1.jar
new file mode 100644
index 000000000..97eaf869b
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo-2/org/apache/archiva/archiva-search/1.1/archiva-search-1.1.jar
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo-2/org/apache/archiva/archiva-search/1.1/archiva-search-1.1.pom b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo-2/org/apache/archiva/archiva-search/1.1/archiva-search-1.1.pom
new file mode 100644
index 000000000..be02c7c4d
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo-2/org/apache/archiva/archiva-search/1.1/archiva-search-1.1.pom
@@ -0,0 +1,28 @@
+<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/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-search</artifactId>
+ <packaging>jar</packaging>
+ <version>1.1</version>
+ <name>Archiva Search</name>
+ <url>http://archiva.apache.org</url>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ <version>2.3</version>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.8</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/artifactid-numeric/1.0/artifactid-numeric-1.0.jar b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/artifactid-numeric/1.0/artifactid-numeric-1.0.jar
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/artifactid-numeric/1.0/artifactid-numeric-1.0.jar
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/artifactid-numeric/1.0/artifactid-numeric-1.0.pom b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/artifactid-numeric/1.0/artifactid-numeric-1.0.pom
new file mode 100644
index 000000000..3ef0bb6c9
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/artifactid-numeric/1.0/artifactid-numeric-1.0.pom
@@ -0,0 +1,17 @@
+<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/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com</groupId>
+ <artifactId>artifactid-numeric</artifactId>
+ <packaging>jar</packaging>
+ <version>1.0</version>
+ <name>ArtifactID numeric - NOT</name>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/artifactid-numeric123/1.0/artifactid-numeric123-1.0.jar b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/artifactid-numeric123/1.0/artifactid-numeric123-1.0.jar
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/artifactid-numeric123/1.0/artifactid-numeric123-1.0.jar
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/artifactid-numeric123/1.0/artifactid-numeric123-1.0.pom b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/artifactid-numeric123/1.0/artifactid-numeric123-1.0.pom
new file mode 100644
index 000000000..5d87546da
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/artifactid-numeric123/1.0/artifactid-numeric123-1.0.pom
@@ -0,0 +1,17 @@
+<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/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com</groupId>
+ <artifactId>artifactid-numeric123</artifactId>
+ <packaging>jar</packaging>
+ <version>1.0</version>
+ <name>ArtifactID numeric</name>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/classname-search/1.0/classname-search-1.0.jar b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/classname-search/1.0/classname-search-1.0.jar
new file mode 100644
index 000000000..0572c67fe
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/classname-search/1.0/classname-search-1.0.jar
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/classname-search/1.0/classname-search-1.0.pom b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/classname-search/1.0/classname-search-1.0.pom
new file mode 100644
index 000000000..80b54abc2
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/com/classname-search/1.0/classname-search-1.0.pom
@@ -0,0 +1,18 @@
+<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/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com</groupId>
+ <artifactId>classname-search</artifactId>
+ <packaging>jar</packaging>
+ <version>1.0</version>
+ <name>classname-search</name>
+ <url>http://maven.apache.org</url>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-search/1.0/archiva-search-1.0-patch1.jar b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-search/1.0/archiva-search-1.0-patch1.jar
new file mode 100644
index 000000000..9c16c3ced
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-search/1.0/archiva-search-1.0-patch1.jar
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-search/1.0/archiva-search-1.0-sources.jar b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-search/1.0/archiva-search-1.0-sources.jar
new file mode 100644
index 000000000..9c16c3ced
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-search/1.0/archiva-search-1.0-sources.jar
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.jar b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.jar
new file mode 100644
index 000000000..9c16c3ced
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.jar
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.pom b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.pom
new file mode 100644
index 000000000..b0d983fde
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-search/1.0/archiva-search-1.0.pom
@@ -0,0 +1,28 @@
+<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/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-search</artifactId>
+ <packaging>jar</packaging>
+ <version>1.0</version>
+ <name>Archiva Search</name>
+ <url>http://archiva.apache.org</url>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ <version>2.3</version>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.8</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-test/1.0/archiva-test-1.0.jar b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-test/1.0/archiva-test-1.0.jar
new file mode 100644
index 000000000..843ee45bc
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-test/1.0/archiva-test-1.0.jar
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-test/1.0/archiva-test-1.0.pom b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-test/1.0/archiva-test-1.0.pom
new file mode 100644
index 000000000..0b037a3a5
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-test/1.0/archiva-test-1.0.pom
@@ -0,0 +1,23 @@
+<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/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-test</artifactId>
+ <packaging>jar</packaging>
+ <version>1.0</version>
+ <name>archiva-test</name>
+ <url>http://maven.apache.org</url>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ <version>2.3</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-test/2.0/archiva-test-2.0.jar b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-test/2.0/archiva-test-2.0.jar
new file mode 100644
index 000000000..e904a17c4
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-test/2.0/archiva-test-2.0.jar
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-test/2.0/archiva-test-2.0.pom b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-test/2.0/archiva-test-2.0.pom
new file mode 100644
index 000000000..04fdb4859
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-test/2.0/archiva-test-2.0.pom
@@ -0,0 +1,28 @@
+<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/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-test</artifactId>
+ <packaging>jar</packaging>
+ <version>2.0</version>
+ <name>Archiva Test</name>
+ <url>http://archiva.apache.org</url>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ <version>2.3</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>1.4</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-webapp/1.0/archiva-webapp-1.0.pom b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-webapp/1.0/archiva-webapp-1.0.pom
new file mode 100644
index 000000000..ca28dc132
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-webapp/1.0/archiva-webapp-1.0.pom
@@ -0,0 +1,23 @@
+<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/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>archiva-webapp</artifactId>
+ <packaging>war</packaging>
+ <version>1.0</version>
+ <name>Archiva Webapp</name>
+ <url>http://archiva.apache.org</url>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>3.8.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.8</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-webapp/1.0/archiva-webapp-1.0.war b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-webapp/1.0/archiva-webapp-1.0.war
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/archiva-webapp/1.0/archiva-webapp-1.0.war
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/test-webapp/1.0-SNAPSHOT/test-webapp-1.0-SNAPSHOT.war b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/test-webapp/1.0-SNAPSHOT/test-webapp-1.0-SNAPSHOT.war
new file mode 100644
index 000000000..5559103fd
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/archiva/test-webapp/1.0-SNAPSHOT/test-webapp-1.0-SNAPSHOT.war
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/felix/org.apache.felix.bundlerepository/1.6.6/org.apache.felix.bundlerepository-1.6.6.jar b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/felix/org.apache.felix.bundlerepository/1.6.6/org.apache.felix.bundlerepository-1.6.6.jar
new file mode 100644
index 000000000..218330e3f
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/felix/org.apache.felix.bundlerepository/1.6.6/org.apache.felix.bundlerepository-1.6.6.jar
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/felix/org.apache.felix.bundlerepository/1.6.6/org.apache.felix.bundlerepository-1.6.6.pom b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/felix/org.apache.felix.bundlerepository/1.6.6/org.apache.felix.bundlerepository-1.6.6.pom
new file mode 100644
index 000000000..19831eefa
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/maven-search-test-repo/org/apache/felix/org.apache.felix.bundlerepository/1.6.6/org.apache.felix.bundlerepository-1.6.6.pom
@@ -0,0 +1,138 @@
+<!--
+ 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/maven-v4_0_0.xsd">
+ <parent>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>felix-parent</artifactId>
+ <version>2.1</version>
+ <relativePath>../pom/pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>bundle</packaging>
+ <name>Apache Felix Bundle Repository</name>
+ <description>Bundle repository service.</description>
+ <artifactId>org.apache.felix.bundlerepository</artifactId>
+ <version>1.6.6</version>
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/felix/releases/org.apache.felix.bundlerepository-1.6.6</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/felix/releases/org.apache.felix.bundlerepository-1.6.6</developerConnection>
+ <url>http://svn.apache.org/repos/asf/felix/releases/org.apache.felix.bundlerepository-1.6.6</url>
+ </scm>
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>org.apache.felix.utils</artifactId>
+ <version>1.1.0</version>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>org.osgi.service.obr</artifactId>
+ <version>1.0.2</version>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>org.apache.felix.shell</artifactId>
+ <version>1.4.1</version>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.kxml</groupId>
+ <artifactId>kxml2</artifactId>
+ <version>2.3.0</version>
+ <optional>true</optional>
+ <exclusions>
+ <exclusion>
+ <groupId>xmlpull</groupId>
+ <artifactId>xmlpull</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>4.0.0</version>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>4.1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.woodstox</groupId>
+ <artifactId>woodstox-core-asl</artifactId>
+ <version>4.0.7</version>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.easymock</groupId>
+ <artifactId>easymock</artifactId>
+ <version>2.4</version>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>2.3.4</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>org.apache.felix.bundlerepository;version="2.0"</Export-Package>
+ <Private-Package>
+ org.kxml2.io,
+ org.xmlpull.v1,
+ org.apache.felix.bundlerepository.impl.*,
+ org.apache.felix.utils.*
+ </Private-Package>
+ <Import-Package>!javax.xml.parsers,!org.xml.sax,org.osgi.service.log;resolution:=optional,org.osgi.service.obr;resolution:=optional,javax.xml.stream;resolution:=optional,*</Import-Package>
+ <DynamicImport-Package>org.apache.felix.shell</DynamicImport-Package>
+ <Bundle-Activator>${project.artifactId}.impl.Activator</Bundle-Activator>
+ <Bundle-DocURL>http://felix.apache.org/site/apache-felix-osgi-bundle-repository.html</Bundle-DocURL>
+ <Bundle-Url>http://felix.apache.org/site/downloads.cgi</Bundle-Url>
+ <Bundle-Source>http://felix.apache.org/site/downloads.cgi</Bundle-Source>
+ <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+ <Bundle-Vendor>The Apache Software Foundation</Bundle-Vendor>
+ <Export-Service>org.apache.felix.bundlerepository.RepositoryAdmin,org.osgi.service.obr.RepositoryAdmin</Export-Service>
+ <_versionpolicy>[$(version;==;$(@)),$(version;+;$(@)))</_versionpolicy>
+ <Include-Resource>META-INF/LICENSE=LICENSE,META-INF/LICENSE.kxml2=LICENSE.kxml2,META-INF/NOTICE=NOTICE,META-INF/DEPENDENCIES=DEPENDENCIES</Include-Resource>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.rat</groupId>
+ <artifactId>apache-rat-plugin</artifactId>
+ <configuration>
+ <excludeSubProjects>false</excludeSubProjects>
+ <useEclipseDefaultExcludes>true</useEclipseDefaultExcludes>
+ <useMavenDefaultExcludes>true</useMavenDefaultExcludes>
+ <excludes>
+ <param>doc/*</param>
+ <param>maven-eclipse.xml</param>
+ <param>.checkstyle</param>
+ <param>.externalToolBuilders/*</param>
+ </excludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.fdt b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.fdt
new file mode 100644
index 000000000..9ab6d4d90
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.fdt
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.fdx b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.fdx
new file mode 100644
index 000000000..a26fbfa6b
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.fdx
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.fnm b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.fnm
new file mode 100644
index 000000000..b9f92430c
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.fnm
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.nvd b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.nvd
new file mode 100644
index 000000000..d832c2f6f
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.nvd
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.nvm b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.nvm
new file mode 100644
index 000000000..a27cff81a
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.nvm
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.si b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.si
new file mode 100644
index 000000000..2539c768d
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8.si
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8_Lucene41_0.doc b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8_Lucene41_0.doc
new file mode 100644
index 000000000..0a2394bd2
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8_Lucene41_0.doc
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8_Lucene41_0.pos b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8_Lucene41_0.pos
new file mode 100644
index 000000000..4d9bbaad5
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8_Lucene41_0.pos
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8_Lucene41_0.tim b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8_Lucene41_0.tim
new file mode 100644
index 000000000..ac8475263
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8_Lucene41_0.tim
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8_Lucene41_0.tip b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8_Lucene41_0.tip
new file mode 100644
index 000000000..d94d9c92b
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/_8_Lucene41_0.tip
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/segments.gen b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/segments.gen
new file mode 100644
index 000000000..077a451a0
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/segments.gen
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/segments_9 b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/segments_9
new file mode 100644
index 000000000..a01b84fd7
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/segments_9
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/timestamp b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/timestamp
new file mode 100644
index 000000000..902816464
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/timestamp
Binary files differ
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/write.lock b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/write.lock
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/repo-release/.index/write.lock
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/resources/log4j2-test.xml b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/resources/log4j2-test.xml
new file mode 100644
index 000000000..db1c64d28
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/resources/log4j2-test.xml
@@ -0,0 +1,45 @@
+<?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 status="debug">
+ <appenders>
+ <Console name="console" target="SYSTEM_OUT">
+ <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
+ </Console>
+ </appenders>
+ <loggers>
+
+ <logger name="org.apache.archiva.repository" level="info"/>
+ <logger name="org.apache.archiva.indexer" level="info" />
+
+ <logger name="JPOX" level="error"/>
+
+
+ <logger name="org.springframework" level="error"/>
+
+
+ <root level="info">
+ <appender-ref ref="console"/>
+ </root>
+ </loggers>
+</configuration>
+
+
diff --git a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/resources/spring-context.xml b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/resources/spring-context.xml
new file mode 100644
index 000000000..83a361f17
--- /dev/null
+++ b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/resources/spring-context.xml
@@ -0,0 +1,81 @@
+<?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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
+ 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 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"
+ default-lazy-init="false">
+
+ <context:annotation-config/>
+ <context:component-scan base-package="org.apache.archiva.indexer.maven,org.apache.archiva.repository,org.apache.archiva.repository.content.maven2" />
+
+
+ <bean name="wagon#file" scope="prototype" class="org.apache.maven.wagon.providers.file.FileWagon"/>
+
+ <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>
+ <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