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