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