]> source.dussan.org Git - archiva.git/commitdiff
[MRM-1283] moved first part of browse() functionality over to metadata repository
authorBrett Porter <brett@apache.org>
Fri, 27 Nov 2009 13:09:04 +0000 (13:09 +0000)
committerBrett Porter <brett@apache.org>
Fri, 27 Nov 2009 13:09:04 +0000 (13:09 +0000)
git-svn-id: https://svn.apache.org/repos/asf/archiva/branches/MRM-1025@884866 13f79535-47bb-0310-9956-ffa450edef68

archiva-modules/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/action/BrowseAction.java
archiva-modules/archiva-web/archiva-webapp/src/test/java/org/apache/archiva/metadata/repository/memory/TestMetadataResolver.java
archiva-modules/archiva-web/archiva-webapp/src/test/java/org/apache/maven/archiva/web/action/BrowseActionTest.java
archiva-modules/archiva-web/archiva-webapp/src/test/resources/org/apache/maven/archiva/web/action/BrowseActionTest.xml
archiva-modules/metadata/metadata-model/src/main/java/org/apache/archiva/metadata/model/ProjectVersionMetadata.java
archiva-modules/metadata/metadata-repository-api/src/main/java/org/apache/archiva/metadata/repository/DefaultMetadataResolver.java
archiva-modules/metadata/metadata-repository-api/src/main/java/org/apache/archiva/metadata/repository/MetadataResolver.java
archiva-modules/plugins/maven2-repository/src/main/java/org/apache/archiva/metadata/repository/storage/maven2/Maven2RepositoryMetadataResolver.java
archiva-modules/plugins/metadata-repository-file/pom.xml
archiva-modules/plugins/metadata-repository-file/src/main/java/org/apache/archiva/metadata/repository/file/FileMetadataRepository.java
archiva-modules/plugins/metadata-repository-file/src/test/java/org/apache/archiva/metadata/repository/file/FileMetadataRepositoryTest.java [new file with mode: 0644]

index 42020f657b908ed0b35f5fbca4b11d8fc2847328..3630a75bb2061e2ad6b970dfb20773d3a37b71c0 100644 (file)
@@ -19,9 +19,14 @@ package org.apache.maven.archiva.web.action;
  * under the License.
  */
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
 
+import org.apache.archiva.metadata.repository.MetadataResolver;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang.StringUtils;
 import org.apache.maven.archiva.database.ArchivaDatabaseException;
@@ -39,7 +44,6 @@ import org.apache.maven.archiva.security.UserRepositories;
  *
  * @todo cache browsing results.
  * @todo implement repository selectors (all or specific repository)
- * @todo implement security around browse (based on repository id at first)
  * @plexus.component role="com.opensymphony.xwork2.Action" role-hint="browseAction" instantiation-strategy="per-lookup"
  */
 public class BrowseAction
@@ -49,22 +53,29 @@ public class BrowseAction
      * @plexus.requirement role-hint="default"
      */
     private RepositoryBrowsing repoBrowsing;
-    
+
+    /**
+     * @plexus.requirement
+     */
+    private MetadataResolver metadataResolver;
+
     /**
      * @plexus.requirement
      */
     private UserRepositories userRepositories;
-    
+
+    // TODO: eventually, move to just use the metadata directly, with minimal JSP changes
     private BrowsingResults results;
 
     private String groupId;
 
     private String artifactId;
-    
+
     private String repositoryId;
-    
+
+    // TODO: eventually, move to just use the metadata directly, with minimal JSP changes, mostly for Maven specifics
     private ArchivaProjectModel sharedModel;
-    
+
     public String browse()
     {
         List<String> selectedRepos = getObservableRepos();
@@ -73,10 +84,49 @@ public class BrowseAction
             return GlobalResults.ACCESS_TO_NO_REPOS;
         }
 
-        this.results = repoBrowsing.getRoot( getPrincipal(), selectedRepos );
+        Set<String> namespaces = new LinkedHashSet<String>();
+        for ( String repoId : selectedRepos )
+        {
+            Collection<String> rootNamespaces = metadataResolver.getRootNamespaces( repoId );
+            // TODO: this logic should be optional, particularly remembering we want to keep this code simple
+            //       it is located here to avoid the content repository implementation needing to do too much for what
+            //       is essentially presentation code
+            for ( String n : rootNamespaces )
+            {
+                // TODO: check performance of this
+                namespaces.add( collapseNamespaces( repoId, n ) );
+            }
+        }
+        ArrayList<String> list = new ArrayList<String>( namespaces );
+        Collections.sort( list );
+
+        this.results = new BrowsingResults();
+        results.setGroupIds( list );
+        results.setSelectedRepositoryIds( selectedRepos );
         return SUCCESS;
     }
 
+    private String collapseNamespaces( String repoId, String n )
+    {
+        Collection<String> subNamespaces = metadataResolver.getNamespaces( repoId, n );
+        if ( subNamespaces.size() != 1 )
+        {
+            return n;
+        }
+        else
+        {
+            Collection<String> projects = metadataResolver.getProjects( repoId, n );
+            if ( projects != null && !projects.isEmpty() )
+            {
+                return n;
+            }
+            else
+            {
+                return collapseNamespaces( repoId, n + "." + subNamespaces.iterator().next() );
+            }
+        }
+    }
+
     public String browseGroup()
     {
         if ( StringUtils.isEmpty( groupId ) )
@@ -92,7 +142,6 @@ public class BrowseAction
             return GlobalResults.ACCESS_TO_NO_REPOS;
         }
 
-        
         this.results = repoBrowsing.selectGroupId( getPrincipal(), selectedRepos, groupId );
         return SUCCESS;
     }
@@ -118,11 +167,11 @@ public class BrowseAction
         {
             return GlobalResults.ACCESS_TO_NO_REPOS;
         }
-        
+
         this.results = repoBrowsing.selectArtifactId( getPrincipal(), selectedRepos, groupId, artifactId );
 
         populateSharedModel();
-        
+
         return SUCCESS;
     }
 
@@ -132,20 +181,20 @@ public class BrowseAction
         sharedModel.setGroupId( groupId );
         sharedModel.setArtifactId( artifactId );
         boolean isFirstVersion = true;
-                
-        for( String version :  this.results.getVersions() )
-        {            
+
+        for ( String version : this.results.getVersions() )
+        {
             try
             {
                 ArchivaProjectModel model =
                     repoBrowsing.selectVersion( getPrincipal(), getObservableRepos(), groupId, artifactId, version );
-                
-                if( model == null )
+
+                if ( model == null )
                 {
                     continue;
                 }
-                
-                if( isFirstVersion )
+
+                if ( isFirstVersion )
                 {
                     sharedModel = model;
                     sharedModel.setVersion( null );
@@ -157,7 +206,7 @@ public class BrowseAction
                     {
                         sharedModel.setPackaging( null );
                     }
-                    
+
                     if ( sharedModel.getName() != null &&
                         !StringUtils.equalsIgnoreCase( sharedModel.getName(), model.getName() ) )
                     {
@@ -171,29 +220,33 @@ public class BrowseAction
                     }
 
                     if ( sharedModel.getIssueManagement() != null && model.getIssueManagement() != null &&
-                        !StringUtils.equalsIgnoreCase( sharedModel.getIssueManagement().getIssueManagementUrl(), model.getIssueManagement().getIssueManagementUrl() ) )
+                        !StringUtils.equalsIgnoreCase( sharedModel.getIssueManagement().getIssueManagementUrl(),
+                                                       model.getIssueManagement().getIssueManagementUrl() ) )
                     {
                         sharedModel.setIssueManagement( null );
                     }
 
                     if ( sharedModel.getCiManagement() != null && model.getCiManagement() != null &&
-                        !StringUtils.equalsIgnoreCase( sharedModel.getCiManagement().getCiUrl(), model.getCiManagement().getCiUrl() ) )
+                        !StringUtils.equalsIgnoreCase( sharedModel.getCiManagement().getCiUrl(),
+                                                       model.getCiManagement().getCiUrl() ) )
                     {
                         sharedModel.setCiManagement( null );
                     }
 
-                    if ( sharedModel.getOrganization() != null && model.getOrganization() != null && 
-                        !StringUtils.equalsIgnoreCase( sharedModel.getOrganization().getOrganizationName(), model.getOrganization().getOrganizationName() ) )
+                    if ( sharedModel.getOrganization() != null && model.getOrganization() != null &&
+                        !StringUtils.equalsIgnoreCase( sharedModel.getOrganization().getOrganizationName(),
+                                                       model.getOrganization().getOrganizationName() ) )
                     {
                         sharedModel.setOrganization( null );
                     }
 
-                    if ( sharedModel.getUrl() != null && !StringUtils.equalsIgnoreCase( sharedModel.getUrl(), model.getUrl() ) )
+                    if ( sharedModel.getUrl() != null &&
+                        !StringUtils.equalsIgnoreCase( sharedModel.getUrl(), model.getUrl() ) )
                     {
                         sharedModel.setUrl( null );
                     }
                 }
-                
+
                 isFirstVersion = false;
             }
             catch ( ObjectNotFoundException e )
@@ -204,9 +257,9 @@ public class BrowseAction
             {
                 log.debug( e.getMessage(), e );
             }
-        }        
+        }
     }
-    
+
     private List<String> getObservableRepos()
     {
         try
@@ -253,15 +306,17 @@ public class BrowseAction
     {
         return results;
     }
-    
-    public String getRepositoryId(){
-       
-       return repositoryId;
+
+    public String getRepositoryId()
+    {
+
+        return repositoryId;
     }
-    
-    public void setRepositoryId(String repositoryId){
-       
-       this.repositoryId = repositoryId;
+
+    public void setRepositoryId( String repositoryId )
+    {
+
+        this.repositoryId = repositoryId;
     }
 
     public ArchivaProjectModel getSharedModel()
@@ -273,4 +328,9 @@ public class BrowseAction
     {
         this.sharedModel = sharedModel;
     }
+
+    public MetadataResolver getMetadataResolver()
+    {
+        return metadataResolver;
+    }
 }
index 8287f3aaec8936585079c159f5f6a1fabb511656..b0473b20119439c6b6b96f1370309adc7b214b61 100644 (file)
@@ -22,8 +22,10 @@ package org.apache.archiva.metadata.repository.memory;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.archiva.metadata.model.ProjectMetadata;
 import org.apache.archiva.metadata.model.ProjectVersionMetadata;
@@ -40,6 +42,10 @@ public class TestMetadataResolver
     private Map<String, List<ProjectVersionReference>> references =
         new HashMap<String, List<ProjectVersionReference>>();
 
+    private List<String> namespaces;
+
+    private Map<String, Collection<String>> projectsInNamespace = new HashMap<String, Collection<String>>();
+
     public ProjectMetadata getProject( String repoId, String namespace, String projectId )
     {
         ProjectMetadata metadata = new ProjectMetadata();
@@ -67,10 +73,54 @@ public class TestMetadataResolver
         return references.get( createMapKey( repoId, namespace, projectId, projectVersion ) );
     }
 
+    public Collection<String> getRootNamespaces( String repoId )
+    {
+        return getNamespaces( null );
+    }
+
+    private Collection<String> getNamespaces( String baseNamespace )
+    {
+        Set<String> namespaces = new LinkedHashSet<String>();
+        int fromIndex = baseNamespace != null ? baseNamespace.length() + 1 : 0;
+        for ( String namespace : this.namespaces )
+        {
+            if ( baseNamespace == null || namespace.startsWith( baseNamespace + "." ) )
+            {
+                int i = namespace.indexOf( '.', fromIndex );
+                if ( i >= 0 )
+                {
+                    namespaces.add( namespace.substring( fromIndex, i ) );
+                }
+                else
+                {
+                    namespaces.add( namespace.substring( fromIndex ) );
+                }
+            }
+        }
+        return namespaces;
+    }
+
+    public Collection<String> getNamespaces( String repoId, String namespace )
+    {
+        return getNamespaces( namespace );
+    }
+
+    public Collection<String> getProjects( String repoId, String namespace )
+    {
+        return projectsInNamespace.get( namespace );
+    }
+
     public void setProjectVersion( String repoId, String namespace, String projectId,
                                    ProjectVersionMetadata versionMetadata )
     {
         projectVersions.put( createMapKey( repoId, namespace, projectId, versionMetadata.getId() ), versionMetadata );
+        Collection<String> projects = projectsInNamespace.get( namespace );
+        if ( projects == null )
+        {
+            projects = new LinkedHashSet<String>();
+            projectsInNamespace.put( namespace, projects );
+        }
+        projects.add( projectId );
     }
 
     public void setArtifactVersions( String repoId, String namespace, String projectId, String projectVersion,
@@ -89,4 +139,9 @@ public class TestMetadataResolver
     {
         this.references.put( createMapKey( repoId, namespace, projectId, projectVersion ), references );
     }
+
+    public void setNamespaces( List<String> namespaces )
+    {
+        this.namespaces = namespaces;
+    }
 }
index 2fb9658d24f800d1491195d46f96b686f36d24d8..dd95b40ccae912cc92790ca890354ddef3e4dad8 100644 (file)
@@ -24,6 +24,8 @@ import java.util.Collections;
 import java.util.List;
 
 import com.opensymphony.xwork2.Action;
+import org.apache.archiva.metadata.model.ProjectVersionMetadata;
+import org.apache.archiva.metadata.repository.memory.TestMetadataResolver;
 import org.apache.maven.archiva.database.ArchivaDAO;
 import org.apache.maven.archiva.database.ArchivaDatabaseException;
 import org.apache.maven.archiva.database.ArtifactDAO;
@@ -52,7 +54,8 @@ public class BrowseActionTest
     private ArchivaDAOStub archivaDao;
 
     private static final List<String> GROUPS =
-        Arrays.asList( "org.apache.archiva", "commons-lang", "org.apache.maven", "com.sun", "com.oracle" );
+        Arrays.asList( "org.apache.archiva", "commons-lang", "org.apache.maven", "com.sun", "com.oracle",
+                       "repeat.repeat", "org.apache", "single.group" );
 
     public void testInstantiation()
     {
@@ -61,7 +64,9 @@ public class BrowseActionTest
 
     public void testBrowse()
     {
-        archivaDao.setGroups( GROUPS );
+        metadataResolver.setNamespaces( GROUPS );
+        // add an artifact in the tree to make sure "single" is not collapsed
+        metadataResolver.setProjectVersion( TEST_REPO, "single", "single", new ProjectVersionMetadata() );
 
         String result = action.browse();
         assertSuccessResult( result );
@@ -69,7 +74,8 @@ public class BrowseActionTest
         BrowsingResults results = action.getResults();
         assertNotNull( results );
         assertEquals( Arrays.asList( TEST_REPO ), results.getSelectedRepositoryIds() );
-        assertEquals( Arrays.asList( "com", "commons-lang", "org.apache" ), results.getGroupIds() );
+        assertEquals( Arrays.asList( "com", "commons-lang", "org.apache", "repeat.repeat", "single" ),
+                      results.getGroupIds() );
         assertNull( results.getArtifacts() );
         assertNull( results.getSelectedArtifactId() );
         assertNull( results.getSelectedGroupId() );
@@ -390,6 +396,7 @@ public class BrowseActionTest
         super.setUp();
         action = (BrowseAction) lookup( Action.class, ACTION_HINT );
         archivaDao = (ArchivaDAOStub) lookup( ArchivaDAO.class, "jdo" );
+        metadataResolver = (TestMetadataResolver) action.getMetadataResolver();
     }
 
     protected ArchivaProjectModel createProjectModel( String groupId, String artifactId, String version )
index cccf675cb7c6da8c0f948304bbdc873e24989d45..3bd761e1905e4d7f973406ce37cfb3b21c6aeae6 100644 (file)
       <role-hint>default</role-hint>
       <implementation>org.apache.maven.archiva.security.UserRepositoriesStub</implementation>
     </component>
+    <component>
+      <role>org.apache.archiva.metadata.repository.MetadataResolver</role>
+      <role-hint>default</role-hint>
+      <implementation>org.apache.archiva.metadata.repository.memory.TestMetadataResolver</implementation>
+      <instantiation-strategy>per-lookup</instantiation-strategy>
+    </component>
   </components>
 </plexus>
index 0f60d601e2fd4ff507db05e5da210da93d14d71f..5e4626f9464eae2937f7e6c6440cbc13ab57aa11 100644 (file)
@@ -43,13 +43,13 @@ public class ProjectVersionMetadata
 
     private CiManagement ciManagement;
 
-    private List<License> licenses;
+    private List<License> licenses = new ArrayList<License>();
 
     private Map<String, ProjectVersionFacet> facets = new HashMap<String, ProjectVersionFacet>();
 
-    private List<MailingList> mailingLists;
+    private List<MailingList> mailingLists = new ArrayList<MailingList>();
 
-    private List<Dependency> dependencies;
+    private List<Dependency> dependencies = new ArrayList<Dependency>();
 
     public String getId()
     {
@@ -143,10 +143,6 @@ public class ProjectVersionMetadata
 
     public void addLicense( License license )
     {
-        if ( this.licenses == null )
-        {
-            this.licenses = new ArrayList<License>();
-        }
         this.licenses.add( license );
     }
 
@@ -182,10 +178,6 @@ public class ProjectVersionMetadata
 
     public void addMailingList( MailingList mailingList )
     {
-        if ( this.mailingLists == null )
-        {
-            this.mailingLists = new ArrayList<MailingList>();
-        }
         this.mailingLists.add( mailingList );
     }
 
@@ -201,10 +193,6 @@ public class ProjectVersionMetadata
 
     public void addDependency( Dependency dependency )
     {
-        if ( this.dependencies == null )
-        {
-            this.dependencies = new ArrayList<Dependency>();
-        }
         this.dependencies.add( dependency );
     }
 }
index 64f15522dc9269aeebb15ea8bfb914e01091d63f..17b237455a06380b3d5e84a2b2c07a3b9ca6558a 100644 (file)
@@ -101,4 +101,25 @@ public class DefaultMetadataResolver
         // not passed to the storage mechanism as resolving references would require iterating all artifacts
         return metadataRepository.getProjectReferences( repoId, namespace, projectId, projectVersion );
     }
+
+    public Collection<String> getRootNamespaces( String repoId )
+    {
+        // TODO: is this assumption correct? could a storage mech. actually know all references in a non-Maven scenario?
+        // not passed to the storage mechanism as resolving references would require iterating all groups
+        return metadataRepository.getRootNamespaces( repoId );
+    }
+
+    public Collection<String> getNamespaces( String repoId, String namespace )
+    {
+        // TODO: is this assumption correct? could a storage mech. actually know all references in a non-Maven scenario?
+        // not passed to the storage mechanism as resolving references would require iterating all groups
+        return metadataRepository.getNamespaces( repoId, namespace );
+    }
+
+    public Collection<String> getProjects( String repoId, String namespace )
+    {
+        // TODO: is this assumption correct? could a storage mech. actually know all references in a non-Maven scenario?
+        // not passed to the storage mechanism as resolving references would require iterating all projects
+        return metadataRepository.getProjects( repoId, namespace );
+    }
 }
index e6f8e63165e747f1ae30193ee1ca175b1f8614fd..2a2600ae0e63949a6c9de7f873a24199a8cbbc3d 100644 (file)
@@ -47,4 +47,10 @@ public interface MetadataResolver
      */
     Collection<ProjectVersionReference> getProjectReferences( String repoId, String namespace, String projectId,
                                                               String projectVersion );
+
+    Collection<String> getRootNamespaces( String repoId );
+
+    Collection<String> getNamespaces( String repoId, String namespace );
+
+    Collection<String> getProjects( String repoId, String namespace );
 }
index 54e6f2e5086f07d51e5f57af740d9f2ff5e21346..10ba310b4eb866db8c2d45efc9b78e47bbf92fbf 100644 (file)
@@ -281,4 +281,19 @@ public class Maven2RepositoryMetadataResolver
     {
         throw new UnsupportedOperationException();
     }
+
+    public Collection<String> getRootNamespaces( String repoId )
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public List<String> getNamespaces( String repoId, String namespace )
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public Collection<String> getProjects( String repoId, String namespace )
+    {
+        throw new UnsupportedOperationException();
+    }
 }
index ec034157b1106d3f674c0577377db89928048b57..09eeeb71b9841060b91684c75f50c90a5aa09e4d 100644 (file)
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-simple</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging-api</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.plexus</groupId>
+      <artifactId>plexus-spring</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 </project>
index 728898accb9e9fad4e262aaa293c27c9398ea87e..57772258c21701fcea46dd18350d22f874527a0a 100644 (file)
@@ -28,9 +28,11 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
+import java.util.Set;
 
 import org.apache.archiva.metadata.model.ArtifactMetadata;
 import org.apache.archiva.metadata.model.CiManagement;
@@ -70,17 +72,30 @@ public class FileMetadataRepository
 
     private static final Logger log = LoggerFactory.getLogger( FileMetadataRepository.class );
 
+    private static final String PROJECT_METADATA_KEY = "project-metadata";
+
+    private static final String PROJECT_VERSION_METADATA_KEY = "version-metadata";
+
+    private static final String NAMESPACE_METADATA_KEY = "namespace-metadata";
+
     public void updateProject( String repoId, ProjectMetadata project )
+    {
+        updateProject( repoId, project.getNamespace(), project.getId() );
+    }
+
+    private void updateProject( String repoId, String namespace, String id )
     {
         // TODO: this is a more braindead implementation than we would normally expect, for prototyping purposes
         try
         {
-            File projectDirectory =
-                new File( this.directory, repoId + "/" + project.getNamespace() + "/" + project.getId() );
+            File namespaceDirectory = new File( this.directory, repoId + "/" + namespace );
             Properties properties = new Properties();
-            properties.setProperty( "namespace", project.getNamespace() );
-            properties.setProperty( "id", project.getId() );
-            writeProperties( properties, projectDirectory );
+            properties.setProperty( "namespace", namespace );
+            writeProperties( properties, namespaceDirectory, NAMESPACE_METADATA_KEY );
+
+            properties.setProperty( "id", id );
+            writeProperties( properties, new File( namespaceDirectory, id ), PROJECT_METADATA_KEY );
+
         }
         catch ( IOException e )
         {
@@ -92,10 +107,12 @@ public class FileMetadataRepository
     public void updateProjectVersion( String repoId, String namespace, String projectId,
                                       ProjectVersionMetadata versionMetadata )
     {
+        updateProject( repoId, namespace, projectId );
+
         File directory =
             new File( this.directory, repoId + "/" + namespace + "/" + projectId + "/" + versionMetadata.getId() );
 
-        Properties properties = readProperties( directory );
+        Properties properties = readProperties( directory, PROJECT_VERSION_METADATA_KEY );
         // remove properties that are not references or artifacts
         for ( String name : properties.stringPropertyNames() )
         {
@@ -167,7 +184,7 @@ public class FileMetadataRepository
 
         try
         {
-            writeProperties( properties, directory );
+            writeProperties( properties, directory, PROJECT_VERSION_METADATA_KEY );
         }
         catch ( IOException e )
         {
@@ -181,7 +198,7 @@ public class FileMetadataRepository
     {
         File directory = new File( this.directory, repoId + "/" + namespace + "/" + projectId + "/" + projectVersion );
 
-        Properties properties = readProperties( directory );
+        Properties properties = readProperties( directory, PROJECT_VERSION_METADATA_KEY );
         int i = Integer.valueOf( properties.getProperty( "ref:lastReferenceNum", "-1" ) ) + 1;
         setProperty( properties, "ref:lastReferenceNum", Integer.toString( i ) );
         setProperty( properties, "ref:reference." + i + ".namespace", reference.getNamespace() );
@@ -191,7 +208,7 @@ public class FileMetadataRepository
 
         try
         {
-            writeProperties( properties, directory );
+            writeProperties( properties, directory, PROJECT_VERSION_METADATA_KEY );
         }
         catch ( IOException e )
         {
@@ -202,13 +219,17 @@ public class FileMetadataRepository
 
     private String join( Collection<String> ids )
     {
-        StringBuilder s = new StringBuilder();
-        for ( String id : ids )
+        if ( !ids.isEmpty() )
         {
-            s.append( id );
-            s.append( "," );
+            StringBuilder s = new StringBuilder();
+            for ( String id : ids )
+            {
+                s.append( id );
+                s.append( "," );
+            }
+            return s.substring( 0, s.length() - 1 );
         }
-        return s.substring( 0, s.length() - 1 );
+        return "";
     }
 
     private void setProperty( Properties properties, String name, String value )
@@ -224,7 +245,7 @@ public class FileMetadataRepository
     {
         File directory = new File( this.directory, repoId + "/" + namespace + "/" + projectId + "/" + projectVersion );
 
-        Properties properties = readProperties( directory );
+        Properties properties = readProperties( directory, PROJECT_VERSION_METADATA_KEY );
 
         properties.setProperty( "artifact:updated:" + artifact.getId(),
                                 Long.toString( artifact.getUpdated().getTime() ) );
@@ -233,7 +254,7 @@ public class FileMetadataRepository
 
         try
         {
-            writeProperties( properties, directory );
+            writeProperties( properties, directory, PROJECT_VERSION_METADATA_KEY );
         }
         catch ( IOException e )
         {
@@ -242,13 +263,13 @@ public class FileMetadataRepository
         }
     }
 
-    private Properties readProperties( File directory )
+    private Properties readProperties( File directory, String propertiesKey )
     {
         Properties properties = new Properties();
         FileInputStream in = null;
         try
         {
-            in = new FileInputStream( new File( directory, "metadata.properties" ) );
+            in = new FileInputStream( new File( directory, propertiesKey + ".properties" ) );
             properties.load( in );
         }
         catch ( FileNotFoundException e )
@@ -271,7 +292,7 @@ public class FileMetadataRepository
     {
         File directory = new File( this.directory, repoId + "/" + namespace + "/" + projectId );
 
-        Properties properties = readProperties( directory );
+        Properties properties = readProperties( directory, PROJECT_VERSION_METADATA_KEY );
 
         ProjectMetadata project = new ProjectMetadata();
         project.setNamespace( properties.getProperty( "namespace" ) );
@@ -284,7 +305,7 @@ public class FileMetadataRepository
     {
         File directory = new File( this.directory, repoId + "/" + namespace + "/" + projectId + "/" + projectVersion );
 
-        Properties properties = readProperties( directory );
+        Properties properties = readProperties( directory, PROJECT_VERSION_METADATA_KEY );
         String id = properties.getProperty( "id" );
         ProjectVersionMetadata versionMetadata = null;
         if ( id != null )
@@ -443,7 +464,7 @@ public class FileMetadataRepository
     {
         File directory = new File( this.directory, repoId + "/" + namespace + "/" + projectId + "/" + projectVersion );
 
-        Properties properties = readProperties( directory );
+        Properties properties = readProperties( directory, PROJECT_VERSION_METADATA_KEY );
 
         List<String> versions = new ArrayList<String>();
         for ( Map.Entry entry : properties.entrySet() )
@@ -462,7 +483,7 @@ public class FileMetadataRepository
     {
         File directory = new File( this.directory, repoId + "/" + namespace + "/" + projectId + "/" + projectVersion );
 
-        Properties properties = readProperties( directory );
+        Properties properties = readProperties( directory, PROJECT_VERSION_METADATA_KEY );
         int numberOfRefs = Integer.valueOf( properties.getProperty( "ref:lastReferenceNum", "-1" ) ) + 1;
 
         List<ProjectVersionReference> references = new ArrayList<ProjectVersionReference>();
@@ -479,11 +500,70 @@ public class FileMetadataRepository
         return references;
     }
 
-    private void writeProperties( Properties properties, File directory )
+    public Collection<String> getRootNamespaces( String repoId )
+    {
+        return getNamespaces( repoId, null );
+    }
+
+    public Collection<String> getNamespaces( String repoId, String baseNamespace )
+    {
+        List<String> allNamespaces = new ArrayList<String>();
+        File directory = new File( this.directory, repoId );
+        File[] files = directory.listFiles();
+        if ( files != null )
+        {
+            for ( File namespace : files )
+            {
+                if ( new File( namespace, NAMESPACE_METADATA_KEY + ".properties" ).exists() )
+                {
+                    allNamespaces.add( namespace.getName() );
+                }
+            }
+        }
+
+        Set<String> namespaces = new LinkedHashSet<String>();
+        int fromIndex = baseNamespace != null ? baseNamespace.length() + 1 : 0;
+        for ( String namespace : allNamespaces )
+        {
+            if ( baseNamespace == null || namespace.startsWith( baseNamespace + "." ) )
+            {
+                int i = namespace.indexOf( '.', fromIndex );
+                if ( i >= 0 )
+                {
+                    namespaces.add( namespace.substring( fromIndex, i ) );
+                }
+                else
+                {
+                    namespaces.add( namespace.substring( fromIndex ) );
+                }
+            }
+        }
+        return new ArrayList<String>( namespaces );
+    }
+
+    public Collection<String> getProjects( String repoId, String namespace )
+    {
+        List<String> projects = new ArrayList<String>();
+        File directory = new File( this.directory, repoId + "/" + namespace );
+        File[] files = directory.listFiles();
+        if ( files != null )
+        {
+            for ( File project : files )
+            {
+                if ( new File( project, PROJECT_METADATA_KEY + ".properties" ).exists() )
+                {
+                    projects.add( project.getName() );
+                }
+            }
+        }
+        return projects;
+    }
+
+    private void writeProperties( Properties properties, File directory, String propertiesKey )
         throws IOException
     {
         directory.mkdirs();
-        FileOutputStream os = new FileOutputStream( new File( directory, "metadata.properties" ) );
+        FileOutputStream os = new FileOutputStream( new File( directory, propertiesKey + ".properties" ) );
         try
         {
             properties.store( os, null );
@@ -494,4 +574,8 @@ public class FileMetadataRepository
         }
     }
 
+    public void setDirectory( File directory )
+    {
+        this.directory = directory;
+    }
 }
diff --git a/archiva-modules/plugins/metadata-repository-file/src/test/java/org/apache/archiva/metadata/repository/file/FileMetadataRepositoryTest.java b/archiva-modules/plugins/metadata-repository-file/src/test/java/org/apache/archiva/metadata/repository/file/FileMetadataRepositoryTest.java
new file mode 100644 (file)
index 0000000..45c8bf2
--- /dev/null
@@ -0,0 +1,70 @@
+package org.apache.archiva.metadata.repository.file;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.archiva.metadata.model.MailingList;
+import org.apache.archiva.metadata.model.ProjectVersionMetadata;
+import org.apache.archiva.metadata.repository.MetadataRepository;
+import org.apache.commons.io.FileUtils;
+import org.codehaus.plexus.spring.PlexusInSpringTestCase;
+
+/*
+ * 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.
+ */
+
+public class FileMetadataRepositoryTest
+    extends PlexusInSpringTestCase
+{
+    private FileMetadataRepository repository;
+
+    private static final String TEST_REPO_ID = "test";
+
+    private static final String TEST_PROJECT = "projectId";
+
+    private static final String TEST_NAMESPACE = "namespace";
+
+    public void setUp()
+        throws Exception
+    {
+        super.setUp();
+
+        repository = (FileMetadataRepository) lookup( MetadataRepository.class );
+        File directory = getTestFile( "target/test-repository" );
+        FileUtils.deleteDirectory( directory );
+        repository.setDirectory( directory );
+    }
+
+    public void testRootNamespaceWithNoMetadataRepository()
+    {
+        Collection<String> namespaces = repository.getRootNamespaces( TEST_REPO_ID );
+        assertEquals( Collections.<String>emptyList(), namespaces );
+    }
+
+    public void testUpdateProjectVersionMetadataWithNoOtherArchives()
+    {
+        ProjectVersionMetadata metadata = new ProjectVersionMetadata();
+        metadata.setId( TEST_PROJECT );
+        MailingList mailingList = new MailingList();
+        mailingList.setName( "Foo List" );
+        mailingList.setOtherArchives( Collections.<String>emptyList() );
+        metadata.setMailingLists( Collections.singletonList( mailingList ) );
+        repository.updateProjectVersion( TEST_REPO_ID, TEST_NAMESPACE, TEST_PROJECT, metadata );
+    }
+}