You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@archiva.apache.org by ma...@apache.org on 2020/03/12 14:38:52 UTC

[archiva] branch master updated: Modifying query interface and implementing new repo content methods

This is an automated email from the ASF dual-hosted git repository.

martin_s pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/archiva.git


The following commit(s) were added to refs/heads/master by this push:
     new fb63a9d  Modifying query interface and implementing new repo content methods
fb63a9d is described below

commit fb63a9d09d27c93a4884130cbc436582c0573639
Author: Martin Stockhammer <ma...@apache.org>
AuthorDate: Thu Mar 12 15:38:12 2020 +0100

    Modifying query interface and implementing new repo content methods
---
 .../repository/ManagedRepositoryContent.java       |   5 +-
 .../archiva/repository/content/ItemSelector.java   |  10 +-
 .../content/base/ArchivaItemSelector.java          | 112 +++++++--
 .../mock/ManagedRepositoryContentMock.java         |   4 +-
 .../scanner/mock/ManagedRepositoryContentMock.java |   4 +-
 .../mock/ManagedRepositoryContentMock.java         |   4 +-
 .../content/ManagedDefaultRepositoryContent.java   | 258 ++++++++++++++++-----
 7 files changed, 295 insertions(+), 102 deletions(-)

diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/ManagedRepositoryContent.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/ManagedRepositoryContent.java
index ce819eb..318a6a6 100644
--- a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/ManagedRepositoryContent.java
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/ManagedRepositoryContent.java
@@ -186,7 +186,7 @@ public interface ManagedRepositoryContent extends RepositoryContent
      * @throws ItemNotFoundException if the specified coordinates cannot be found in the repository
      * @throws ContentAccessException if the access to the underlying storage failed
      */
-    Stream<? extends Artifact> getArtifactStream( ItemSelector selector) throws ContentAccessException;
+    Stream<? extends Artifact> newArtifactStream( ItemSelector selector) throws ContentAccessException;
 
 
     /**
@@ -247,8 +247,9 @@ public interface ManagedRepositoryContent extends RepositoryContent
      * @param item the item from where the artifacts should be returned
      * @return a stream of artifacts. The stream is auto closable. You should always make sure, that the stream
      * is closed after use.
+     * @throws ContentAccessException if the access to the underlying storage failed
      */
-    Stream<? extends Artifact> getArtifactStream( ContentItem item ) throws ContentAccessException;
+    Stream<? extends Artifact> newArtifactStream( ContentItem item ) throws ContentAccessException;
 
 
     /**
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/ItemSelector.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/ItemSelector.java
index 6bff843..bc048f9 100644
--- a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/ItemSelector.java
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/content/ItemSelector.java
@@ -30,7 +30,7 @@ public interface ItemSelector
 {
 
     /**
-     * Selects the namespace to search for. You can use the {@link #searchSubNamespaces()} flag
+     * Selects the namespace to search for. You can use the {@link #recurse()} flag
      * to decide, if only the given namespace or the namespace and all sub namespaces (if they exist) should be
      * queried. If empty, the root namespace is searched.
      * @return the namespace to search
@@ -96,10 +96,10 @@ public interface ItemSelector
     Map<String, String> getAttributes( );
 
     /**
-     * Returns <code>true</code>, if not only the given namespace but all sub namespaces
-     * of the given namespace should be queried too.
+     * Returns <code>true</code>, if the query should recurse into all sub directories for
+     * retrieving artifacts.
      */
-    boolean searchSubNamespaces();
+    boolean recurse();
 
     /**
      * <code>true</code>, if all files/assets should be returned that match the given selector,
@@ -107,7 +107,7 @@ public interface ItemSelector
      * Related assets are e.g. hash files or signature files.
      * @return <code>true</code>, if all assets should be found otherwise <code>false</code>
      */
-    boolean findRelatedArtifacts();
+    boolean includeRelatedArtifacts();
 
     default boolean hasNamespace( )
     {
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaItemSelector.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaItemSelector.java
index 3737429..103ce28 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaItemSelector.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/content/base/ArchivaItemSelector.java
@@ -32,17 +32,17 @@ import java.util.Map;
 public class ArchivaItemSelector implements ItemSelector
 {
 
-    private String projectId = null;
-    private String version = null;
-    private String artifactVersion = null;
-    private String artifactId = null;
+    private String projectId = "";
+    private String version = "";
+    private String artifactVersion = "";
+    private String artifactId = "";
     private String namespace = "";
-    private String type = null;
-    private String classifier = null;
-    private String extension = null;
+    private String type = "";
+    private String classifier = "";
+    private String extension = "";
     private Map<String, String> attributes;
-    private boolean searchRelatedArtifacts = false;
-    private boolean searchSubNamespaces = false;
+    private boolean includeRelatedArtifacts = false;
+    private boolean recurse = false;
 
 
     private ArchivaItemSelector( )
@@ -61,49 +61,70 @@ public class ArchivaItemSelector implements ItemSelector
 
         public Builder withNamespace( String namespace )
         {
-            selector.namespace = namespace;
+            if (namespace!=null)
+            {
+                selector.namespace = namespace;
+            }
             return this;
         }
 
 
         public Builder withProjectId( String projectId )
         {
-            selector.projectId = projectId;
+            if (projectId!=null)
+            {
+                selector.projectId = projectId;
+            }
             return this;
         }
 
 
         public Builder withVersion( String version )
         {
-            selector.version = version;
+            if (version!=null)
+            {
+                selector.version = version;
+            }
             return this;
         }
 
 
         public Builder withArtifactVersion( String artifactVersion )
         {
-            selector.artifactVersion = artifactVersion;
+            if (artifactVersion!=null)
+            {
+                selector.artifactVersion = artifactVersion;
+            }
             return this;
         }
 
 
         public Builder withArtifactId( String artifactId )
         {
-            selector.artifactId = artifactId;
+            if (artifactId!=null)
+            {
+                selector.artifactId = artifactId;
+            }
             return this;
         }
 
 
         public Builder withType( String type )
         {
-            selector.type = type;
+            if (type!=null)
+            {
+                selector.type = type;
+            }
             return this;
         }
 
 
         public Builder withClassifier( String classifier )
         {
-            selector.classifier = classifier;
+            if (classifier != null )
+            {
+                selector.classifier = classifier;
+            }
             return this;
         }
 
@@ -116,17 +137,20 @@ public class ArchivaItemSelector implements ItemSelector
 
         public Builder withExtension( String extension )
         {
-            selector.extension = extension;
+            if (extension!=null)
+            {
+                selector.extension = extension;
+            }
             return this;
         }
 
-        public Builder enableSearchRelatedArtifacts() {
-            selector.searchRelatedArtifacts = true;
+        public Builder includeRelatedArtifacts() {
+            selector.includeRelatedArtifacts = true;
             return this;
         }
 
-        public Builder enableSearchSubNamespaces() {
-            selector.searchSubNamespaces = true;
+        public Builder recurse() {
+            selector.recurse = true;
             return this;
         }
 
@@ -221,15 +245,15 @@ public class ArchivaItemSelector implements ItemSelector
     }
 
     @Override
-    public boolean searchSubNamespaces( )
+    public boolean recurse( )
     {
-        return searchSubNamespaces;
+        return recurse;
     }
 
     @Override
-    public boolean findRelatedArtifacts( )
+    public boolean includeRelatedArtifacts( )
     {
-        return searchRelatedArtifacts;
+        return includeRelatedArtifacts;
     }
 
     @Override
@@ -243,4 +267,42 @@ public class ArchivaItemSelector implements ItemSelector
     {
         return StringUtils.isNotEmpty( extension );
     }
+
+    @Override
+    public boolean equals( Object o )
+    {
+        if ( this == o ) return true;
+        if ( o == null || getClass( ) != o.getClass( ) ) return false;
+
+        ArchivaItemSelector that = (ArchivaItemSelector) o;
+
+        if ( includeRelatedArtifacts != that.includeRelatedArtifacts ) return false;
+        if ( recurse != that.recurse ) return false;
+        if ( !projectId.equals( that.projectId ) ) return false;
+        if ( !version.equals( that.version ) ) return false;
+        if ( !artifactVersion.equals( that.artifactVersion ) ) return false;
+        if ( !artifactId.equals( that.artifactId ) ) return false;
+        if ( !namespace.equals( that.namespace ) ) return false;
+        if ( !type.equals( that.type ) ) return false;
+        if ( !classifier.equals( that.classifier ) ) return false;
+        if ( !extension.equals( that.extension ) ) return false;
+        return attributes != null ? attributes.equals( that.attributes ) : that.attributes == null;
+    }
+
+    @Override
+    public int hashCode( )
+    {
+        int result = projectId.hashCode( );
+        result = 31 * result + version.hashCode( );
+        result = 31 * result + artifactVersion.hashCode( );
+        result = 31 * result + artifactId.hashCode( );
+        result = 31 * result + namespace.hashCode( );
+        result = 31 * result + type.hashCode( );
+        result = 31 * result + classifier.hashCode( );
+        result = 31 * result + extension.hashCode( );
+        result = 31 * result + ( attributes != null ? attributes.hashCode( ) : 0 );
+        result = 31 * result + ( includeRelatedArtifacts ? 1 : 0 );
+        result = 31 * result + ( recurse ? 1 : 0 );
+        return result;
+    }
 }
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/mock/ManagedRepositoryContentMock.java b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/mock/ManagedRepositoryContentMock.java
index 9eb4b81..21230c8 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/mock/ManagedRepositoryContentMock.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/mock/ManagedRepositoryContentMock.java
@@ -133,7 +133,7 @@ public class ManagedRepositoryContentMock implements ManagedRepositoryContent
     }
 
     @Override
-    public Stream<? extends Artifact> getArtifactStream( ItemSelector selector ) throws ContentAccessException
+    public Stream<? extends Artifact> newArtifactStream( ItemSelector selector ) throws ContentAccessException
     {
         return null;
     }
@@ -169,7 +169,7 @@ public class ManagedRepositoryContentMock implements ManagedRepositoryContent
     }
 
     @Override
-    public Stream<? extends Artifact> getArtifactStream( ContentItem item ) throws ContentAccessException
+    public Stream<? extends Artifact> newArtifactStream( ContentItem item ) throws ContentAccessException
     {
         return null;
     }
diff --git a/archiva-modules/archiva-base/archiva-repository-scanner/src/test/java/org/apache/archiva/repository/scanner/mock/ManagedRepositoryContentMock.java b/archiva-modules/archiva-base/archiva-repository-scanner/src/test/java/org/apache/archiva/repository/scanner/mock/ManagedRepositoryContentMock.java
index 6db475b..b79e47e 100644
--- a/archiva-modules/archiva-base/archiva-repository-scanner/src/test/java/org/apache/archiva/repository/scanner/mock/ManagedRepositoryContentMock.java
+++ b/archiva-modules/archiva-base/archiva-repository-scanner/src/test/java/org/apache/archiva/repository/scanner/mock/ManagedRepositoryContentMock.java
@@ -134,7 +134,7 @@ public class ManagedRepositoryContentMock implements ManagedRepositoryContent
     }
 
     @Override
-    public Stream<? extends Artifact> getArtifactStream( ItemSelector selector ) throws ContentAccessException
+    public Stream<? extends Artifact> newArtifactStream( ItemSelector selector ) throws ContentAccessException
     {
         return null;
     }
@@ -170,7 +170,7 @@ public class ManagedRepositoryContentMock implements ManagedRepositoryContent
     }
 
     @Override
-    public Stream<? extends Artifact> getArtifactStream( ContentItem item ) throws ContentAccessException
+    public Stream<? extends Artifact> newArtifactStream( ContentItem item ) throws ContentAccessException
     {
         return null;
     }
diff --git a/archiva-modules/archiva-maven/archiva-maven-proxy/src/test/java/org/apache/archiva/repository/mock/ManagedRepositoryContentMock.java b/archiva-modules/archiva-maven/archiva-maven-proxy/src/test/java/org/apache/archiva/repository/mock/ManagedRepositoryContentMock.java
index 23300ff..17069d5 100644
--- a/archiva-modules/archiva-maven/archiva-maven-proxy/src/test/java/org/apache/archiva/repository/mock/ManagedRepositoryContentMock.java
+++ b/archiva-modules/archiva-maven/archiva-maven-proxy/src/test/java/org/apache/archiva/repository/mock/ManagedRepositoryContentMock.java
@@ -138,7 +138,7 @@ public class ManagedRepositoryContentMock implements ManagedRepositoryContent
     }
 
     @Override
-    public Stream<? extends Artifact> getArtifactStream( ItemSelector selector ) throws ContentAccessException
+    public Stream<? extends Artifact> newArtifactStream( ItemSelector selector ) throws ContentAccessException
     {
         return null;
     }
@@ -174,7 +174,7 @@ public class ManagedRepositoryContentMock implements ManagedRepositoryContent
     }
 
     @Override
-    public Stream<? extends Artifact> getArtifactStream( ContentItem item ) throws ContentAccessException
+    public Stream<? extends Artifact> newArtifactStream( ContentItem item ) throws ContentAccessException
     {
         return null;
     }
diff --git a/archiva-modules/archiva-maven/archiva-maven-repository/src/main/java/org/apache/archiva/repository/maven/content/ManagedDefaultRepositoryContent.java b/archiva-modules/archiva-maven/archiva-maven-repository/src/main/java/org/apache/archiva/repository/maven/content/ManagedDefaultRepositoryContent.java
index de7de97..427a72b 100644
--- a/archiva-modules/archiva-maven/archiva-maven-repository/src/main/java/org/apache/archiva/repository/maven/content/ManagedDefaultRepositoryContent.java
+++ b/archiva-modules/archiva-maven/archiva-maven-repository/src/main/java/org/apache/archiva/repository/maven/content/ManagedDefaultRepositoryContent.java
@@ -24,8 +24,6 @@ import org.apache.archiva.common.utils.VersionUtil;
 import org.apache.archiva.configuration.FileTypes;
 import org.apache.archiva.metadata.maven.MavenMetadataReader;
 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
-import org.apache.archiva.repository.maven.metadata.storage.ArtifactMappingProvider;
-import org.apache.archiva.repository.maven.metadata.storage.DefaultArtifactMappingProvider;
 import org.apache.archiva.model.ArchivaArtifact;
 import org.apache.archiva.model.ArtifactReference;
 import org.apache.archiva.model.ProjectReference;
@@ -43,10 +41,13 @@ import org.apache.archiva.repository.content.ItemSelector;
 import org.apache.archiva.repository.content.Namespace;
 import org.apache.archiva.repository.content.Project;
 import org.apache.archiva.repository.content.Version;
+import org.apache.archiva.repository.content.base.ArchivaItemSelector;
 import org.apache.archiva.repository.content.base.ArchivaNamespace;
 import org.apache.archiva.repository.content.base.ArchivaProject;
 import org.apache.archiva.repository.content.base.ArchivaVersion;
 import org.apache.archiva.repository.content.base.builder.ArtifactOptBuilder;
+import org.apache.archiva.repository.maven.metadata.storage.ArtifactMappingProvider;
+import org.apache.archiva.repository.maven.metadata.storage.DefaultArtifactMappingProvider;
 import org.apache.archiva.repository.storage.RepositoryStorage;
 import org.apache.archiva.repository.storage.StorageAsset;
 import org.apache.archiva.repository.storage.util.StorageUtil;
@@ -55,6 +56,7 @@ import org.apache.commons.lang3.StringUtils;
 
 import javax.inject.Inject;
 import javax.inject.Named;
+import javax.naming.Name;
 import java.io.IOException;
 import java.net.URI;
 import java.nio.file.Files;
@@ -70,6 +72,7 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
 
 /**
  * ManagedDefaultRepositoryContent
@@ -511,48 +514,6 @@ public class ManagedDefaultRepositoryContent
         return getStorage( ).getAsset( path.toString( ) );
     }
 
-    /*
-     * File filter to select certain artifacts using the selector data.
-     */
-    private Predicate<StorageAsset> getFileFilterFromSelector(final ItemSelector selector) {
-        Predicate<StorageAsset> p = a -> a.isLeaf( );
-        StringBuilder fileNamePattern = new StringBuilder("^" );
-        if (selector.hasArtifactId()) {
-            fileNamePattern.append( Pattern.quote(selector.getArtifactId( )) ).append("-");
-        } else {
-            fileNamePattern.append("[A-Za-z0-9_\\-.]+-");
-        }
-        if (selector.hasArtifactVersion()) {
-            fileNamePattern.append( Pattern.quote(selector.getArtifactVersion( )) );
-        } else  {
-            fileNamePattern.append( "[A-Za-z0-9_\\-.]+" );
-        }
-        String classifier = selector.hasClassifier( ) ? selector.getClassifier( ) :
-            ( selector.hasType( ) ? MavenContentHelper.getClassifierFromType( selector.getType( ) ) : null );
-        if (classifier != null)
-        {
-            if ( "*".equals( classifier ) )
-            {
-                fileNamePattern.append( "-[A-Za-z0-9]+\\." );
-            }
-            else
-            {
-                fileNamePattern.append("-").append( Pattern.quote( classifier ) ).append( "\\." );
-            }
-        } else {
-            fileNamePattern.append( "\\." );
-        }
-        String extension = selector.hasExtension( ) ? selector.getExtension( ) :
-            ( selector.hasType( ) ? MavenContentHelper.getArtifactExtension( selector ) : null );
-        if (extension != null) {
-            fileNamePattern.append( Pattern.quote( extension ) );
-        } else {
-            fileNamePattern.append( ".*" );
-        }
-        final Pattern pattern = Pattern.compile( fileNamePattern.toString() );
-        return p.and( a -> pattern.matcher( a.getName( ) ).matches());
-    }
-
 
     /**
      * Returns all the subdirectories of the given namespace directory as project.
@@ -620,41 +581,210 @@ public class ManagedDefaultRepositoryContent
     }
 
 
-    /*
-    TBD
- */
+    /**
+     * See {@link #newArtifactStream(ItemSelector)}. This method collects the stream into a list.
+     *
+     * @param selector the selector for the artifacts
+     * @return the list of artifacts
+     * @throws ContentAccessException if the access to the underlying filesystem failed
+     */
     @Override
     public List<? extends Artifact> getArtifacts( ItemSelector selector ) throws ContentAccessException
     {
-        return null;
+        try(Stream<? extends Artifact> stream = newArtifactStream( selector )) {
+            return stream.collect( Collectors.toList());
+        }
     }
 
+
     /*
-        TBD
+     * File filter to select certain artifacts using the selector data.
+     */
+    private Predicate<StorageAsset> getFileFilterFromSelector(final ItemSelector selector) {
+        Predicate<StorageAsset> p = a -> a.isLeaf( );
+        StringBuilder fileNamePattern = new StringBuilder("^" );
+        if (selector.hasArtifactId()) {
+            fileNamePattern.append( Pattern.quote(selector.getArtifactId( )) ).append("-");
+        } else {
+            fileNamePattern.append("[A-Za-z0-9_\\-.]+-");
+        }
+        if (selector.hasArtifactVersion()) {
+            fileNamePattern.append( Pattern.quote(selector.getArtifactVersion( )) );
+        } else  {
+            fileNamePattern.append( "[A-Za-z0-9_\\-.]+" );
+        }
+        String classifier = selector.hasClassifier( ) ? selector.getClassifier( ) :
+            ( selector.hasType( ) ? MavenContentHelper.getClassifierFromType( selector.getType( ) ) : null );
+        if (classifier != null)
+        {
+            if ( "*".equals( classifier ) )
+            {
+                fileNamePattern.append( "-[A-Za-z0-9]+\\." );
+            }
+            else
+            {
+                fileNamePattern.append("-").append( Pattern.quote( classifier ) ).append( "\\." );
+            }
+        } else {
+            fileNamePattern.append( "\\." );
+        }
+        String extension = selector.hasExtension( ) ? selector.getExtension( ) :
+            ( selector.hasType( ) ? MavenContentHelper.getArtifactExtension( selector ) : null );
+        if (extension != null) {
+            if (selector.includeRelatedArtifacts())
+            {
+                fileNamePattern.append( Pattern.quote( extension ) ).append("(\\.[A-Za-z0-9]+)?");
+            } else {
+                fileNamePattern.append( Pattern.quote( extension ) );
+            }
+        } else {
+            fileNamePattern.append( "[A-Za-z0-9]+" );
+        }
+        final Pattern pattern = Pattern.compile( fileNamePattern.toString() );
+        return p.and( a -> pattern.matcher( a.getName( ) ).matches());
+    }
+
+
+    /**
+     * Returns the artifacts. The number of artifacts returned depend on the selector.
+     * If the selector sets the flag {@link ItemSelector#includeRelatedArtifacts()} to <code>true</code>,
+     * additional to the matching artifacts, related artifacts like hash values or signatures are included in the artifact
+     * stream.
+     * If the selector sets the flag {@link ItemSelector#recurse()} to <code>true</code>, artifacts of the given
+     * namespace and from all sub namespaces that start with the given namespace are returned.
+     * <ul>
+     *     <li>If only a namespace is given, all artifacts with the given namespace or starting with the given
+     *     namespace (see {@link ItemSelector#recurse()} are returned.</li>
+     *     <li>If a namespace and a project id, or artifact id is given, the artifacts of all versions of the given
+     *     namespace and project are returned.</li>
+     *     <li>If a namespace and a project id or artifact id and a version is given, the artifacts of the given
+     *     version are returned</li>
+     * </ul>
+     *
+     * There is no determinate order of the elements in the stream.
+     *
+     * Returned streams are auto closable and should be used in a try-with-resources statement.
+     *
+     * @param selector the item selector
+     * @throws ContentAccessException if the access to the underlying filesystem failed
      */
     @Override
-    public Stream<? extends Artifact> getArtifactStream( ItemSelector selector ) throws ContentAccessException
+    public Stream<? extends Artifact> newArtifactStream( ItemSelector selector ) throws ContentAccessException
     {
-        return null;
+        String projectId = selector.hasProjectId( ) ? selector.getProjectId( ) : ( selector.hasArtifactId( ) ? selector.getArtifactId( )
+            : null );
+        final Predicate<StorageAsset> filter = getFileFilterFromSelector( selector );
+        if (projectId!=null && selector.hasVersion()) {
+            return getAsset( selector.getNamespace( ), projectId, selector.getVersion( ) )
+                .list( ).stream( ).filter( filter )
+                .map( this::getArtifactFromPath );
+        } else if (projectId!=null) {
+            final StorageAsset projDir = getAsset( selector.getNamespace( ), projectId );
+            return projDir.list( ).stream( ).filter( StorageAsset::isContainer )
+                .map( StorageAsset::list )
+                .flatMap( List::stream )
+                .filter( filter )
+                .map( this::getArtifactFromPath );
+        } else
+        {
+            StorageAsset namespaceDir = getAsset( selector.getNamespace( ) );
+            if (selector.recurse())
+            {
+                return StorageUtil.newAssetStream( namespaceDir, true )
+                    .filter( filter )
+                        .map( this::getArtifactFromPath );
+
+            } else {
+                return namespaceDir.list( ).stream( )
+                    .filter( StorageAsset::isContainer )
+                    .map( StorageAsset::list )
+                    .flatMap( List::stream )
+                    .filter( StorageAsset::isContainer )
+                    .map( StorageAsset::list )
+                    .flatMap( List::stream )
+                    .filter( filter )
+                    .map(this::getArtifactFromPath);
+            }
+        }
     }
 
-    /*
-            TBD
-         */
+    /**
+     * Same as {@link #newArtifactStream(ContentItem)} but returns the collected stream as list.
+     *
+     * @param item the item the parent item
+     * @return the list of artifacts or a empty list of no artifacts where found
+     */
     @Override
     public List<? extends Artifact> getArtifacts( ContentItem item )
     {
-        return null;
+        try(Stream<? extends Artifact> stream = newArtifactStream( item )) {
+            return stream.collect( Collectors.toList());
+        }
     }
 
+    /**
+     * Returns all artifacts
+     * @param item
+     * @return
+     * @throws ContentAccessException
+     */
+    public Stream<? extends Artifact> newArtifactStream( Namespace item ) throws ContentAccessException
+    {
+        return newArtifactStream( ArchivaItemSelector.builder( ).withNamespace( item.getNamespace( ) ).build( ) );
+    }
 
-    /*
-        TBD
+    public Stream<? extends Artifact> newArtifactStream( Project item ) throws ContentAccessException
+    {
+        return newArtifactStream( ArchivaItemSelector.builder( ).withNamespace( item.getNamespace( ).getNamespace() )
+            .withProjectId( item.getId() ).build( ) );
+    }
+
+    public Stream<? extends Artifact> newArtifactStream( Version item ) throws ContentAccessException
+    {
+        return newArtifactStream( ArchivaItemSelector.builder( ).withNamespace( item.getProject().getNamespace( ).getNamespace() )
+            .withProjectId( item.getProject().getId() )
+            .withVersion( item.getVersion() ).build( ) );
+    }
+
+    /**
+     * Returns all related artifacts that match the given artifact. That means all artifacts that have
+     * the same filename plus an additional extension, e.g. ${fileName}.sha2
+     * @param item the artifact
+     * @return the stream of artifacts
+     * @throws ContentAccessException
+     */
+    public Stream<? extends Artifact> newArtifactStream( Artifact item ) throws ContentAccessException
+    {
+        final Version v = item.getVersion( );
+        final String fileName = item.getFileName( );
+        final Predicate<StorageAsset> filter = ( StorageAsset a ) ->
+            a.getName( ).startsWith( fileName + "." );
+        return v.getAsset( ).list( ).stream( ).filter( filter )
+            .map( a -> getArtifactFromPath( a ) );
+    }
+    /**
+     * Returns the stream of artifacts that are children of the given item.
+     *
+     * @param item the item from where the artifacts should be returned
+     * @return
+     * @throws ContentAccessException
      */
     @Override
-    public Stream<? extends Artifact> getArtifactStream( ContentItem item )
+    public Stream<? extends Artifact> newArtifactStream( ContentItem item ) throws ContentAccessException
     {
-        return null;
+        if (item instanceof Namespace) {
+            return newArtifactStream( ( (Namespace) item ) );
+        } else if (item instanceof Project) {
+            return newArtifactStream( (Project) item );
+        } else if (item instanceof Version) {
+            return newArtifactStream( (Version) item );
+        } else if (item instanceof Artifact) {
+            return newArtifactStream( (Artifact) item );
+        } else
+        {
+            log.warn( "newArtifactStream for unsupported item requested: {}", item.getClass( ).getName( ) );
+            return Stream.empty( );
+        }
     }
 
     /**
@@ -1092,7 +1222,7 @@ public class ManagedDefaultRepositoryContent
     public Set<String> getVersions( VersionedReference reference )
         throws ContentNotFoundException, ContentAccessException, LayoutException
     {
-        try(Stream<ArtifactReference> stream = getArtifactStream( reference ))
+        try(Stream<ArtifactReference> stream = newArtifactStream( reference ))
         {
             return stream.filter( Objects::nonNull )
                 .map( ar -> ar.getVersion( ) )
@@ -1230,7 +1360,7 @@ public class ManagedDefaultRepositoryContent
     private ArtifactReference getFirstArtifact( VersionedReference reference )
         throws ContentNotFoundException, LayoutException, IOException
     {
-        try(Stream<ArtifactReference> stream = getArtifactStream( reference ))
+        try(Stream<ArtifactReference> stream = newArtifactStream( reference ))
         {
             return stream.findFirst( ).orElse( null );
         } catch (RuntimeException e) {
@@ -1238,7 +1368,7 @@ public class ManagedDefaultRepositoryContent
         }
     }
 
-    private Stream<ArtifactReference> getArtifactStream(VersionedReference reference) throws ContentNotFoundException, LayoutException, IOException {
+    private Stream<ArtifactReference> newArtifactStream( VersionedReference reference) throws ContentNotFoundException, LayoutException, IOException {
         final Path repoBase = getRepoDir( );
         String path = toMetadataPath( reference );
         Path versionDir = repoBase.resolve( path ).getParent();
@@ -1262,7 +1392,7 @@ public class ManagedDefaultRepositoryContent
 
     public List<ArtifactReference> getArtifacts(VersionedReference reference) throws ContentNotFoundException, LayoutException, ContentAccessException
     {
-        try (Stream<ArtifactReference> stream = getArtifactStream( reference ))
+        try (Stream<ArtifactReference> stream = newArtifactStream( reference ))
         {
             return stream.collect( Collectors.toList( ) );
         } catch ( IOException e )
@@ -1277,7 +1407,7 @@ public class ManagedDefaultRepositoryContent
     private boolean hasArtifact( VersionedReference reference )
 
     {
-        try(Stream<ArtifactReference> stream = getArtifactStream( reference ))
+        try(Stream<ArtifactReference> stream = newArtifactStream( reference ))
         {
             return stream.anyMatch( e -> true );
         } catch (ContentNotFoundException e) {