]> source.dussan.org Git - archiva.git/commitdiff
MRM-1037 - Search Usability
authorJames William Dumay <jdumay@apache.org>
Tue, 16 Dec 2008 02:13:25 +0000 (02:13 +0000)
committerJames William Dumay <jdumay@apache.org>
Tue, 16 Dec 2008 02:13:25 +0000 (02:13 +0000)
* timestamp versions are merged to -SNAPSHOT versions
* duplicate artifacts are now merge by use of boolean filters
* we now search the correct fields
* content search has been removed (more accurate results)
* added more tokenizers for groupId, artifactId, version, etc
* Artifact Id's are weighted to improve quicksearch results

git-svn-id: https://svn.apache.org/repos/asf/archiva/trunk@726928 13f79535-47bb-0310-9956-ffa450edef68

25 files changed:
archiva-modules/archiva-base/archiva-consumers/archiva-lucene-consumers/src/main/java/org/apache/maven/archiva/consumers/lucene/IndexContentConsumer.java
archiva-modules/archiva-base/archiva-indexer/pom.xml
archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/maven/archiva/indexer/filecontent/FileContentAnalyzer.java
archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/maven/archiva/indexer/filecontent/FileContentConverter.java
archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/maven/archiva/indexer/filecontent/FileContentHandlers.java
archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/maven/archiva/indexer/filecontent/FileContentKeys.java
archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/maven/archiva/indexer/filecontent/FileContentRecord.java
archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/maven/archiva/indexer/lucene/LuceneDocumentMaker.java
archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/maven/archiva/indexer/lucene/analyzers/ArtifactIdTokenizer.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/maven/archiva/indexer/search/DefaultCrossRepositorySearch.java
archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/maven/archiva/indexer/search/SearchResultHit.java
archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/maven/archiva/indexer/search/SearchResults.java
archiva-modules/archiva-base/archiva-indexer/src/test/java/org/apache/maven/archiva/indexer/search/DefaultCrossRepositorySearchTest.java
archiva-modules/archiva-base/archiva-indexer/src/test/java/org/apache/maven/archiva/indexer/search/FileContentIndexPopulator.java
archiva-modules/archiva-base/archiva-indexer/src/test/managed-repository/ant/ant-junit/1.6.5/ant-junit-1.6.5.pom [new file with mode: 0644]
archiva-modules/archiva-base/archiva-indexer/src/test/managed-repository/ant/ant-optional/1.5.1/ant-optional-1.5.1.pom [new file with mode: 0644]
archiva-modules/archiva-base/archiva-indexer/src/test/managed-repository/ant/ant/1.5.1/ant-1.5.1.pom [new file with mode: 0644]
archiva-modules/archiva-base/archiva-indexer/src/test/managed-repository/ant/ant/1.5/ant-1.5.pom [new file with mode: 0644]
archiva-modules/archiva-base/archiva-model/src/main/java/org/apache/maven/archiva/model/ArchivaArtifact.java
archiva-modules/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/action/SearchAction.java
archiva-modules/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/action/ShowArtifactAction.java
archiva-modules/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/jsp/results.jsp
archiva-modules/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/tags/showArtifactLink.tag
archiva-modules/archiva-web/archiva-xmlrpc/archiva-xmlrpc-services/src/test/java/org/apache/archiva/web/xmlrpc/services/SearchServiceImplTest.java
pom.xml

index ea3908772ff23a4eade6b961878612048ae22ae2..2c805f64cbc2da79a06bdfbe06790947bcc0c9b0 100644 (file)
@@ -159,10 +159,8 @@ public class IndexContentConsumer
         FileContentRecord record = new FileContentRecord();
         try
         {
-            File file = new File( repositoryDir, path );
             record.setRepositoryId( this.repository.getId() );
             record.setFilename( path );
-            record.setContents( FileUtils.readFileToString( file, null ) );
 
             // Test for possible artifact reference syntax.
             try
@@ -179,10 +177,6 @@ public class IndexContentConsumer
 
             index.modifyRecord( record );
         }
-        catch ( IOException e )
-        {
-            triggerConsumerError( READ_CONTENT, "Unable to read file contents: " + e.getMessage() );
-        }
         catch ( RepositoryIndexException e )
         {
             triggerConsumerError( INDEX_ERROR, "Unable to index file contents: " + e.getMessage() );
index 31f88e6794c656c2febf7585531ac4d4d9deae87..13b1b2b923a844cbe4c4529d1c7db6bed1383384 100644 (file)
       <groupId>org.apache.lucene</groupId>
       <artifactId>lucene-core</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.apache.lucene</groupId>
+      <artifactId>lucene-queries</artifactId>
+    </dependency>
     <dependency>
       <groupId>org.codehaus.plexus</groupId>
       <artifactId>plexus-spring</artifactId>
index 855d2259132f6ceefab94f0fd98c4b7374e3e0d1..7921022cea051fd08ea445bacd9eb5910eaf59e8 100644 (file)
@@ -23,8 +23,11 @@ import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.TokenStream;
 import org.apache.lucene.analysis.standard.StandardAnalyzer;
 import org.apache.maven.archiva.indexer.lucene.analyzers.FilenamesTokenizer;
+import org.apache.maven.archiva.indexer.lucene.analyzers.ArtifactIdTokenizer;
+import org.apache.maven.archiva.indexer.lucene.analyzers.GroupIdTokenizer;
 
 import java.io.Reader;
+import org.apache.maven.archiva.indexer.lucene.analyzers.VersionTokenizer;
 
 /**
  * FileContentAnalyzer 
@@ -42,6 +45,21 @@ public class FileContentAnalyzer extends Analyzer
             return new FilenamesTokenizer( reader );
         }
 
+        if ( FileContentKeys.ARTIFACTID.equals( field ))
+        {
+            return new ArtifactIdTokenizer(reader);
+        }
+
+        if ( FileContentKeys.GROUPID.equals( field ) )
+        {
+            return new GroupIdTokenizer(reader);
+        }
+
+        if ( FileContentKeys.VERSION.equals( field ))
+        {
+            return new VersionTokenizer(reader);
+        }
+
         return STANDARD.tokenStream( field, reader );
     }
 }
index ad191f673ad18f0a5a7dd5d07512495e611d777e..cefc5e9ad0bae3d0ba65e66ef005df0c6a93166b 100644 (file)
@@ -37,7 +37,6 @@ import java.text.ParseException;
 public class FileContentConverter
     implements LuceneEntryConverter
 {
-
     public Document convert( LuceneRepositoryContentRecord record )
     {
         if ( !( record instanceof FileContentRecord ) )
@@ -55,16 +54,15 @@ public class FileContentConverter
             // Artifact Reference
             doc.addFieldTokenized( ArtifactKeys.GROUPID, filecontent.getArtifact().getGroupId() );
             doc.addFieldExact( ArtifactKeys.GROUPID_EXACT, filecontent.getArtifact().getGroupId() );
-            doc.addFieldTokenized( ArtifactKeys.ARTIFACTID, filecontent.getArtifact().getArtifactId() );
-            doc.addFieldExact( ArtifactKeys.ARTIFACTID_EXACT, filecontent.getArtifact().getArtifactId() );
+            doc.addFieldTokenized( ArtifactKeys.ARTIFACTID, filecontent.getArtifact().getArtifactId()); //, 2.0f);
+            doc.addFieldExact( ArtifactKeys.ARTIFACTID_EXACT, filecontent.getArtifact().getArtifactId(), 2.0f);
             doc.addFieldTokenized( ArtifactKeys.VERSION, filecontent.getArtifact().getVersion() );
             doc.addFieldExact( ArtifactKeys.VERSION_EXACT, filecontent.getArtifact().getVersion() );
             doc.addFieldTokenized( ArtifactKeys.TYPE, filecontent.getArtifact().getType() );
             doc.addFieldUntokenized( ArtifactKeys.CLASSIFIER, filecontent.getArtifact().getClassifier() );
         }
-        
+
         doc.addFieldTokenized( FileContentKeys.FILENAME, filecontent.getFilename() );
-        doc.addFieldTokenized( FileContentKeys.CONTENT, filecontent.getContents() );
 
         return doc.getDocument();
     }
@@ -91,7 +89,6 @@ public class FileContentConverter
 
         // Filecontent Specifics
         record.setFilename( document.get( FileContentKeys.FILENAME ) );
-        record.setContents( document.get( FileContentKeys.CONTENT ) );
 
         return record;
     }
index 70a95c9f469ef6099485b88806829a8f47a86b00..f3058dda09ba6d2f67cccf3a5d11a0f3b45f4a81 100644 (file)
@@ -43,8 +43,17 @@ public class FileContentHandlers
     {
         analyzer = new FileContentAnalyzer();
         converter = new FileContentConverter();
-        queryParser = new MultiFieldQueryParser( new String[] { FileContentKeys.FILENAME, FileContentKeys.CONTENT },
-                                                 analyzer );
+        queryParser = new MultiFieldQueryParser( new String[] {
+                                                FileContentKeys.FILENAME,
+                                                FileContentKeys.ARTIFACTID,
+                                                FileContentKeys.GROUPID,
+                                                FileContentKeys.ARTIFACTID_EXACT,
+                                                FileContentKeys.GROUPID_EXACT,
+                                                FileContentKeys.VERSION,
+                                                FileContentKeys.VERSION_EXACT},
+                                                analyzer );
+        //We prefer the narrowing approach to search results.
+        queryParser.setDefaultOperator(MultiFieldQueryParser.Operator.AND);
     }
 
     public String getId()
index 1b9e6260c015dd19efcca96250235411d929ac4d..343f359a3f0bc1db6369c0cb01129ec28da21659 100644 (file)
@@ -32,6 +32,4 @@ public class FileContentKeys
     public static final String ID = "filecontent";
 
     public static final String FILENAME = "filename";
-
-    public static final String CONTENT = "content";
 }
index 991f7b0a21be385e6c5355a43c63b7bc1eaea71d..0a1221e1e702c1a76c770ed7118f99f5ed5d3138 100644 (file)
@@ -39,8 +39,6 @@ public class FileContentRecord
      */
     private ArchivaArtifact artifact;
 
-    private String contents;
-
     public String getRepositoryId()
     {
         return repositoryId;
@@ -51,16 +49,6 @@ public class FileContentRecord
         this.repositoryId = repositoryId;
     }
 
-    public String getContents()
-    {
-        return contents;
-    }
-
-    public void setContents( String contents )
-    {
-        this.contents = contents;
-    }
-
     public String getPrimaryKey()
     {
         return repositoryId + ":" + filename;
index 169058be1146d6997f4af81390a464936769e334..347e0e91a2d4879c2509a99a570e867700471802 100644 (file)
@@ -81,6 +81,18 @@ public class LuceneDocumentMaker
         return this;
     }
 
+    public LuceneDocumentMaker addFieldTokenized( String key, String value, float boost )
+    {
+        if ( value != null )
+        {
+            Field field = new Field( key, value, Field.Store.YES, Field.Index.TOKENIZED );
+            field.setBoost(boost);
+            document.add( field );
+        }
+
+        return this;
+    }
+
     public LuceneDocumentMaker addFieldTokenized( String key, List list )
     {
         if ( ( list != null ) && ( !list.isEmpty() ) )
@@ -101,6 +113,18 @@ public class LuceneDocumentMaker
         return this;
     }
 
+    public LuceneDocumentMaker addFieldUntokenized( String name, String value, float boost )
+    {
+        if ( value != null )
+        {
+            Field field = new Field( name, value, Field.Store.YES, Field.Index.UN_TOKENIZED );
+            field.setBoost(boost);
+            document.add( field );
+        }
+
+        return this;
+    }
+
     public LuceneDocumentMaker addFieldExact( String name, String value )
     {
         if ( value != null )
@@ -111,6 +135,18 @@ public class LuceneDocumentMaker
         return this;
     }
 
+    public LuceneDocumentMaker addFieldExact( String name, String value, float boost )
+    {
+        if ( value != null )
+        {
+            Field field = new Field( name, value, Field.Store.NO, Field.Index.UN_TOKENIZED );
+            field.setBoost(boost);
+            document.add( field );
+        }
+
+        return this;
+    }
+
     public Document getDocument()
     {
         return this.document;
diff --git a/archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/maven/archiva/indexer/lucene/analyzers/ArtifactIdTokenizer.java b/archiva-modules/archiva-base/archiva-indexer/src/main/java/org/apache/maven/archiva/indexer/lucene/analyzers/ArtifactIdTokenizer.java
new file mode 100644 (file)
index 0000000..2e99c26
--- /dev/null
@@ -0,0 +1,45 @@
+package org.apache.maven.archiva.indexer.lucene.analyzers;
+
+/*
+ * 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.Reader;
+import org.apache.lucene.analysis.CharTokenizer;
+
+/**
+ * Lucene Tokenizer for {@link ArtifactKeys#ARTIFACTID} fields.
+ */
+public class ArtifactIdTokenizer extends CharTokenizer
+{
+    public ArtifactIdTokenizer( Reader reader )
+    {
+        super( reader );
+    }
+
+    /**
+     * Break on "-" for "atlassian-plugins-core"
+     * @param c
+     * @return
+     */
+    @Override
+    protected boolean isTokenChar(char c)
+    {
+        return (c != '-');
+    }
+}
index 624e4bc501499343c2b0517ed80ac27aa0a8cbb0..21deef954421b543108a2ff7a55a08b225d115ce 100644 (file)
@@ -28,8 +28,11 @@ import org.apache.lucene.queryParser.MultiFieldQueryParser;
 import org.apache.lucene.queryParser.ParseException;
 import org.apache.lucene.queryParser.QueryParser;
 import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanFilter;
 import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.DuplicateFilter;
 import org.apache.lucene.search.Filter;
+import org.apache.lucene.search.FilterClause;
 import org.apache.lucene.search.Hits;
 import org.apache.lucene.search.MultiSearcher;
 import org.apache.lucene.search.Query;
@@ -46,6 +49,7 @@ import org.apache.maven.archiva.indexer.RepositoryIndexSearchException;
 import org.apache.maven.archiva.indexer.bytecode.BytecodeHandlers;
 import org.apache.maven.archiva.indexer.bytecode.BytecodeKeys;
 import org.apache.maven.archiva.indexer.filecontent.FileContentHandlers;
+import org.apache.maven.archiva.indexer.filecontent.FileContentKeys;
 import org.apache.maven.archiva.indexer.hashcodes.HashcodesHandlers;
 import org.apache.maven.archiva.indexer.hashcodes.HashcodesKeys;
 import org.apache.maven.archiva.indexer.lucene.LuceneEntryConverter;
@@ -208,10 +212,17 @@ public class DefaultCrossRepositorySearch
             QueryParser parser = new FileContentHandlers().getQueryParser();
             LuceneQuery query = null;
             SearchResults results = null;
+
+            BooleanFilter duplicateFilter = new BooleanFilter();
+            DuplicateFilter artifactIdDuplicateFilter = new DuplicateFilter(FileContentKeys.ARTIFACTID_EXACT);
+            duplicateFilter.add(new FilterClause(artifactIdDuplicateFilter, BooleanClause.Occur.SHOULD));
+            DuplicateFilter groupIdDuplicateFilter = new DuplicateFilter(FileContentKeys.GROUPID_EXACT);
+            duplicateFilter.add(new FilterClause(groupIdDuplicateFilter, BooleanClause.Occur.SHOULD));
+
             if ( previousSearchTerms == null || previousSearchTerms.isEmpty() )
             {
                 query = new LuceneQuery( parser.parse( term ) );
-                results = searchAll( query, limits, indexes, null );
+                results = searchAll( query, limits, indexes, duplicateFilter );
             }
             else
             {
@@ -224,7 +235,8 @@ public class DefaultCrossRepositorySearch
 
                 query = new LuceneQuery( booleanQuery );
                 Filter filter = new QueryWrapperFilter( parser.parse( term ) );
-                results = searchAll( query, limits, indexes, filter );
+                duplicateFilter.add(new FilterClause(filter, BooleanClause.Occur.SHOULD));
+                results = searchAll( query, limits, indexes, duplicateFilter );
             }
             results.getRepositories().addAll( this.localIndexedRepositories );
 
@@ -268,7 +280,7 @@ public class DefaultCrossRepositorySearch
         {
             // Create a multi-searcher for looking up the information.
             searcher = new MultiSearcher( searchables );
-
+            
             // Perform the search.
             Hits hits = null;
             if ( filter != null )
index 05c3a1aef85076b06090974151fdb1f3e74091fc..ba3bca500c7d4e0be8a5e8fa161504e27316acad 100644 (file)
@@ -48,10 +48,9 @@ public class SearchResultHit
     
     private String repositoryId = "";
 
-    // Advanced hit, if artifact, all versions of artifact
-    private List artifacts = new ArrayList();
+    private List<String> versions = new ArrayList();
 
-    private List versions = new ArrayList();
+    private ArchivaArtifact artifact;
 
     public String getContext()
     {
@@ -88,11 +87,10 @@ public class SearchResultHit
         this.artifactId = artifactId;
     }
 
-    public void addArtifact( ArchivaArtifact artifact )
+    public void setArtifact( ArchivaArtifact artifact )
     {
-        this.artifacts.add( artifact );
-                
-        String ver = artifact.getVersion();        
+        this.artifact = artifact;
+        final String ver = artifact.getVersion();
 
         if ( !this.versions.contains( ver ) )
         {
@@ -115,9 +113,9 @@ public class SearchResultHit
         }
     }
 
-    public List getArtifacts()
+    public ArchivaArtifact getArtifact()
     {
-        return artifacts;
+        return artifact;
     }
 
     public String getGroupId()
@@ -135,11 +133,21 @@ public class SearchResultHit
         return version;
     }
 
-    public List getVersions()
+    public void setVersion(String version)
+    {
+        this.version = version;
+    }
+
+    public List<String> getVersions()
     {
         return versions;
     }
 
+    public void setVersions(List<String> versions)
+    {
+        this.versions = versions;
+    }
+
     public String getRepositoryId()
     {
         return repositoryId;
index 50ed79b28181d7ca06af45118e59fbedfcaf0555..ecb01fbcf0795dfdb312b2b37a656bf4c4895fe2 100644 (file)
@@ -40,7 +40,7 @@ public class SearchResults
 {
     private List repositories = new ArrayList();
 
-    private Map hits = new HashMap();
+    private Map<String, SearchResultHit> hits = new HashMap();
 
     private int totalHits;
 
@@ -82,7 +82,7 @@ public class SearchResults
         }
         
         hit.setRepositoryId( bytecode.getRepositoryId() );
-        hit.addArtifact( bytecode.getArtifact() );
+        hit.setArtifact( bytecode.getArtifact() );
         hit.setContext( null ); // TODO: provide context on why this is a valuable hit.
 
         this.hits.put( key, hit );
@@ -111,18 +111,16 @@ public class SearchResults
             hit = new SearchResultHit();
         }
 
-        hit.addArtifact( hashcodes.getArtifact() );
+        hit.setArtifact( hashcodes.getArtifact() );
         hit.setContext( null ); // TODO: provide context on why this is a valuable hit.
 
-        this.hits.put( key, hit );
+        hits.put( key, hit );
     }
 
     public void addFileContentHit( FileContentRecord filecontent )
     {
-        String key = filecontent.getPrimaryKey();
-
-        SearchResultHit hit = (SearchResultHit) this.hits.get( key );
-
+        final String key = filecontent.getPrimaryKey();
+        SearchResultHit hit = hits.get( key );
         if ( hit == null )
         {
             // Only need to worry about this hit if it is truely new.
@@ -135,7 +133,7 @@ public class SearchResults
             // Test for possible artifact reference ...
             if( filecontent.getArtifact() != null )
             {
-                hit.addArtifact( filecontent.getArtifact() );
+                hit.setArtifact( filecontent.getArtifact() );
             }
 
             this.hits.put( key, hit );
@@ -147,7 +145,7 @@ public class SearchResults
      * 
      * @return the list of {@link SearchResultHit} objects.
      */
-    public List getHits()
+    public List<SearchResultHit> getHits()
     {
         return new ArrayList( hits.values() );
     }
index bfbe2b9af7f459b62b6357656b477ec5da9dfc4e..6877129462eb64e6a77f43d8a04c6a92103c9ed8 100644 (file)
@@ -126,119 +126,77 @@ public class DefaultCrossRepositorySearchTest
         return search;
     }
 
-    public void testSearchTerm_Org()
-        throws Exception
-    {        
+    public void testSearchArtifactIdHasMoreWieghtThanGroupId() throws Exception
+    {
         CrossRepositorySearch search = lookupCrossRepositorySearch();
 
         String expectedRepos[] = new String[] {
             TEST_DEFAULT_REPO_ID
         };
-        
-        String expectedResults[] = new String[] { 
-            "org","org2","org3","org4","org5","org6","org7"
-        };
-        
-        assertSearchResults( expectedRepos, expectedResults, search, "org", null, false );
-    }
 
-    public void testSearchTerm_Junit()
-        throws Exception
-    {        
-        CrossRepositorySearch search = lookupCrossRepositorySearch();
-        
-        String expectedRepos[] = new String[] {
-            TEST_DEFAULT_REPO_ID
-        };
-        
-        String expectedResults[] = new String[] { 
-            "junit","junit2","junit3"
-        };
-        
-        assertSearchResults( expectedRepos, expectedResults, search, "junit", null, false );
+        List<SearchResultHit> expectedHits = new ArrayList<SearchResultHit>();
+        SearchResultHit hit = new SearchResultHit();
+        hit.setGroupId("ant");
+        hit.setArtifactId("ant");
+        hit.setVersion("1.5");
+        expectedHits.add(hit);
+
+        hit = new SearchResultHit();
+        hit.setGroupId("ant");
+        hit.setArtifactId("ant-optional");
+        hit.setVersion("1.5.1");
+        expectedHits.add(hit);
+
+        hit = new SearchResultHit();
+        hit.setGroupId("ant");
+        hit.setArtifactId("ant-junit");
+        hit.setVersion("1.6.5");
+        expectedHits.add(hit);
+
+        assertSearchResults( expectedRepos, expectedHits, search, "ant", null, false );
     }
 
     public void testSearchInvalidTerm()
         throws Exception
-    {        
+    {
         CrossRepositorySearch search = lookupCrossRepositorySearch();
 
         String expectedRepos[] = new String[] {
             TEST_DEFAULT_REPO_ID
         };
-        
-        String expectedResults[] = new String[] { 
-            // Nothing.
-        };
-        
-        assertSearchResults( expectedRepos, expectedResults, search, "monosodium", null, false );
-    }
-    
-    public void testSearchWithinSearchResults()
-        throws Exception
-    {        
-        CrossRepositorySearch search = lookupCrossRepositorySearch();
 
-        String expectedRepos[] = new String[] {
-            TEST_DEFAULT_REPO_ID
-        };
-        
-        String expectedResults[] = new String[] { 
-            "org","org2","org3","org4","org5","org6","org7"
-        };
-        
-        // first search
-        assertSearchResults( expectedRepos, expectedResults, search, "org", null, false );
-        
-        List<String> previousSearchTerms = new ArrayList<String>();
-        previousSearchTerms.add( "org" );        
-        String secondSearchExpectedResults[] = new String[] { 
-            "org.apache.maven.archiva.record", "org.apache.maven.archiva.record2",
-                "org.apache.maven.archiva.record3", "org.apache.maven.archiva.record4",
-                "org.apache.maven.archiva.record5", "org.apache.maven.archiva.record6",
-                "org.apache.maven.archiva.record7" 
-        };
-        
-        //second search
-        assertSearchResults( expectedRepos, secondSearchExpectedResults, search, "org.apache.maven.archiva.record",
-                             previousSearchTerms, false );
-        
-        previousSearchTerms.add( "org.apache.maven.archiva.record" );
-        String thirdSearchExpectedResults[] = new String[] { 
-            "junit", "junit2", "junit3"
-        };
-        
-        //third search
-        assertSearchResults( expectedRepos, thirdSearchExpectedResults, search, "junit", previousSearchTerms, false );        
+        assertSearchResults( expectedRepos, new ArrayList<SearchResultHit>(), search, "monosodium", null, false );
     }
-    
+
     public void testSearchForClassesAndPackages()
         throws Exception
-    {                
+    {
         CrossRepositorySearch search = lookupCrossRepositorySearch();
 
         String expectedRepos[] = new String[] {
             TEST_DEFAULT_REPO_ID
         };
-                
-        String expectedResults[] = new String[] { 
-            "archiva-common-1.0.jar"
-        };
-        
+
+        SearchResultHit archivaCommon = new SearchResultHit();
+        archivaCommon.setArtifactId("archiva-common");
+        archivaCommon.setGroupId("org.apache.maven.archiva");
+        archivaCommon.setVersion("1.0");
+
         // class with packagename search
-        assertSearchResults( expectedRepos, expectedResults, search, 
+        assertSearchResults( expectedRepos, Arrays.asList(archivaCommon), search,
                              "org.apache.maven.archiva.common.utils.BaseFile", null, true );
         // class name search
-        assertSearchResults( expectedRepos, expectedResults, search, 
+        assertSearchResults( expectedRepos, Arrays.asList(archivaCommon), search,
                              "BaseFile", null, true );
-                
-        String expectedMethodSearchResults[] = new String[] { 
-            "continuum-webapp-1.0.3-SNAPSHOT.war"
-        };
-        
+
+        SearchResultHit hit = new SearchResultHit();
+        hit.setGroupId("org.apache.maven.continuum");
+        hit.setArtifactId("continuum-webapp");
+        hit.setVersion("1.0.3-SNAPSHOT");
+
         // method search
-        assertSearchResults( expectedRepos, expectedMethodSearchResults, search,
-                             "org.apache.maven.continuum.web.action.BuildDefinitionAction.isBuildFresh", null, true );        
+        assertSearchResults( expectedRepos, Arrays.asList(hit), search,
+                             "org.apache.maven.continuum.web.action.BuildDefinitionAction.isBuildFresh", null, true );
     }
     
     public void testExecuteFilteredSearch()
@@ -312,7 +270,7 @@ public class DefaultCrossRepositorySearchTest
         assertEquals( "Search Result Hits", expectedResults.length, results.getHits().size() );
     }
     
-    private void assertSearchResults( String expectedRepos[], String expectedResults[], CrossRepositorySearch search,
+    private void assertSearchResults( String expectedRepos[], List<SearchResultHit> expectedResults, CrossRepositorySearch search,
                                       String term, List<String> previousSearchTerms, boolean bytecode )
         throws Exception
     {
@@ -325,8 +283,8 @@ public class DefaultCrossRepositorySearchTest
         SearchResults results = null;
 
         if( previousSearchTerms == null )
-            {
-                if( bytecode )
+        {
+            if( bytecode )
             {
                 results = search.searchForBytecode( "guest", selectedRepos, term, limits );
             }
@@ -346,9 +304,16 @@ public class DefaultCrossRepositorySearchTest
         
         // TODO: test the repository ids returned.
 
-        assertEquals( "Search Result Hits", expectedResults.length, results.getHits().size() );
-        // TODO: test the order of hits.
-        // TODO: test the value of the hits.
+        assertEquals( "Search Result Hits", expectedResults.size(), results.getHits().size() );
+
+        for (int i = 0; i < expectedResults.size(); i++)
+        {
+            final SearchResultHit expectedResult = expectedResults.get(i);
+            final SearchResultHit hit = results.getHits().get(i);
+            assertEquals("artifactid", expectedResult.getArtifactId(), hit.getArtifactId());
+            assertEquals("groupid", expectedResult.getGroupId(), hit.getGroupId());
+            assertEquals("version", expectedResult.getVersion(), hit.getVersion());
+        }
     }
     
     protected ManagedRepositoryConfiguration createRepository( String id, String name, File location )
index c0dc214e88f888f7337b7f08bcbdef519a4a8ecb..0bc1b3d6497ab0f3e541957fdb63cb3d09a61d6f 100644 (file)
@@ -19,16 +19,18 @@ package org.apache.maven.archiva.indexer.search;
  * under the License.
  */
 
-import org.apache.commons.io.FileUtils;
 import org.apache.maven.archiva.indexer.filecontent.FileContentRecord;
 import org.apache.maven.archiva.model.ArchivaArtifact;
 
 import java.io.File;
-import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 
 import junit.framework.AssertionFailedError;
+import org.apache.maven.archiva.model.ArtifactReference;
+import org.apache.maven.archiva.repository.content.DefaultPathParser;
+import org.apache.maven.archiva.repository.content.PathParser;
+import org.apache.maven.archiva.repository.layout.LayoutException;
 
 /**
  * FileContentIndexPopulator 
@@ -62,6 +64,11 @@ public class FileContentIndexPopulator
         map.put( "test-pom-1.0", createFileContentRecord( repoDir, prefix + "test-pom/1.0/test-pom-1.0.pom" ) );
         map.put( "test-skin-1.0", createFileContentRecord( repoDir, prefix + "test-skin/1.0/test-skin-1.0.pom" ) );
 
+        map.put("ant-1.5.pom", createFileContentRecord(repoDir, "ant/ant/1.5/ant-1.5.pom"));
+        map.put("ant-1.5.1.pom", createFileContentRecord(repoDir, "ant/ant/1.5.1/ant-1.5.1.pom"));
+        map.put("ant-junit-1.6.5.pom", createFileContentRecord(repoDir, "ant/ant-junit/1.6.5/ant-junit-1.6.5.pom"));
+        map.put("ant-optional-1.5.1.pom", createFileContentRecord(repoDir, "ant/ant-optional/1.5.1/ant-optional-1.5.1.pom"));
+        
         return map;
     }
 
@@ -78,14 +85,16 @@ public class FileContentIndexPopulator
         record.setRepositoryId( "test-repo" );
         record.setFilename( path );
 
+        PathParser pathParser = new DefaultPathParser();
         try
         {
-            record.setContents( FileUtils.readFileToString( pathToFile, null ) );
+            ArtifactReference reference = pathParser.toArtifactReference(path);
+            ArchivaArtifact artifact = new ArchivaArtifact( reference );
+            record.setArtifact(artifact);
         }
-        catch ( IOException e )
+        catch (LayoutException e)
         {
-            e.printStackTrace();
-            throw new AssertionFailedError( "Can't load test file contents: " + pathToFile.getAbsolutePath() );
+            throw new RuntimeException(e);
         }
 
         return record;
diff --git a/archiva-modules/archiva-base/archiva-indexer/src/test/managed-repository/ant/ant-junit/1.6.5/ant-junit-1.6.5.pom b/archiva-modules/archiva-base/archiva-indexer/src/test/managed-repository/ant/ant-junit/1.6.5/ant-junit-1.6.5.pom
new file mode 100644 (file)
index 0000000..c3710e6
--- /dev/null
@@ -0,0 +1,6 @@
+<project>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>ant</groupId>
+  <artifactId>ant-junit</artifactId>
+  <version>1.6.5</version>
+</project>
\ No newline at end of file
diff --git a/archiva-modules/archiva-base/archiva-indexer/src/test/managed-repository/ant/ant-optional/1.5.1/ant-optional-1.5.1.pom b/archiva-modules/archiva-base/archiva-indexer/src/test/managed-repository/ant/ant-optional/1.5.1/ant-optional-1.5.1.pom
new file mode 100644 (file)
index 0000000..9f34b9e
--- /dev/null
@@ -0,0 +1,6 @@
+<project>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>ant</groupId>
+  <artifactId>ant-optional</artifactId>
+  <version>1.5.1</version>
+</project>
\ No newline at end of file
diff --git a/archiva-modules/archiva-base/archiva-indexer/src/test/managed-repository/ant/ant/1.5.1/ant-1.5.1.pom b/archiva-modules/archiva-base/archiva-indexer/src/test/managed-repository/ant/ant/1.5.1/ant-1.5.1.pom
new file mode 100644 (file)
index 0000000..5ef7489
--- /dev/null
@@ -0,0 +1,6 @@
+<project>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>ant</groupId>
+  <artifactId>ant</artifactId>
+  <version>1.5.1</version>
+</project>
\ No newline at end of file
diff --git a/archiva-modules/archiva-base/archiva-indexer/src/test/managed-repository/ant/ant/1.5/ant-1.5.pom b/archiva-modules/archiva-base/archiva-indexer/src/test/managed-repository/ant/ant/1.5/ant-1.5.pom
new file mode 100644 (file)
index 0000000..b634f14
--- /dev/null
@@ -0,0 +1,6 @@
+<project>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>ant</groupId>
+  <artifactId>ant</artifactId>
+  <version>1.5</version>
+</project>
\ No newline at end of file
index 54bf1a828ae5a9051af8d29e946736c1cdb85397..9da58c4ecdddf08691c31b3b62cdc68a81185e37 100644 (file)
@@ -131,6 +131,7 @@ public class ArchivaArtifact
         return StringUtils.isNotEmpty( model.getClassifier() );
     }
 
+    @Override
     public int hashCode()
     {
         final int PRIME = 31;
@@ -146,6 +147,7 @@ public class ArchivaArtifact
         return result;
     }
 
+    @Override
     public boolean equals( Object obj )
     {
         if ( this == obj )
@@ -180,6 +182,7 @@ public class ArchivaArtifact
         return true;
     }
 
+    @Override
     public String toString()
     {
         StringBuffer sb = new StringBuffer();
index 939dbc51a5eb2f61a9a89782baef8768c612eadc..c3d9194fb59a270a14b7ab0559fbe1d00a434f0f 100644 (file)
@@ -46,6 +46,9 @@ import org.apache.maven.archiva.security.UserRepositories;
 
 import com.opensymphony.xwork2.ActionContext;
 import com.opensymphony.xwork2.Preparable;
+import org.apache.maven.archiva.common.utils.VersionUtil;
+import org.apache.maven.archiva.database.constraints.UniqueVersionConstraint;
+import org.apache.maven.archiva.indexer.search.SearchResultHit;
 
 /**
  * Search all indexed fields by the given criteria.
@@ -127,8 +130,6 @@ public class SearchAction
 
     private boolean fromResultsPage;
 
-    private int num;
-
     public boolean isFromResultsPage()
     {
         return fromResultsPage;
@@ -231,7 +232,8 @@ public class SearchAction
             return GlobalResults.ACCESS_TO_NO_REPOS;
         }
 
-        if( SearchUtil.isBytecodeSearch( q ) )
+        final boolean isbytecodeSearch = SearchUtil.isBytecodeSearch( q );
+        if( isbytecodeSearch )
         {
             results = crossRepoSearch.searchForBytecode( getPrincipal(), selectedRepos, SearchUtil.removeBytecodeKeyword( q ), limits );
         }
@@ -274,9 +276,41 @@ public class SearchAction
             buildCompleteQueryString( q );
         }
 
+        if (!isbytecodeSearch)
+        {
+            //Lets get the versions for the artifact we just found and display them
+            //Yes, this is in the lucene index but its more challenging to get them out when we are searching by project
+            for (SearchResultHit resultHit : results.getHits())
+            {
+                final List<String> versions = dao.query(new UniqueVersionConstraint(getObservableRepos(), resultHit.getGroupId(), resultHit.getArtifactId()));
+                if (versions != null && !versions.isEmpty())
+                {
+                    resultHit.setVersion(null);
+                    resultHit.setVersions(filterTimestampedSnapshots(versions));
+                }
+            }
+        }
+
         return SUCCESS;
     }
 
+    /**
+     * Remove timestamped snapshots from versions
+     */
+    private static List<String> filterTimestampedSnapshots(List<String> versions)
+    {
+        final List<String> filtered = new ArrayList<String>();
+        for (final String version : versions)
+        {
+            final String baseVersion = VersionUtil.getBaseVersion(version);
+            if (!filtered.contains(baseVersion))
+            {
+                filtered.add(baseVersion);
+            }
+        }
+        return filtered;
+    }
+
     public String findArtifact()
         throws Exception
     {
@@ -329,7 +363,6 @@ public class SearchAction
         catch ( AccessDeniedException e )
         {
             getLogger().warn( e.getMessage(), e );
-            // TODO: pass this onto the screen.
         }
         catch ( ArchivaSecurityException e )
         {
index 56fd71aa013e1d19bc347d742513f4a6892d586b..8ec411f0d77f9f634d3bdb7fe8b8940534506fca 100644 (file)
@@ -108,10 +108,10 @@ public class ShowArtifactAction
             this.repositoryId =
                 repoBrowsing.getRepositoryId( getPrincipal(), getObservableRepos(), groupId, artifactId, version );
         }
-        catch ( ObjectNotFoundException oe )
+        catch ( ObjectNotFoundException e )
         {
-            addActionError( "Unable to find project model for [" + groupId + ":" + artifactId + ":" + version + "]." );
-
+            getLogger().debug(e.getMessage(), e);
+            addActionError( e.getMessage() );
             return ERROR;
         }
 
@@ -208,6 +208,7 @@ public class ShowArtifactAction
         return Collections.emptyList();
     }
 
+    @Override
     public void validate()
     {
         if ( StringUtils.isBlank( groupId ) )
index 57ba124dba70b48405223a42bd544fdc6c004815..971f0025279673b8116f8c8c63eea161b6a0db5d 100644 (file)
@@ -20,7 +20,7 @@
 <%@ taglib uri="/struts-tags" prefix="s" %>
 <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
 <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
-<%@ taglib prefix="my" tagdir="/WEB-INF/tags" %>
+<%@ taglib prefix="archiva" tagdir="/WEB-INF/tags" %>
 
 <html>
 <head>
                    <s:param name="className" value="%{#attr.className}"/>
                    <s:param name="repositoryId" value="%{#attr.repositoryId}"/>
                    <s:param name="filterSearch" value="%{#attr.filterSearch}"/>
-                  <s:param name="fromResultsPage" value="true"/>
+                   <s:param name="fromResultsPage" value="true"/>
                    <s:param name="currentPage" value="%{#attr.currentPage - 1}"/>
-                 <s:param name="searchResultsOnly" value="%{#attr.searchResultsOnly}"/>
-                 <s:param name="completeQueryString" value="%{#attr.completeQueryString}"/>
+                   <s:param name="searchResultsOnly" value="%{#attr.searchResultsOnly}"/>
+                   <s:param name="completeQueryString" value="%{#attr.completeQueryString}"/>
                  </s:url>
                      </c:set>
                      <c:set var="nextPageUrl">
                   <s:param name="className" value="%{#attr.className}"/>
                   <s:param name="repositoryId" value="%{#attr.repositoryId}"/>
                   <s:param name="filterSearch" value="%{#attr.filterSearch}"/>
-                 <s:param name="fromResultsPage" value="true"/>
+                         <s:param name="fromResultsPage" value="true"/>
                   <s:param name="currentPage" value="%{#attr.currentPage + 1}"/>
-                 <s:param name="searchResultsOnly" value="%{#attr.searchResultsOnly}"/>
-                 <s:param name="completeQueryString" value="%{#attr.completeQueryString}"/>
+                         <s:param name="searchResultsOnly" value="%{#attr.searchResultsOnly}"/>
+                         <s:param name="completeQueryString" value="%{#attr.completeQueryString}"/>
                 </s:url>
              </c:set>    
              </c:if>
               <c:choose>
                 <c:when test="${not empty (record.groupId)}">
                   <h3 class="artifact-title">
-                    <my:showArtifactTitle groupId="${record.groupId}" artifactId="${record.artifactId}"
-                                          version="${record.version}"/>
+
+                    <archiva:showArtifactTitle groupId="${record.groupId}" artifactId="${record.artifactId}"/>
                   </h3>
                   <p>
-                    <my:showArtifactLink groupId="${record.groupId}" artifactId="${record.artifactId}"
-                                         version="${record.version}" versions="${record.versions}" repositoryId="${record.repositoryId}"/>
+                    <archiva:showArtifactLink groupId="${record.groupId}" artifactId="${record.artifactId}"
+                                         versions="${record.versions}" repositoryId="${record.repositoryId}"/>
                   </p>
                 </c:when>
                 <c:otherwise>
               <c:choose>
                 <c:when test="${not empty (artifactModel.groupId)}">
                   <h3 class="artifact-title">
-                    <my:showArtifactTitle groupId="${artifactModel.groupId}" artifactId="${artifactModel.artifactId}"
-                                          version="${artifactModel.version}"/>
+                    <archiva:showArtifactTitle groupId="${artifactModel.groupId}" artifactId="${artifactModel.artifactId}"/>
                   </h3>
                   <p>
-                    <my:showArtifactLink groupId="${artifactModel.groupId}" artifactId="${artifactModel.artifactId}"
-                                         version="${artifactModel.version}" versions="${artifactModel.versions}"/>
+                    <archiva:showArtifactLink groupId="${artifactModel.groupId}" artifactId="${artifactModel.artifactId}"
+                                         versions="${artifactModel.versions}"/>
                   </p>
                 </c:when>
                 <c:otherwise>
index a5327a1a3ba33e5bd48498a9d96a60311d6b0ab7..3377bf1bb43eed3122bd3220ffd4a760cafd5a88 100644 (file)
 <%@ attribute name="repositoryId" %>
 
 <span class="artifact-link">
-  <a href="${pageContext.request.contextPath}/repository/${repositoryId}">${repositoryId}</a>
-  <strong> : </strong>
-  <archiva:groupIdLink var="${groupId}" includeTop="false" />
-  
+  <archiva:groupIdLink var="${groupId}" includeTop="false" /> 
   <c:if test="${!empty (artifactId)}">    
     <c:set var="url">
       <s:url action="browseArtifact" namespace="/">
index 2deb6f1c2e4ef9a4be2c9f5be9c0b94bd3a888cb..2db1b931ce709aff092e16664e655718d46efcaa 100644 (file)
@@ -152,7 +152,6 @@ public class SearchServiceImplTest
         FileContentRecord record = new FileContentRecord();
         record.setRepositoryId( "repo1.mirror" );
         record.setArtifact( artifact );
-        record.setContents( "org.apache.archiva:archiva-test:1.0:jar org.apache.archiva.test.MyClassName" );
         record.setFilename( "archiva-test-1.0.jar" );
                 
         results.addHit( record );
@@ -198,7 +197,6 @@ public class SearchServiceImplTest
         FileContentRecord record = new FileContentRecord();
         record.setRepositoryId( "repo1.mirror" );
         record.setArtifact( artifact );
-        record.setContents( "org.apache.archiva:archiva-test:1.0:jar" );
         record.setFilename( "archiva-test-1.0.jar" );
                 
         results.addHit( record );
diff --git a/pom.xml b/pom.xml
index dbd03d690395dcd328c3ae799189dbec425350bf..3f13622441aa5f90cf0b9ea75e182c62b4a6962c 100644 (file)
--- a/pom.xml
+++ b/pom.xml
         <artifactId>lucene-core</artifactId>
         <version>2.4.0</version>
       </dependency>
+      <dependency>
+        <groupId>org.apache.lucene</groupId>
+        <artifactId>lucene-queries</artifactId>
+        <version>2.4.0</version>
+      </dependency>
       <dependency>
         <groupId>javax.mail</groupId>
         <artifactId>mail</artifactId>