]> source.dussan.org Git - archiva.git/blob
427a72b3f392d8642e4e093ad8ead2e7f409b4ec
[archiva.git] /
1 package org.apache.archiva.repository.maven.content;
2
3 /*
4  * Licensed to the Apache Software Foundation (ASF) under one
5  * or more contributor license agreements.  See the NOTICE file
6  * distributed with this work for additional information
7  * regarding copyright ownership.  The ASF licenses this file
8  * to you under the Apache License, Version 2.0 (the
9  * "License"); you may not use this file except in compliance
10  * with the License.  You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  */
20
21 import org.apache.archiva.common.filelock.FileLockManager;
22 import org.apache.archiva.common.utils.FileUtils;
23 import org.apache.archiva.common.utils.VersionUtil;
24 import org.apache.archiva.configuration.FileTypes;
25 import org.apache.archiva.metadata.maven.MavenMetadataReader;
26 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
27 import org.apache.archiva.model.ArchivaArtifact;
28 import org.apache.archiva.model.ArtifactReference;
29 import org.apache.archiva.model.ProjectReference;
30 import org.apache.archiva.model.VersionedReference;
31 import org.apache.archiva.repository.ContentAccessException;
32 import org.apache.archiva.repository.ContentNotFoundException;
33 import org.apache.archiva.repository.EditableManagedRepository;
34 import org.apache.archiva.repository.LayoutException;
35 import org.apache.archiva.repository.ManagedRepository;
36 import org.apache.archiva.repository.ManagedRepositoryContent;
37 import org.apache.archiva.repository.content.Artifact;
38 import org.apache.archiva.repository.content.ContentItem;
39 import org.apache.archiva.repository.content.ItemNotFoundException;
40 import org.apache.archiva.repository.content.ItemSelector;
41 import org.apache.archiva.repository.content.Namespace;
42 import org.apache.archiva.repository.content.Project;
43 import org.apache.archiva.repository.content.Version;
44 import org.apache.archiva.repository.content.base.ArchivaItemSelector;
45 import org.apache.archiva.repository.content.base.ArchivaNamespace;
46 import org.apache.archiva.repository.content.base.ArchivaProject;
47 import org.apache.archiva.repository.content.base.ArchivaVersion;
48 import org.apache.archiva.repository.content.base.builder.ArtifactOptBuilder;
49 import org.apache.archiva.repository.maven.metadata.storage.ArtifactMappingProvider;
50 import org.apache.archiva.repository.maven.metadata.storage.DefaultArtifactMappingProvider;
51 import org.apache.archiva.repository.storage.RepositoryStorage;
52 import org.apache.archiva.repository.storage.StorageAsset;
53 import org.apache.archiva.repository.storage.util.StorageUtil;
54 import org.apache.commons.collections4.map.ReferenceMap;
55 import org.apache.commons.lang3.StringUtils;
56
57 import javax.inject.Inject;
58 import javax.inject.Named;
59 import javax.naming.Name;
60 import java.io.IOException;
61 import java.net.URI;
62 import java.nio.file.Files;
63 import java.nio.file.Path;
64 import java.nio.file.Paths;
65 import java.util.Collections;
66 import java.util.List;
67 import java.util.Objects;
68 import java.util.Optional;
69 import java.util.Set;
70 import java.util.function.Predicate;
71 import java.util.regex.Matcher;
72 import java.util.regex.Pattern;
73 import java.util.stream.Collectors;
74 import java.util.stream.Stream;
75 import java.util.stream.StreamSupport;
76
77 /**
78  * ManagedDefaultRepositoryContent
79  */
80 public class ManagedDefaultRepositoryContent
81     extends AbstractDefaultRepositoryContent
82     implements ManagedRepositoryContent
83 {
84
85     // attribute flag that marks version objects that point to a snapshot artifact version
86     public static final String SNAPSHOT_ARTIFACT_VERSION = "maven.snav";
87
88     private FileTypes filetypes;
89
90     public void setFileTypes(FileTypes fileTypes) {
91         this.filetypes = fileTypes;
92     }
93
94     private ManagedRepository repository;
95
96     private FileLockManager lockManager;
97
98     @Inject
99     @Named("repositoryPathTranslator#maven2")
100     private RepositoryPathTranslator pathTranslator;
101
102     @Inject
103     @Named( "metadataReader#maven" )
104     MavenMetadataReader metadataReader;
105
106     @Inject
107     @Named( "MavenContentHelper" )
108     MavenContentHelper mavenContentHelper;
109
110     public static final String SNAPSHOT = "SNAPSHOT";
111
112     public static final Pattern UNIQUE_SNAPSHOT_PATTERN = Pattern.compile( "^(SNAPSHOT|[0-9]{8}\\.[0-9]{6}-[0-9]+)(.*)" );
113     public static final Pattern CLASSIFIER_PATTERN = Pattern.compile( "^-([^.]+)(\\..*)" );
114
115     public static final Pattern TIMESTAMP_PATTERN = Pattern.compile( "^([0-9]{8})\\.([0-9]{6})$" );
116
117     public static final Pattern GENERIC_SNAPSHOT_PATTERN = Pattern.compile( "^(.*)-" + SNAPSHOT );
118
119     /**
120      * We are caching content items in a weak reference map. To avoid always recreating the
121      * the hierarchical structure.
122      * TODO: Better use a object cache? E.g. our spring cache implementation?
123      */
124     private ReferenceMap<String, Namespace> namespaceMap = new ReferenceMap<>( );
125     private ReferenceMap<StorageAsset, Project> projectMap = new ReferenceMap<>( );
126     private ReferenceMap<StorageAsset, Version> versionMap = new ReferenceMap<>( );
127     private ReferenceMap<StorageAsset, Artifact> artifactMap = new ReferenceMap<>( );
128
129     public ManagedDefaultRepositoryContent() {
130         super(Collections.singletonList( new DefaultArtifactMappingProvider() ));
131     }
132
133     public ManagedDefaultRepositoryContent(ManagedRepository repository, FileTypes fileTypes, FileLockManager lockManager) {
134         super(Collections.singletonList( new DefaultArtifactMappingProvider() ));
135         setFileTypes( fileTypes );
136         this.lockManager = lockManager;
137         setRepository( repository );
138     }
139
140     public ManagedDefaultRepositoryContent( ManagedRepository repository, List<? extends ArtifactMappingProvider> artifactMappingProviders, FileTypes fileTypes, FileLockManager lockManager )
141     {
142         super(artifactMappingProviders==null ? Collections.singletonList( new DefaultArtifactMappingProvider() ) : artifactMappingProviders);
143         setFileTypes( fileTypes );
144         this.lockManager = lockManager;
145         setRepository( repository );
146
147     }
148
149     private StorageAsset getAssetByPath(String assetPath) {
150         return getStorage( ).getAsset( assetPath );
151     }
152
153     private StorageAsset getAsset(String namespace) {
154         String namespacePath = formatAsDirectory( namespace.trim() );
155         if (StringUtils.isEmpty( namespacePath )) {
156             namespacePath = "";
157         }
158         return getAssetByPath(namespacePath);
159     }
160
161     private StorageAsset getAsset(String namespace, String project) {
162         return getAsset( namespace ).resolve( project );
163     }
164
165     private StorageAsset getAsset(String namespace, String project, String version) {
166         return getAsset( namespace, project ).resolve( version );
167     }
168
169     private StorageAsset getAsset(String namespace, String project, String version, String fileName) {
170         return getAsset( namespace, project, version ).resolve( fileName );
171     }
172
173
174     /// ************* End of new generation interface ******************
175     @Override
176     public void deleteItem( ContentItem item ) throws ItemNotFoundException, ContentAccessException
177     {
178         final Path baseDirectory = getRepoDir( );
179         final Path itemPath = item.getAsset( ).getFilePath( );
180         if ( !Files.exists( itemPath ) )
181         {
182             throw new ItemNotFoundException( "The item " + item.toString() + "does not exist in the repository " + getId( ) );
183         }
184         if ( !itemPath.toAbsolutePath().startsWith( baseDirectory.toAbsolutePath() ) )
185         {
186             log.error( "The namespace {} to delete from repository {} is not a subdirectory of the repository base.", item, getId( ) );
187             log.error( "Namespace directory: {}", itemPath );
188             log.error( "Repository directory: {}", baseDirectory );
189             throw new ContentAccessException( "Inconsistent directories found. Could not delete namespace." );
190         }
191         try
192         {
193             if (Files.isDirectory( itemPath ))
194             {
195                 FileUtils.deleteDirectory( itemPath );
196             } else {
197                 Files.deleteIfExists( itemPath );
198             }
199         }
200         catch ( IOException e )
201         {
202             log.error( "Could not delete namespace directory {}: {}", itemPath, e.getMessage( ), e );
203             throw new ContentAccessException( "Error occured while deleting namespace " + item + ": " + e.getMessage( ), e );
204         }
205     }
206
207     @Override
208     public ContentItem getItem( ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
209     {
210         if (selector.hasVersion() && selector.hasArtifactId()) {
211             return getArtifact( selector );
212         } else if (selector.hasProjectId() && selector.hasVersion()) {
213             return getVersion( selector );
214         } else if (selector.hasProjectId()) {
215             return getProject( selector );
216         } else {
217             return getNamespace( selector );
218         }
219     }
220
221     @Override
222     public Namespace getNamespace( final ItemSelector namespaceSelector ) throws ContentAccessException, IllegalArgumentException
223     {
224         return namespaceMap.computeIfAbsent( namespaceSelector.getNamespace(),
225             namespace -> {
226                 StorageAsset nsPath = getAsset( namespace );
227                 return ArchivaNamespace.withRepository( this ).withAsset( nsPath ).
228                     withNamespace( namespace ).build( );
229             });
230     }
231
232
233     @Override
234     public Project getProject( final ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
235     {
236         if (!selector.hasProjectId()) {
237             throw new IllegalArgumentException( "Project id must be set" );
238         }
239         final StorageAsset path = getAsset( selector.getNamespace( ), selector.getProjectId( ) );
240         return projectMap.computeIfAbsent( path, projectPath -> {
241             final Namespace ns = getNamespace( selector );
242             return ArchivaProject.withAsset( projectPath ).withNamespace( ns ).withId( selector.getProjectId( ) ).build( );
243         }
244         );
245     }
246
247
248     @Override
249     public Version getVersion( final ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
250     {
251         if (!selector.hasProjectId()) {
252             throw new IllegalArgumentException( "Project id must be set" );
253         }
254         if (!selector.hasVersion() ) {
255             throw new IllegalArgumentException( "Version must be set" );
256         }
257         final StorageAsset path = getAsset(selector.getNamespace(), selector.getProjectId(), selector.getVersion());
258         return versionMap.computeIfAbsent( path, versionPath -> {
259             final Project project = getProject( selector );
260             return ArchivaVersion.withAsset( path )
261                 .withProject( project )
262                 .withVersion( selector.getVersion( ) ).build();
263         } );
264     }
265
266
267
268
269
270     public Artifact createArtifact(final StorageAsset artifactPath, final ItemSelector selector,
271         final String classifier, final String extension) {
272         Version version = getVersion(selector);
273         ArtifactOptBuilder builder = org.apache.archiva.repository.content.base.ArchivaArtifact.withAsset( artifactPath )
274             .withVersion( version )
275             .withId( selector.getArtifactId( ) )
276             .withArtifactVersion( mavenContentHelper.getArtifactVersion( artifactPath, selector ) )
277             .withClassifier( classifier );
278         if (selector.hasType()) {
279             builder.withType( selector.getType( ) );
280         }
281         return builder.build( );
282     }
283
284     public Namespace getNamespaceFromArtifactPath( final StorageAsset artifactPath) {
285         final StorageAsset namespacePath = artifactPath.getParent( ).getParent( ).getParent( );
286         final String namespace = MavenContentHelper.getNamespaceFromNamespacePath( namespacePath );
287         return namespaceMap.computeIfAbsent( namespace,
288             myNamespace -> ArchivaNamespace.withRepository( this )
289                 .withAsset( namespacePath )
290                 .withNamespace( namespace )
291                 .build( ) );
292     }
293
294     private Project getProjectFromArtifactPath( final StorageAsset artifactPath) {
295         final StorageAsset projectPath = artifactPath.getParent( ).getParent( );
296         return projectMap.computeIfAbsent( projectPath,
297             myProjectPath -> ArchivaProject.withAsset( projectPath )
298                 .withNamespace( getNamespaceFromArtifactPath( artifactPath ) )
299                 .withId( projectPath.getName( ) ).build( )
300         );
301     }
302
303     private Version getVersionFromArtifactPath( final StorageAsset artifactPath) {
304         final StorageAsset versionPath = artifactPath.getParent( );
305         return versionMap.computeIfAbsent( versionPath,
306             myVersionPath -> ArchivaVersion.withAsset( versionPath )
307                 .withProject( getProjectFromArtifactPath( artifactPath ) )
308                 .withVersion( versionPath.getName( ) ).build( ) );
309     }
310
311     private Artifact getArtifactFromPath(final StorageAsset artifactPath) {
312         final Version version = getVersionFromArtifactPath( artifactPath );
313         final ArtifactInfo info  = getArtifactInfoFromPath( version.getVersion(), artifactPath );
314         return artifactMap.computeIfAbsent( artifactPath, myArtifactPath ->
315             org.apache.archiva.repository.content.base.ArchivaArtifact.withAsset( artifactPath )
316                 .withVersion( version )
317                 .withId( info.id )
318                 .withClassifier( info.classifier )
319                 .withRemainder( info.remainder )
320                 .withType( info.type )
321                 .withArtifactVersion( info.version )
322                 .withContentType( info.contentType )
323                 .build( )
324         );
325     }
326
327     private ContentItem getItemFromPath(final StorageAsset itemPath) {
328         if (itemPath.isLeaf()) {
329             return getArtifactFromPath( itemPath );
330         } else {
331             if (versionMap.containsKey( itemPath )) {
332                 return versionMap.get( itemPath );
333             }
334             if (projectMap.containsKey( itemPath )) {
335                 return projectMap.get( itemPath );
336             }
337             String ns = MavenContentHelper.getNamespaceFromNamespacePath( itemPath );
338             if (namespaceMap.containsKey( ns )) {
339                 return namespaceMap.get( ns );
340             }
341             // No cached item, so we have to gather more information:
342             // Check for version directory (contains at least a pom or metadata file)
343             if (itemPath.list( ).stream( ).map(a -> a.getName().toLowerCase()).anyMatch( n ->
344                 n.endsWith( ".pom" )
345                 || n.startsWith( "maven-metadata" )
346             )) {
347                 return versionMap.computeIfAbsent( itemPath,
348                     myVersionPath -> ArchivaVersion.withAsset( itemPath )
349                         .withProject( (Project)getItemFromPath( itemPath.getParent() ) )
350                         .withVersion( itemPath.getName() ).build());
351             } else {
352                 // We have to dig further and find the next directory with a pom
353                 Optional<StorageAsset> foundFile = StorageUtil.newAssetStream( itemPath )
354                     .filter( a -> a.getName().toLowerCase().endsWith( ".pom" )
355                         || a.getName().toLowerCase().startsWith( "maven-metadata" ) )
356                     .findFirst( );
357                 if (foundFile.isPresent())
358                 {
359                     int level = 0;
360                     StorageAsset current = foundFile.get( );
361                     while (current.hasParent() && !current.equals(itemPath)) {
362                         level++;
363                         current = current.getParent( );
364                     }
365                     // Project path if it is one level up from the found file
366                     if (level==2) {
367                         return projectMap.computeIfAbsent( itemPath,
368                             myItemPath -> getProjectFromArtifactPath( foundFile.get( ) ) );
369                     } else {
370                         // All other paths are treated as namespace
371                         return namespaceMap.computeIfAbsent( ns,
372                             myNamespace -> ArchivaNamespace.withRepository( this )
373                                 .withAsset( itemPath )
374                                 .withNamespace( ns )
375                                 .build( ) );
376                     }
377                 } else {
378                     // Don't know what to do with it, so we treat it as namespace path
379                     return namespaceMap.computeIfAbsent( ns,
380                         myNamespace -> ArchivaNamespace.withRepository( this )
381                             .withAsset( itemPath )
382                             .withNamespace( ns )
383                             .build( ) );
384                 }
385
386             }
387         }
388     }
389
390     // Simple object to hold artifact information
391     private class ArtifactInfo  {
392         private String id;
393         private String version;
394         private String extension;
395         private String remainder;
396         private String type;
397         private String classifier;
398         private String contentType;
399         private StorageAsset asset;
400     }
401
402     private ArtifactInfo getArtifactInfoFromPath(String genericVersion, StorageAsset path) {
403         final ArtifactInfo info = new ArtifactInfo( );
404         info.asset = path;
405         info.id = path.getParent( ).getParent( ).getName( );
406         final String fileName = path.getName( );
407         if ( genericVersion.endsWith( "-" + SNAPSHOT ) )
408         {
409             String baseVersion = StringUtils.substringBeforeLast( genericVersion, "-" + SNAPSHOT );
410             String prefix = info.id+"-"+baseVersion+"-";
411             if (fileName.startsWith( prefix ))
412             {
413                 String versionPostfix = StringUtils.removeStart( fileName, prefix );
414                 Matcher matcher = UNIQUE_SNAPSHOT_PATTERN.matcher( versionPostfix );
415                 if (matcher.matches()) {
416                     info.version = baseVersion + "-" + matcher.group( 1 );
417                     String newPrefix = prefix + info.version;
418                     if (fileName.startsWith( newPrefix ))
419                     {
420                         String classPostfix = StringUtils.removeStart( fileName, newPrefix );
421                         Matcher cMatch = CLASSIFIER_PATTERN.matcher( classPostfix );
422                         if (cMatch.matches()) {
423                             info.classifier = cMatch.group( 1 );
424                             info.remainder = cMatch.group( 2 );
425                         } else {
426                             info.classifier = "";
427                             info.remainder = classPostfix;
428                         }
429                     } else {
430                         log.error( "Artifact does not match the maven name pattern {}", path );
431                         info.classifier = "";
432                         info.remainder = StringUtils.substringAfter( fileName, prefix );
433                     }
434                 } else {
435                     log.error( "Artifact does not match the snapshot version pattern {}", path );
436                     info.version = "";
437                     info.classifier = "";
438                     info.remainder = StringUtils.substringAfter( fileName, prefix );
439                 }
440             } else {
441                 log.error( "Artifact does not match the maven name pattern: {}", path );
442                 info.version = "";
443                 info.classifier = "";
444                 info.remainder = StringUtils.substringAfterLast( fileName, "." );
445             }
446         } else {
447             String prefix = info.id+"-"+genericVersion;
448             if (fileName.startsWith( prefix ))
449             {
450                 info.version=genericVersion;
451                 String classPostfix = StringUtils.removeStart( fileName, prefix );
452                 Matcher cMatch = CLASSIFIER_PATTERN.matcher( classPostfix );
453                 if (cMatch.matches()) {
454                     info.classifier = cMatch.group( 1 );
455                     info.remainder = cMatch.group( 2 );
456                 } else {
457                     info.classifier = "";
458                     info.remainder = classPostfix;
459                 }
460             } else {
461                 log.error( "Artifact does not match the version pattern {}", path );
462                 info.version = "";
463                 info.classifier = "";
464                 info.remainder = StringUtils.substringAfterLast( fileName, "." );
465             }
466         }
467         info.extension = StringUtils.substringAfterLast( fileName, "." );
468         info.type = MavenContentHelper.getTypeFromClassifierAndExtension( info.classifier, info.extension );
469         try {
470             info.contentType = Files.probeContentType( path.getFilePath( ) );
471         } catch (IOException e) {
472             info.contentType = "";
473             //
474         }
475         return info;
476
477     }
478
479     @Override
480     public Artifact getArtifact( final ItemSelector selector ) throws ContentAccessException
481     {
482         if (!selector.hasProjectId( )) {
483             throw new IllegalArgumentException( "Project id must be set" );
484         }
485         if (!selector.hasVersion( )) {
486             throw new IllegalArgumentException( "Version must be set" );
487         }
488         if (!selector.hasArtifactId( )) {
489             throw new IllegalArgumentException( "Artifact Id must be set" );
490         }
491         final StorageAsset artifactDir = getAsset(selector.getNamespace(), selector.getProjectId(),
492             selector.getVersion());
493         final String artifactVersion = mavenContentHelper.getArtifactVersion( artifactDir, selector );
494         final String classifier = MavenContentHelper.getClassifier( selector );
495         final String extension = MavenContentHelper.getArtifactExtension( selector );
496         final String artifactId = StringUtils.isEmpty( selector.getArtifactId( ) ) ? selector.getProjectId( ) : selector.getArtifactId( );
497         final String fileName = MavenContentHelper.getArtifactFileName( artifactId, artifactVersion, classifier, extension );
498         final StorageAsset path = getAsset( selector.getNamespace( ), selector.getProjectId( ),
499             selector.getVersion( ), fileName );
500         return artifactMap.computeIfAbsent( path, artifactPath -> createArtifact( path, selector, classifier, extension ) );
501     }
502
503     private StorageAsset getBasePathFromSelector(ItemSelector selector) {
504         StringBuilder path = new StringBuilder( );
505         if (selector.hasNamespace()) {
506             path.append(String.join( "/", getNamespace( selector ).getNamespacePath( ) ));
507         }
508         if (selector.hasProjectId()) {
509             path.append( "/" ).append( selector.getProjectId( ) );
510         }
511         if (selector.hasVersion()) {
512             path.append( "/" ).append( selector.getVersion( ) );
513         }
514         return getStorage( ).getAsset( path.toString( ) );
515     }
516
517
518     /**
519      * Returns all the subdirectories of the given namespace directory as project.
520      */
521     @Override
522     public List<? extends Project> getProjects( Namespace namespace )
523     {
524         return namespace.getAsset( ).list( ).stream( )
525             .filter( a -> a.isContainer( ) )
526             .map( a -> getProjectFromArtifactPath( a ) )
527             .collect( Collectors.toList());
528     }
529
530     @Override
531     public List<? extends Project> getProjects( ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
532     {
533         return getProjects( getNamespace( selector ) );
534     }
535
536     /**
537      * Returns a version object for each directory that is a direct child of the project directory.
538      * @param project the project for which the versions should be returned
539      * @return the list of versions or a empty list, if not version was found
540      */
541     @Override
542     public List<? extends Version> getVersions( final Project project )
543     {
544         StorageAsset asset = getAsset( project.getNamespace( ).getNamespace( ), project.getId( ) );
545         return asset.list( ).stream( ).filter( a -> a.isContainer( ) )
546             .map( a -> ArchivaVersion.withAsset( a )
547                 .withProject( project )
548                 .withVersion( a.getName() ).build() )
549             .collect( Collectors.toList( ) );
550     }
551
552     /**
553      * If the selector specifies a version, all artifact versions are returned, which means for snapshot
554      * versions the artifact versions are returned too.
555      *
556      * @param selector the item selector. At least namespace and projectId must be set.
557      * @return the list of version objects or a empty list, if the selector does not match a version
558      * @throws ContentAccessException if the access to the underlying backend failed
559      * @throws IllegalArgumentException if the selector has no projectId specified
560      */
561     @Override
562     public List<? extends Version> getVersions( final ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
563     {
564         if (StringUtils.isEmpty( selector.getProjectId() )) {
565             log.error( "Bad item selector for version list: {}", selector );
566             throw new IllegalArgumentException( "Project ID not set, while retrieving versions." );
567         }
568         final Project project = getProject( selector );
569         if (selector.hasVersion()) {
570             final StorageAsset asset = getAsset( selector.getNamespace( ), selector.getProjectId( ), selector.getVersion( ) );
571             return asset.list( ).stream( ).map( a -> getArtifactInfoFromPath( selector.getVersion( ), a ) )
572                 .filter( ai -> StringUtils.isNotEmpty( ai.version ) )
573                 .map( v -> ArchivaVersion.withAsset( v.asset.getParent() )
574                     .withProject( project ).withVersion( v.version )
575                     .withAttribute(SNAPSHOT_ARTIFACT_VERSION,"true").build() )
576                 .distinct()
577                 .collect( Collectors.toList( ) );
578         } else {
579             return getVersions( project );
580         }
581     }
582
583
584     /**
585      * See {@link #newArtifactStream(ItemSelector)}. This method collects the stream into a list.
586      *
587      * @param selector the selector for the artifacts
588      * @return the list of artifacts
589      * @throws ContentAccessException if the access to the underlying filesystem failed
590      */
591     @Override
592     public List<? extends Artifact> getArtifacts( ItemSelector selector ) throws ContentAccessException
593     {
594         try(Stream<? extends Artifact> stream = newArtifactStream( selector )) {
595             return stream.collect( Collectors.toList());
596         }
597     }
598
599
600     /*
601      * File filter to select certain artifacts using the selector data.
602      */
603     private Predicate<StorageAsset> getFileFilterFromSelector(final ItemSelector selector) {
604         Predicate<StorageAsset> p = a -> a.isLeaf( );
605         StringBuilder fileNamePattern = new StringBuilder("^" );
606         if (selector.hasArtifactId()) {
607             fileNamePattern.append( Pattern.quote(selector.getArtifactId( )) ).append("-");
608         } else {
609             fileNamePattern.append("[A-Za-z0-9_\\-.]+-");
610         }
611         if (selector.hasArtifactVersion()) {
612             fileNamePattern.append( Pattern.quote(selector.getArtifactVersion( )) );
613         } else  {
614             fileNamePattern.append( "[A-Za-z0-9_\\-.]+" );
615         }
616         String classifier = selector.hasClassifier( ) ? selector.getClassifier( ) :
617             ( selector.hasType( ) ? MavenContentHelper.getClassifierFromType( selector.getType( ) ) : null );
618         if (classifier != null)
619         {
620             if ( "*".equals( classifier ) )
621             {
622                 fileNamePattern.append( "-[A-Za-z0-9]+\\." );
623             }
624             else
625             {
626                 fileNamePattern.append("-").append( Pattern.quote( classifier ) ).append( "\\." );
627             }
628         } else {
629             fileNamePattern.append( "\\." );
630         }
631         String extension = selector.hasExtension( ) ? selector.getExtension( ) :
632             ( selector.hasType( ) ? MavenContentHelper.getArtifactExtension( selector ) : null );
633         if (extension != null) {
634             if (selector.includeRelatedArtifacts())
635             {
636                 fileNamePattern.append( Pattern.quote( extension ) ).append("(\\.[A-Za-z0-9]+)?");
637             } else {
638                 fileNamePattern.append( Pattern.quote( extension ) );
639             }
640         } else {
641             fileNamePattern.append( "[A-Za-z0-9]+" );
642         }
643         final Pattern pattern = Pattern.compile( fileNamePattern.toString() );
644         return p.and( a -> pattern.matcher( a.getName( ) ).matches());
645     }
646
647
648     /**
649      * Returns the artifacts. The number of artifacts returned depend on the selector.
650      * If the selector sets the flag {@link ItemSelector#includeRelatedArtifacts()} to <code>true</code>,
651      * additional to the matching artifacts, related artifacts like hash values or signatures are included in the artifact
652      * stream.
653      * If the selector sets the flag {@link ItemSelector#recurse()} to <code>true</code>, artifacts of the given
654      * namespace and from all sub namespaces that start with the given namespace are returned.
655      * <ul>
656      *     <li>If only a namespace is given, all artifacts with the given namespace or starting with the given
657      *     namespace (see {@link ItemSelector#recurse()} are returned.</li>
658      *     <li>If a namespace and a project id, or artifact id is given, the artifacts of all versions of the given
659      *     namespace and project are returned.</li>
660      *     <li>If a namespace and a project id or artifact id and a version is given, the artifacts of the given
661      *     version are returned</li>
662      * </ul>
663      *
664      * There is no determinate order of the elements in the stream.
665      *
666      * Returned streams are auto closable and should be used in a try-with-resources statement.
667      *
668      * @param selector the item selector
669      * @throws ContentAccessException if the access to the underlying filesystem failed
670      */
671     @Override
672     public Stream<? extends Artifact> newArtifactStream( ItemSelector selector ) throws ContentAccessException
673     {
674         String projectId = selector.hasProjectId( ) ? selector.getProjectId( ) : ( selector.hasArtifactId( ) ? selector.getArtifactId( )
675             : null );
676         final Predicate<StorageAsset> filter = getFileFilterFromSelector( selector );
677         if (projectId!=null && selector.hasVersion()) {
678             return getAsset( selector.getNamespace( ), projectId, selector.getVersion( ) )
679                 .list( ).stream( ).filter( filter )
680                 .map( this::getArtifactFromPath );
681         } else if (projectId!=null) {
682             final StorageAsset projDir = getAsset( selector.getNamespace( ), projectId );
683             return projDir.list( ).stream( ).filter( StorageAsset::isContainer )
684                 .map( StorageAsset::list )
685                 .flatMap( List::stream )
686                 .filter( filter )
687                 .map( this::getArtifactFromPath );
688         } else
689         {
690             StorageAsset namespaceDir = getAsset( selector.getNamespace( ) );
691             if (selector.recurse())
692             {
693                 return StorageUtil.newAssetStream( namespaceDir, true )
694                     .filter( filter )
695                         .map( this::getArtifactFromPath );
696
697             } else {
698                 return namespaceDir.list( ).stream( )
699                     .filter( StorageAsset::isContainer )
700                     .map( StorageAsset::list )
701                     .flatMap( List::stream )
702                     .filter( StorageAsset::isContainer )
703                     .map( StorageAsset::list )
704                     .flatMap( List::stream )
705                     .filter( filter )
706                     .map(this::getArtifactFromPath);
707             }
708         }
709     }
710
711     /**
712      * Same as {@link #newArtifactStream(ContentItem)} but returns the collected stream as list.
713      *
714      * @param item the item the parent item
715      * @return the list of artifacts or a empty list of no artifacts where found
716      */
717     @Override
718     public List<? extends Artifact> getArtifacts( ContentItem item )
719     {
720         try(Stream<? extends Artifact> stream = newArtifactStream( item )) {
721             return stream.collect( Collectors.toList());
722         }
723     }
724
725     /**
726      * Returns all artifacts
727      * @param item
728      * @return
729      * @throws ContentAccessException
730      */
731     public Stream<? extends Artifact> newArtifactStream( Namespace item ) throws ContentAccessException
732     {
733         return newArtifactStream( ArchivaItemSelector.builder( ).withNamespace( item.getNamespace( ) ).build( ) );
734     }
735
736     public Stream<? extends Artifact> newArtifactStream( Project item ) throws ContentAccessException
737     {
738         return newArtifactStream( ArchivaItemSelector.builder( ).withNamespace( item.getNamespace( ).getNamespace() )
739             .withProjectId( item.getId() ).build( ) );
740     }
741
742     public Stream<? extends Artifact> newArtifactStream( Version item ) throws ContentAccessException
743     {
744         return newArtifactStream( ArchivaItemSelector.builder( ).withNamespace( item.getProject().getNamespace( ).getNamespace() )
745             .withProjectId( item.getProject().getId() )
746             .withVersion( item.getVersion() ).build( ) );
747     }
748
749     /**
750      * Returns all related artifacts that match the given artifact. That means all artifacts that have
751      * the same filename plus an additional extension, e.g. ${fileName}.sha2
752      * @param item the artifact
753      * @return the stream of artifacts
754      * @throws ContentAccessException
755      */
756     public Stream<? extends Artifact> newArtifactStream( Artifact item ) throws ContentAccessException
757     {
758         final Version v = item.getVersion( );
759         final String fileName = item.getFileName( );
760         final Predicate<StorageAsset> filter = ( StorageAsset a ) ->
761             a.getName( ).startsWith( fileName + "." );
762         return v.getAsset( ).list( ).stream( ).filter( filter )
763             .map( a -> getArtifactFromPath( a ) );
764     }
765     /**
766      * Returns the stream of artifacts that are children of the given item.
767      *
768      * @param item the item from where the artifacts should be returned
769      * @return
770      * @throws ContentAccessException
771      */
772     @Override
773     public Stream<? extends Artifact> newArtifactStream( ContentItem item ) throws ContentAccessException
774     {
775         if (item instanceof Namespace) {
776             return newArtifactStream( ( (Namespace) item ) );
777         } else if (item instanceof Project) {
778             return newArtifactStream( (Project) item );
779         } else if (item instanceof Version) {
780             return newArtifactStream( (Version) item );
781         } else if (item instanceof Artifact) {
782             return newArtifactStream( (Artifact) item );
783         } else
784         {
785             log.warn( "newArtifactStream for unsupported item requested: {}", item.getClass( ).getName( ) );
786             return Stream.empty( );
787         }
788     }
789
790     /**
791      * Checks, if the asset/file queried by the given selector exists.
792      */
793     @Override
794     public boolean hasContent( ItemSelector selector )
795     {
796         return getItem( selector ).getAsset( ).exists( );
797     }
798
799     /*
800         TBD
801      */
802     @Override
803     public void copyArtifact( Path sourceFile, ContentItem destination ) throws IllegalArgumentException
804     {
805
806     }
807
808     /**
809      * TBD
810      * @param path the path string that points to the item
811      * @return
812      * @throws LayoutException
813      */
814     @Override
815     public ContentItem toItem( String path ) throws LayoutException
816     {
817         ItemSelector selector = getPathParser( ).toItemSelector( path );
818         return getItem( selector );
819     }
820
821     @Override
822     public ContentItem toItem( StorageAsset assetPath ) throws LayoutException
823     {
824         return toItem( assetPath.getPath( ) );
825     }
826
827     /// ************* End of new generation interface ******************
828
829     /**
830      * Returns a version reference from the coordinates
831      * @param groupId the group id
832      * @param artifactId the artifact id
833      * @param version the version
834      * @return the versioned reference object
835      */
836     @Override
837     public VersionedReference toVersion( String groupId, String artifactId, String version ) {
838         return new VersionedReference().groupId( groupId ).artifactId( artifactId ).version( version );
839     }
840
841     @Override
842     public VersionedReference toGenericVersion( ArtifactReference artifactReference )
843     {
844         return toVersion( artifactReference.getGroupId( ), artifactReference.getArtifactId( ), VersionUtil.getBaseVersion( artifactReference.getVersion( ) ));
845     }
846
847     /**
848      * Return the version the artifact is part of
849      * @param artifactReference
850      * @return
851      */
852     public VersionedReference toVersion( ArtifactReference artifactReference) {
853         return toVersion( artifactReference.getGroupId( ), artifactReference.getArtifactId( ), artifactReference.getVersion( ) );
854     }
855
856     @Override
857     public ArtifactReference toArtifact( String groupId, String artifactId, String version, String type, String classifier) {
858         return new ArtifactReference( ).groupId( groupId ).artifactId( artifactId ).version( version ).type( type ).classifier( classifier );
859     }
860
861
862     @Override
863     public void deleteVersion( VersionedReference ref ) throws ContentNotFoundException, ContentAccessException
864     {
865         final String path = toPath( ref );
866         final Path deleteTarget = getRepoDir().resolve(path);
867         if ( !Files.exists(deleteTarget) )
868         {
869             log.warn( "Version path for repository {} does not exist: {}", getId(), deleteTarget );
870             throw new ContentNotFoundException( "Version not found for repository "+getId()+": "+path );
871         }
872         if ( Files.isDirectory(deleteTarget) )
873         {
874             try
875             {
876                 org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
877             }
878             catch ( IOException e )
879             {
880                 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
881                 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
882             }
883         } else {
884             log.warn( "Version path for repository {} is not a directory {}", getId(), deleteTarget );
885             throw new ContentNotFoundException( "Version path for repository "+getId()+" is not directory: " + path );
886         }
887     }
888
889     @Override
890     public void deleteProject( ProjectReference ref )
891         throws ContentNotFoundException, ContentAccessException
892     {
893         final String path = toPath( ref );
894         final Path deleteTarget = getRepoDir( ).resolve( path );
895         if ( !Files.exists(deleteTarget) )
896         {
897             log.warn( "Project path for repository {} does not exist: {}", getId(), deleteTarget );
898             throw new ContentNotFoundException( "Project not found for repository "+getId()+": "+path );
899         }
900         if ( Files.isDirectory(deleteTarget) )
901         {
902             try
903             {
904                 org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
905             }
906             catch ( IOException e )
907             {
908                 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
909                 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
910             }
911         }
912         else
913         {
914             log.warn( "Project path for repository {} is not a directory {}", getId(), deleteTarget );
915             throw new ContentNotFoundException( "Project path for repository "+getId()+" is not directory: " + path );
916         }
917
918     }
919
920     @Override
921     public void deleteProject( String namespace, String projectId ) throws ContentNotFoundException, ContentAccessException
922     {
923         this.deleteProject( new ProjectReference().groupId( namespace ).artifactId( projectId ) );
924     }
925
926     @Override
927     public void deleteArtifact( ArtifactReference ref ) throws ContentNotFoundException, ContentAccessException
928     {
929         final String path = toPath( ref );
930         final Path repoDir = getRepoDir( );
931         Path deleteTarget = repoDir.resolve( path );
932         if ( Files.exists(deleteTarget) )
933         {
934             try
935             {
936                 if (Files.isDirectory( deleteTarget ))
937                 {
938                     org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
939                 } else {
940                     Files.delete( deleteTarget );
941                 }
942             }
943             catch ( IOException e )
944             {
945                 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
946                 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
947             }
948         } else {
949             log.warn( "Artifact path for repository {} does not exist: {}", getId(), deleteTarget );
950             throw new ContentNotFoundException( "Artifact not found for repository "+getId()+": "+path );
951         }
952
953     }
954
955     @Override
956     public void deleteGroupId( String groupId )
957         throws ContentNotFoundException, ContentAccessException
958     {
959         final String path = toPath( groupId );
960         final Path deleteTarget = getRepoDir( ).resolve( path );
961         if (!Files.exists(deleteTarget)) {
962             log.warn( "Namespace path for repository {} does not exist: {}", getId(), deleteTarget );
963             throw new ContentNotFoundException( "Namespace not found for repository "+getId()+": "+path );
964         }
965         if ( Files.isDirectory(deleteTarget) )
966         {
967             try
968             {
969                 org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
970             }
971             catch ( IOException e )
972             {
973                 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
974                 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
975             }
976         } else {
977             log.warn( "Namespace path for repository {} is not a directory {}", getId(), deleteTarget );
978             throw new ContentNotFoundException( "Namespace path for repository "+getId()+" is not directory: " + path );
979
980         }
981     }
982
983     @Override
984     public String getId()
985     {
986         return repository.getId();
987     }
988
989     @Override
990     public List<ArtifactReference> getRelatedArtifacts( VersionedReference reference )
991         throws ContentNotFoundException, LayoutException, ContentAccessException
992     {
993         StorageAsset artifactDir = toFile( reference );
994         if ( !artifactDir.exists())
995         {
996             throw new ContentNotFoundException(
997                 "Unable to get related artifacts using a non-existant directory: " + artifactDir.getPath() );
998         }
999
1000         if ( !artifactDir.isContainer() )
1001         {
1002             throw new ContentNotFoundException(
1003                 "Unable to get related artifacts using a non-directory: " + artifactDir.getPath() );
1004         }
1005
1006         // First gather up the versions found as artifacts in the managed repository.
1007
1008         try (Stream<? extends StorageAsset> stream = artifactDir.list().stream() ) {
1009             return stream.filter(asset -> !asset.isContainer()).map(path -> {
1010                 try {
1011                     ArtifactReference artifact = toArtifactReference(path.getPath());
1012                     if( artifact.getGroupId().equals( reference.getGroupId() ) && artifact.getArtifactId().equals(
1013                             reference.getArtifactId() ) && artifact.getVersion().equals( reference.getVersion() )) {
1014                         return artifact;
1015                     } else {
1016                         return null;
1017                     }
1018                 } catch (LayoutException e) {
1019                     log.debug( "Not processing file that is not an artifact: {}", e.getMessage() );
1020                     return null;
1021                 }
1022             }).filter(Objects::nonNull).collect(Collectors.toList());
1023         } catch (RuntimeException e) {
1024             Throwable cause = e.getCause( );
1025             if (cause!=null) {
1026                 if (cause instanceof LayoutException) {
1027                     throw (LayoutException)cause;
1028                 } else
1029                 {
1030                     throw new ContentAccessException( cause.getMessage( ), cause );
1031                 }
1032             } else {
1033                 throw new ContentAccessException( e.getMessage( ), e );
1034             }
1035         }
1036     }
1037
1038     /*
1039      * Create the filter for various combinations of classifier and type
1040      */
1041     private Predicate<ArtifactReference> getChecker(ArtifactReference referenceObject, String extension) {
1042         // TODO: Check, if extension is the correct parameter here
1043         // We compare type with extension which works for artifacts like .jar.md5 but may
1044         // be not the best way.
1045
1046         if (referenceObject.getClassifier()!=null && referenceObject.getType()!=null) {
1047             return ((ArtifactReference a) ->
1048                 referenceObject.getGroupId().equals( a.getGroupId() )
1049                 && referenceObject.getArtifactId().equals( a.getArtifactId() )
1050                 && referenceObject.getVersion( ).equals( a.getVersion( ) )
1051                 && ( (a.getType()==null)
1052                     || referenceObject.getType().equals( a.getType() )
1053                     || a.getType().startsWith(extension) )
1054                 && referenceObject.getClassifier().equals( a.getClassifier() )
1055             );
1056         } else if (referenceObject.getClassifier()!=null && referenceObject.getType()==null){
1057             return ((ArtifactReference a) ->
1058                 referenceObject.getGroupId().equals( a.getGroupId() )
1059                     && referenceObject.getArtifactId().equals( a.getArtifactId() )
1060                     && referenceObject.getVersion( ).equals( a.getVersion( ) )
1061                     && referenceObject.getClassifier().equals( a.getClassifier() )
1062             );
1063         } else if (referenceObject.getClassifier()==null && referenceObject.getType()!=null){
1064             return ((ArtifactReference a) ->
1065                 referenceObject.getGroupId().equals( a.getGroupId() )
1066                     && referenceObject.getArtifactId().equals( a.getArtifactId() )
1067                     && referenceObject.getVersion( ).equals( a.getVersion( ) )
1068                     && ( (a.getType()==null)
1069                     || referenceObject.getType().equals( a.getType() )
1070                     || a.getType().startsWith(extension) )
1071             );
1072         } else {
1073             return ((ArtifactReference a) ->
1074                 referenceObject.getGroupId().equals( a.getGroupId() )
1075                     && referenceObject.getArtifactId().equals( a.getArtifactId() )
1076                     && referenceObject.getVersion( ).equals( a.getVersion( ) )
1077             );
1078         }
1079
1080
1081     }
1082
1083     @Override
1084     public List<ArtifactReference> getRelatedArtifacts( ArtifactReference reference )
1085         throws ContentNotFoundException, LayoutException, ContentAccessException
1086     {
1087         if ( StringUtils.isEmpty( reference.getType() ) && StringUtils.isEmpty( reference.getClassifier() ) ) {
1088             return getRelatedArtifacts( toVersion( reference ) );
1089         }
1090
1091         StorageAsset artifactFile = toFile( reference );
1092         StorageAsset repoDir = artifactFile.getParent();
1093         String ext;
1094         if (!artifactFile.isContainer()) {
1095             ext = StringUtils.substringAfterLast( artifactFile.getName(), ".");
1096         } else {
1097             ext = "";
1098         }
1099
1100         if ( !repoDir.exists())
1101         {
1102             throw new ContentNotFoundException(
1103                 "Unable to get related artifacts using a non-existant directory: " + repoDir.getPath() );
1104         }
1105
1106         if ( !repoDir.isContainer() )
1107         {
1108             throw new ContentNotFoundException(
1109                 "Unable to get related artifacts using a non-directory: " + repoDir.getPath() );
1110         }
1111
1112         // First gather up the versions found as artifacts in the managed repository.
1113
1114         try (Stream<? extends StorageAsset> stream = repoDir.list().stream() ) {
1115             return stream.filter(
1116                 asset -> !asset.isContainer())
1117                 .map(path -> {
1118                 try {
1119                     return toArtifactReference(path.getPath());
1120                 } catch (LayoutException e) {
1121                     log.debug( "Not processing file that is not an artifact: {}", e.getMessage() );
1122                     return null;
1123                 }
1124             }).filter(Objects::nonNull).filter(getChecker( reference, ext )).collect(Collectors.toList());
1125         } catch (RuntimeException e) {
1126             Throwable cause = e.getCause( );
1127             if (cause!=null) {
1128                 if (cause instanceof LayoutException) {
1129                     throw (LayoutException)cause;
1130                 } else
1131                 {
1132                     throw new ContentAccessException( cause.getMessage( ), cause );
1133                 }
1134             } else {
1135                 throw new ContentAccessException( e.getMessage( ), e );
1136             }
1137         }
1138     }
1139
1140     @Override
1141     public List<StorageAsset> getRelatedAssets( ArtifactReference reference ) throws ContentNotFoundException, LayoutException, ContentAccessException
1142     {
1143         return null;
1144     }
1145
1146     @Override
1147     public String getRepoRoot()
1148     {
1149         return convertUriToPath( repository.getLocation() );
1150     }
1151
1152     private String convertUriToPath( URI uri ) {
1153         if (uri.getScheme()==null) {
1154             return Paths.get(uri.getPath()).toString();
1155         } else if ("file".equals(uri.getScheme())) {
1156             return Paths.get(uri).toString();
1157         } else {
1158             return uri.toString();
1159         }
1160     }
1161
1162     @Override
1163     public ManagedRepository getRepository()
1164     {
1165         return repository;
1166     }
1167
1168     /**
1169      * Gather the Available Versions (on disk) for a specific Project Reference, based on filesystem
1170      * information.
1171      *
1172      * @return the Set of available versions, based on the project reference.
1173      * @throws LayoutException
1174      */
1175     @Override
1176     public Set<String> getVersions( ProjectReference reference )
1177         throws ContentNotFoundException, LayoutException, ContentAccessException
1178     {
1179         final String path = toPath( reference );
1180         final Path projDir = getRepoDir().resolve(toPath(reference));
1181         if ( !Files.exists(projDir) )
1182         {
1183             throw new ContentNotFoundException(
1184                 "Unable to get Versions on a non-existant directory for repository "+getId()+": " + path );
1185         }
1186
1187         if ( !Files.isDirectory(projDir) )
1188         {
1189             throw new ContentNotFoundException(
1190                 "Unable to get Versions on a non-directory for repository "+getId()+": " + path );
1191         }
1192
1193         final String groupId = reference.getGroupId();
1194         final String artifactId = reference.getArtifactId();
1195         try(Stream<Path> stream = Files.list(projDir)) {
1196             return stream.filter(Files::isDirectory).map(
1197                     p -> toVersion(groupId, artifactId, p.getFileName().toString())
1198             ).filter(this::hasArtifact).map(ref -> ref.getVersion())
1199                     .collect(Collectors.toSet());
1200         } catch (IOException e) {
1201             log.error("Could not read directory {}: {}", projDir, e.getMessage(), e);
1202             throw new ContentAccessException( "Could not read path for repository "+getId()+": "+ path, e );
1203         } catch (RuntimeException e) {
1204             Throwable cause = e.getCause( );
1205             if (cause!=null)
1206             {
1207                 if ( cause instanceof LayoutException )
1208                 {
1209                     throw (LayoutException) cause;
1210                 } else {
1211                     log.error("Could not read directory {}: {}", projDir, cause.getMessage(), cause);
1212                     throw new ContentAccessException( "Could not read path for repository "+getId()+": "+ path, cause );
1213                 }
1214             } else {
1215                 log.error("Could not read directory {}: {}", projDir, e.getMessage(), e);
1216                 throw new ContentAccessException( "Could not read path for repository "+getId()+": "+ path, cause );
1217             }
1218         }
1219     }
1220
1221     @Override
1222     public Set<String> getVersions( VersionedReference reference )
1223         throws ContentNotFoundException, ContentAccessException, LayoutException
1224     {
1225         try(Stream<ArtifactReference> stream = newArtifactStream( reference ))
1226         {
1227             return stream.filter( Objects::nonNull )
1228                 .map( ar -> ar.getVersion( ) )
1229                 .collect( Collectors.toSet( ) );
1230         } catch (IOException e) {
1231             final String path = toPath( reference );
1232             log.error("Could not read directory from repository {} - {}: ", getId(), path, e.getMessage(), e);
1233             throw new ContentAccessException( "Could not read path for repository "+getId()+": "+ path, e );
1234         }
1235     }
1236
1237     @Override
1238     public boolean hasContent( ArtifactReference reference ) throws ContentAccessException
1239     {
1240         StorageAsset artifactFile = toFile( reference );
1241         return artifactFile.exists() && !artifactFile.isContainer();
1242     }
1243
1244     @Override
1245     public boolean hasContent( ProjectReference reference ) throws ContentAccessException
1246     {
1247         try
1248         {
1249             Set<String> versions = getVersions( reference );
1250             return !versions.isEmpty();
1251         }
1252         catch ( ContentNotFoundException | LayoutException e )
1253         {
1254             return false;
1255         }
1256     }
1257
1258     @Override
1259     public boolean hasContent( VersionedReference reference ) throws ContentAccessException
1260     {
1261         try
1262         {
1263             return ( getFirstArtifact( reference ) != null );
1264         }
1265         catch ( LayoutException | ContentNotFoundException e )
1266         {
1267             return false;
1268         }
1269         catch ( IOException e )
1270         {
1271             String path = toPath( reference );
1272             log.error("Could not read directory from repository {} - {}: ", getId(), path, e.getMessage(), e);
1273             throw new ContentAccessException( "Could not read path from repository " + getId( ) + ": " + path, e );
1274         }
1275     }
1276
1277     @Override
1278     public void setRepository( final ManagedRepository repo )
1279     {
1280         this.repository = repo;
1281         if (repo!=null) {
1282             if (repository instanceof EditableManagedRepository) {
1283                 ((EditableManagedRepository) repository).setContent(this);
1284             }
1285         }
1286     }
1287
1288     private Path getRepoDir() {
1289         return repository.getAsset( "" ).getFilePath( );
1290     }
1291
1292     private RepositoryStorage getStorage() {
1293         return repository.getAsset( "" ).getStorage( );
1294     }
1295
1296     /**
1297      * Convert a path to an artifact reference.
1298      *
1299      * @param path the path to convert. (relative or full location path)
1300      * @throws LayoutException if the path cannot be converted to an artifact reference.
1301      */
1302     @Override
1303     public ArtifactReference toArtifactReference( String path )
1304         throws LayoutException
1305     {
1306         String repoPath = convertUriToPath( repository.getLocation() );
1307         if ( ( path != null ) && path.startsWith( repoPath ) && repoPath.length() > 0 )
1308         {
1309             return super.toArtifactReference( path.substring( repoPath.length() + 1 ) );
1310         } else {
1311             repoPath = path;
1312             if (repoPath!=null) {
1313                 while (repoPath.startsWith("/")) {
1314                     repoPath = repoPath.substring(1);
1315                 }
1316             }
1317             return super.toArtifactReference( repoPath );
1318         }
1319     }
1320
1321
1322     // The variant with runtime exception for stream usage
1323     private ArtifactReference toArtifactRef(String path) {
1324         try {
1325             return toArtifactReference(path);
1326         } catch (LayoutException e) {
1327             throw new RuntimeException(e);
1328         }
1329     }
1330
1331
1332
1333     @Override
1334     public StorageAsset toFile( ArtifactReference reference )
1335     {
1336         return repository.getAsset(toPath(reference));
1337     }
1338
1339     @Override
1340     public StorageAsset toFile( ArchivaArtifact reference )
1341     {
1342         return repository.getAsset( toPath( reference ) );
1343     }
1344
1345     @Override
1346     public StorageAsset toFile( VersionedReference reference )
1347     {
1348         return repository.getAsset( toPath( reference ) );
1349     }
1350
1351     /**
1352      * Get the first Artifact found in the provided VersionedReference location.
1353      *
1354      * @param reference the reference to the versioned reference to search within
1355      * @return the ArtifactReference to the first artifact located within the versioned reference. or null if
1356      *         no artifact was found within the versioned reference.
1357      * @throws java.io.IOException     if the versioned reference is invalid (example: doesn't exist, or isn't a directory)
1358      * @throws LayoutException
1359      */
1360     private ArtifactReference getFirstArtifact( VersionedReference reference )
1361         throws ContentNotFoundException, LayoutException, IOException
1362     {
1363         try(Stream<ArtifactReference> stream = newArtifactStream( reference ))
1364         {
1365             return stream.findFirst( ).orElse( null );
1366         } catch (RuntimeException e) {
1367             throw new ContentNotFoundException( e.getMessage( ), e.getCause( ) );
1368         }
1369     }
1370
1371     private Stream<ArtifactReference> newArtifactStream( VersionedReference reference) throws ContentNotFoundException, LayoutException, IOException {
1372         final Path repoBase = getRepoDir( );
1373         String path = toMetadataPath( reference );
1374         Path versionDir = repoBase.resolve( path ).getParent();
1375         if ( !Files.exists(versionDir) )
1376         {
1377             throw new ContentNotFoundException( "Unable to gather the list of artifacts on a non-existant directory: "
1378                 + versionDir.toAbsolutePath() );
1379         }
1380
1381         if ( !Files.isDirectory(versionDir) )
1382         {
1383             throw new ContentNotFoundException(
1384                 "Unable to gather the list of snapshot versions on a non-directory: " + versionDir.toAbsolutePath() );
1385         }
1386         return Files.list(versionDir).filter(Files::isRegularFile)
1387                 .map(p -> repoBase.relativize(p).toString())
1388                 .filter(p -> !filetypes.matchesDefaultExclusions(p))
1389                 .filter(filetypes::matchesArtifactPattern)
1390                 .map(this::toArtifactRef);
1391     }
1392
1393     public List<ArtifactReference> getArtifacts(VersionedReference reference) throws ContentNotFoundException, LayoutException, ContentAccessException
1394     {
1395         try (Stream<ArtifactReference> stream = newArtifactStream( reference ))
1396         {
1397             return stream.collect( Collectors.toList( ) );
1398         } catch ( IOException e )
1399         {
1400             String path = toPath( reference );
1401             log.error("Could not read directory from repository {} - {}: ", getId(), path, e.getMessage(), e);
1402             throw new ContentAccessException( "Could not read path from repository " + getId( ) + ": " + path, e );
1403
1404         }
1405     }
1406
1407     private boolean hasArtifact( VersionedReference reference )
1408
1409     {
1410         try(Stream<ArtifactReference> stream = newArtifactStream( reference ))
1411         {
1412             return stream.anyMatch( e -> true );
1413         } catch (ContentNotFoundException e) {
1414             return false;
1415         } catch ( LayoutException | IOException e) {
1416             // We throw the runtime exception for better stream handling
1417             throw new RuntimeException(e);
1418         }
1419     }
1420
1421     public void setFiletypes( FileTypes filetypes )
1422     {
1423         this.filetypes = filetypes;
1424     }
1425
1426     public void setMavenContentHelper( MavenContentHelper contentHelper) {
1427         this.mavenContentHelper = contentHelper;
1428     }
1429
1430
1431 }