]> source.dussan.org Git - archiva.git/blob
bc8b2e5f8f441227ac715a49249ab264636d21c9
[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     /// ************* Start 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     /**
922      * Return the version the artifact is part of
923      * @param artifactReference
924      * @return
925      */
926     public VersionedReference toVersion( ArtifactReference artifactReference) {
927         return toVersion( artifactReference.getGroupId( ), artifactReference.getArtifactId( ), artifactReference.getVersion( ) );
928     }
929
930     @Override
931     public ArtifactReference toArtifact( String groupId, String artifactId, String version, String type, String classifier) {
932         return new ArtifactReference( ).groupId( groupId ).artifactId( artifactId ).version( version ).type( type ).classifier( classifier );
933     }
934
935
936     @Override
937     public void deleteVersion( VersionedReference ref ) throws ContentNotFoundException, ContentAccessException
938     {
939         final String path = toPath( ref );
940         final Path deleteTarget = getRepoDir().resolve(path);
941         if ( !Files.exists(deleteTarget) )
942         {
943             log.warn( "Version path for repository {} does not exist: {}", getId(), deleteTarget );
944             throw new ContentNotFoundException( "Version not found for repository "+getId()+": "+path );
945         }
946         if ( Files.isDirectory(deleteTarget) )
947         {
948             try
949             {
950                 org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
951             }
952             catch ( IOException e )
953             {
954                 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
955                 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
956             }
957         } else {
958             log.warn( "Version path for repository {} is not a directory {}", getId(), deleteTarget );
959             throw new ContentNotFoundException( "Version path for repository "+getId()+" is not directory: " + path );
960         }
961     }
962
963     @Override
964     public void deleteProject( ProjectReference ref )
965         throws ContentNotFoundException, ContentAccessException
966     {
967         final String path = toPath( ref );
968         final Path deleteTarget = getRepoDir( ).resolve( path );
969         if ( !Files.exists(deleteTarget) )
970         {
971             log.warn( "Project path for repository {} does not exist: {}", getId(), deleteTarget );
972             throw new ContentNotFoundException( "Project not found for repository "+getId()+": "+path );
973         }
974         if ( Files.isDirectory(deleteTarget) )
975         {
976             try
977             {
978                 org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
979             }
980             catch ( IOException e )
981             {
982                 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
983                 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
984             }
985         }
986         else
987         {
988             log.warn( "Project path for repository {} is not a directory {}", getId(), deleteTarget );
989             throw new ContentNotFoundException( "Project path for repository "+getId()+" is not directory: " + path );
990         }
991
992     }
993
994     @Override
995     public void deleteProject( String namespace, String projectId ) throws ContentNotFoundException, ContentAccessException
996     {
997         this.deleteProject( new ProjectReference().groupId( namespace ).artifactId( projectId ) );
998     }
999
1000     @Override
1001     public void deleteArtifact( ArtifactReference ref ) throws ContentNotFoundException, ContentAccessException
1002     {
1003         final String path = toPath( ref );
1004         final Path repoDir = getRepoDir( );
1005         Path deleteTarget = repoDir.resolve( path );
1006         if ( Files.exists(deleteTarget) )
1007         {
1008             try
1009             {
1010                 if (Files.isDirectory( deleteTarget ))
1011                 {
1012                     org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
1013                 } else {
1014                     Files.delete( deleteTarget );
1015                 }
1016             }
1017             catch ( IOException e )
1018             {
1019                 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
1020                 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
1021             }
1022         } else {
1023             log.warn( "Artifact path for repository {} does not exist: {}", getId(), deleteTarget );
1024             throw new ContentNotFoundException( "Artifact not found for repository "+getId()+": "+path );
1025         }
1026
1027     }
1028
1029     @Override
1030     public void deleteGroupId( String groupId )
1031         throws ContentNotFoundException, ContentAccessException
1032     {
1033         final String path = toPath( groupId );
1034         final Path deleteTarget = getRepoDir( ).resolve( path );
1035         if (!Files.exists(deleteTarget)) {
1036             log.warn( "Namespace path for repository {} does not exist: {}", getId(), deleteTarget );
1037             throw new ContentNotFoundException( "Namespace not found for repository "+getId()+": "+path );
1038         }
1039         if ( Files.isDirectory(deleteTarget) )
1040         {
1041             try
1042             {
1043                 org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
1044             }
1045             catch ( IOException e )
1046             {
1047                 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
1048                 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
1049             }
1050         } else {
1051             log.warn( "Namespace path for repository {} is not a directory {}", getId(), deleteTarget );
1052             throw new ContentNotFoundException( "Namespace path for repository "+getId()+" is not directory: " + path );
1053
1054         }
1055     }
1056
1057     @Override
1058     public String getId()
1059     {
1060         return repository.getId();
1061     }
1062
1063     @Override
1064     public List<ArtifactReference> getRelatedArtifacts( VersionedReference reference )
1065         throws ContentNotFoundException, LayoutException, ContentAccessException
1066     {
1067         StorageAsset artifactDir = toFile( reference );
1068         if ( !artifactDir.exists())
1069         {
1070             throw new ContentNotFoundException(
1071                 "Unable to get related artifacts using a non-existant directory: " + artifactDir.getPath() );
1072         }
1073
1074         if ( !artifactDir.isContainer() )
1075         {
1076             throw new ContentNotFoundException(
1077                 "Unable to get related artifacts using a non-directory: " + artifactDir.getPath() );
1078         }
1079
1080         // First gather up the versions found as artifacts in the managed repository.
1081
1082         try (Stream<? extends StorageAsset> stream = artifactDir.list().stream() ) {
1083             return stream.filter(asset -> !asset.isContainer()).map(path -> {
1084                 try {
1085                     ArtifactReference artifact = toArtifactReference(path.getPath());
1086                     if( artifact.getGroupId().equals( reference.getGroupId() ) && artifact.getArtifactId().equals(
1087                             reference.getArtifactId() ) && artifact.getVersion().equals( reference.getVersion() )) {
1088                         return artifact;
1089                     } else {
1090                         return null;
1091                     }
1092                 } catch (LayoutException e) {
1093                     log.debug( "Not processing file that is not an artifact: {}", e.getMessage() );
1094                     return null;
1095                 }
1096             }).filter(Objects::nonNull).collect(Collectors.toList());
1097         } catch (RuntimeException e) {
1098             Throwable cause = e.getCause( );
1099             if (cause!=null) {
1100                 if (cause instanceof LayoutException) {
1101                     throw (LayoutException)cause;
1102                 } else
1103                 {
1104                     throw new ContentAccessException( cause.getMessage( ), cause );
1105                 }
1106             } else {
1107                 throw new ContentAccessException( e.getMessage( ), e );
1108             }
1109         }
1110     }
1111
1112     /*
1113      * Create the filter for various combinations of classifier and type
1114      */
1115     private Predicate<ArtifactReference> getChecker(ArtifactReference referenceObject, String extension) {
1116         // TODO: Check, if extension is the correct parameter here
1117         // We compare type with extension which works for artifacts like .jar.md5 but may
1118         // be not the best way.
1119
1120         if (referenceObject.getClassifier()!=null && referenceObject.getType()!=null) {
1121             return ((ArtifactReference a) ->
1122                 referenceObject.getGroupId().equals( a.getGroupId() )
1123                 && referenceObject.getArtifactId().equals( a.getArtifactId() )
1124                 && referenceObject.getVersion( ).equals( a.getVersion( ) )
1125                 && ( (a.getType()==null)
1126                     || referenceObject.getType().equals( a.getType() )
1127                     || a.getType().startsWith(extension) )
1128                 && referenceObject.getClassifier().equals( a.getClassifier() )
1129             );
1130         } else if (referenceObject.getClassifier()!=null && referenceObject.getType()==null){
1131             return ((ArtifactReference a) ->
1132                 referenceObject.getGroupId().equals( a.getGroupId() )
1133                     && referenceObject.getArtifactId().equals( a.getArtifactId() )
1134                     && referenceObject.getVersion( ).equals( a.getVersion( ) )
1135                     && referenceObject.getClassifier().equals( a.getClassifier() )
1136             );
1137         } else if (referenceObject.getClassifier()==null && referenceObject.getType()!=null){
1138             return ((ArtifactReference a) ->
1139                 referenceObject.getGroupId().equals( a.getGroupId() )
1140                     && referenceObject.getArtifactId().equals( a.getArtifactId() )
1141                     && referenceObject.getVersion( ).equals( a.getVersion( ) )
1142                     && ( (a.getType()==null)
1143                     || referenceObject.getType().equals( a.getType() )
1144                     || a.getType().startsWith(extension) )
1145             );
1146         } else {
1147             return ((ArtifactReference a) ->
1148                 referenceObject.getGroupId().equals( a.getGroupId() )
1149                     && referenceObject.getArtifactId().equals( a.getArtifactId() )
1150                     && referenceObject.getVersion( ).equals( a.getVersion( ) )
1151             );
1152         }
1153
1154
1155     }
1156
1157     @Override
1158     public List<ArtifactReference> getRelatedArtifacts( ArtifactReference reference )
1159         throws ContentNotFoundException, LayoutException, ContentAccessException
1160     {
1161         if ( StringUtils.isEmpty( reference.getType() ) && StringUtils.isEmpty( reference.getClassifier() ) ) {
1162             return getRelatedArtifacts( toVersion( reference ) );
1163         }
1164
1165         StorageAsset artifactFile = toFile( reference );
1166         StorageAsset repoDir = artifactFile.getParent();
1167         String ext;
1168         if (!artifactFile.isContainer()) {
1169             ext = StringUtils.substringAfterLast( artifactFile.getName(), ".");
1170         } else {
1171             ext = "";
1172         }
1173
1174         if ( !repoDir.exists())
1175         {
1176             throw new ContentNotFoundException(
1177                 "Unable to get related artifacts using a non-existant directory: " + repoDir.getPath() );
1178         }
1179
1180         if ( !repoDir.isContainer() )
1181         {
1182             throw new ContentNotFoundException(
1183                 "Unable to get related artifacts using a non-directory: " + repoDir.getPath() );
1184         }
1185
1186         // First gather up the versions found as artifacts in the managed repository.
1187
1188         try (Stream<? extends StorageAsset> stream = repoDir.list().stream() ) {
1189             return stream.filter(
1190                 asset -> !asset.isContainer())
1191                 .map(path -> {
1192                 try {
1193                     return toArtifactReference(path.getPath());
1194                 } catch (LayoutException e) {
1195                     log.debug( "Not processing file that is not an artifact: {}", e.getMessage() );
1196                     return null;
1197                 }
1198             }).filter(Objects::nonNull).filter(getChecker( reference, ext )).collect(Collectors.toList());
1199         } catch (RuntimeException e) {
1200             Throwable cause = e.getCause( );
1201             if (cause!=null) {
1202                 if (cause instanceof LayoutException) {
1203                     throw (LayoutException)cause;
1204                 } else
1205                 {
1206                     throw new ContentAccessException( cause.getMessage( ), cause );
1207                 }
1208             } else {
1209                 throw new ContentAccessException( e.getMessage( ), e );
1210             }
1211         }
1212     }
1213
1214     @Override
1215     public List<StorageAsset> getRelatedAssets( ArtifactReference reference ) throws ContentNotFoundException, LayoutException, ContentAccessException
1216     {
1217         return null;
1218     }
1219
1220     @Override
1221     public String getRepoRoot()
1222     {
1223         return convertUriToPath( repository.getLocation() );
1224     }
1225
1226     private String convertUriToPath( URI uri ) {
1227         if (uri.getScheme()==null) {
1228             return Paths.get(uri.getPath()).toString();
1229         } else if ("file".equals(uri.getScheme())) {
1230             return Paths.get(uri).toString();
1231         } else {
1232             return uri.toString();
1233         }
1234     }
1235
1236     @Override
1237     public ManagedRepository getRepository()
1238     {
1239         return repository;
1240     }
1241
1242     @Override
1243     public Set<String> getVersions( VersionedReference reference )
1244         throws ContentNotFoundException, ContentAccessException, LayoutException
1245     {
1246         try(Stream<ArtifactReference> stream = newArtifactStream( reference ))
1247         {
1248             return stream.filter( Objects::nonNull )
1249                 .map( ar -> ar.getVersion( ) )
1250                 .collect( Collectors.toSet( ) );
1251         } catch (IOException e) {
1252             final String path = toPath( reference );
1253             log.error("Could not read directory from repository {} - {}: ", getId(), path, e.getMessage(), e);
1254             throw new ContentAccessException( "Could not read path for repository "+getId()+": "+ path, e );
1255         }
1256     }
1257
1258     @Override
1259     public boolean hasContent( ArtifactReference reference ) throws ContentAccessException
1260     {
1261         StorageAsset artifactFile = toFile( reference );
1262         return artifactFile.exists() && !artifactFile.isContainer();
1263     }
1264
1265     @Override
1266     public boolean hasContent( VersionedReference reference ) throws ContentAccessException
1267     {
1268         try
1269         {
1270             return ( getFirstArtifact( reference ) != null );
1271         }
1272         catch ( LayoutException | ContentNotFoundException e )
1273         {
1274             return false;
1275         }
1276         catch ( IOException e )
1277         {
1278             String path = toPath( reference );
1279             log.error("Could not read directory from repository {} - {}: ", getId(), path, e.getMessage(), e);
1280             throw new ContentAccessException( "Could not read path from repository " + getId( ) + ": " + path, e );
1281         }
1282     }
1283
1284     @Override
1285     public void setRepository( final ManagedRepository repo )
1286     {
1287         this.repository = repo;
1288         if (repo!=null) {
1289             if (repository instanceof EditableManagedRepository) {
1290                 ((EditableManagedRepository) repository).setContent(this);
1291             }
1292         }
1293     }
1294
1295     private Path getRepoDir() {
1296         return repository.getAsset( "" ).getFilePath( );
1297     }
1298
1299     private RepositoryStorage getStorage() {
1300         return repository.getAsset( "" ).getStorage( );
1301     }
1302
1303     /**
1304      * Convert a path to an artifact reference.
1305      *
1306      * @param path the path to convert. (relative or full location path)
1307      * @throws LayoutException if the path cannot be converted to an artifact reference.
1308      */
1309     @Override
1310     public ArtifactReference toArtifactReference( String path )
1311         throws LayoutException
1312     {
1313         String repoPath = convertUriToPath( repository.getLocation() );
1314         if ( ( path != null ) && path.startsWith( repoPath ) && repoPath.length() > 0 )
1315         {
1316             return super.toArtifactReference( path.substring( repoPath.length() + 1 ) );
1317         } else {
1318             repoPath = path;
1319             if (repoPath!=null) {
1320                 while (repoPath.startsWith("/")) {
1321                     repoPath = repoPath.substring(1);
1322                 }
1323             }
1324             return super.toArtifactReference( repoPath );
1325         }
1326     }
1327
1328
1329     // The variant with runtime exception for stream usage
1330     private ArtifactReference toArtifactRef(String path) {
1331         try {
1332             return toArtifactReference(path);
1333         } catch (LayoutException e) {
1334             throw new RuntimeException(e);
1335         }
1336     }
1337
1338
1339
1340     @Override
1341     public StorageAsset toFile( ArtifactReference reference )
1342     {
1343         return repository.getAsset(toPath(reference));
1344     }
1345
1346     @Override
1347     public StorageAsset toFile( ArchivaArtifact reference )
1348     {
1349         return repository.getAsset( toPath( reference ) );
1350     }
1351
1352     @Override
1353     public StorageAsset toFile( VersionedReference reference )
1354     {
1355         return repository.getAsset( toPath( reference ) );
1356     }
1357
1358     /**
1359      * Get the first Artifact found in the provided VersionedReference location.
1360      *
1361      * @param reference the reference to the versioned reference to search within
1362      * @return the ArtifactReference to the first artifact located within the versioned reference. or null if
1363      *         no artifact was found within the versioned reference.
1364      * @throws java.io.IOException     if the versioned reference is invalid (example: doesn't exist, or isn't a directory)
1365      * @throws LayoutException
1366      */
1367     private ArtifactReference getFirstArtifact( VersionedReference reference )
1368         throws ContentNotFoundException, LayoutException, IOException
1369     {
1370         try(Stream<ArtifactReference> stream = newArtifactStream( reference ))
1371         {
1372             return stream.findFirst( ).orElse( null );
1373         } catch (RuntimeException e) {
1374             throw new ContentNotFoundException( e.getMessage( ), e.getCause( ) );
1375         }
1376     }
1377
1378     private Stream<ArtifactReference> newArtifactStream( VersionedReference reference) throws ContentNotFoundException, LayoutException, IOException {
1379         final Path repoBase = getRepoDir( );
1380         String path = toMetadataPath( reference );
1381         Path versionDir = repoBase.resolve( path ).getParent();
1382         if ( !Files.exists(versionDir) )
1383         {
1384             throw new ContentNotFoundException( "Unable to gather the list of artifacts on a non-existant directory: "
1385                 + versionDir.toAbsolutePath() );
1386         }
1387
1388         if ( !Files.isDirectory(versionDir) )
1389         {
1390             throw new ContentNotFoundException(
1391                 "Unable to gather the list of snapshot versions on a non-directory: " + versionDir.toAbsolutePath() );
1392         }
1393         return Files.list(versionDir).filter(Files::isRegularFile)
1394                 .map(p -> repoBase.relativize(p).toString())
1395                 .filter(p -> !filetypes.matchesDefaultExclusions(p))
1396                 .filter(filetypes::matchesArtifactPattern)
1397                 .map(this::toArtifactRef);
1398     }
1399
1400     public List<ArtifactReference> getArtifacts(VersionedReference reference) throws ContentNotFoundException, LayoutException, ContentAccessException
1401     {
1402         try (Stream<ArtifactReference> stream = newArtifactStream( reference ))
1403         {
1404             return stream.collect( Collectors.toList( ) );
1405         } catch ( IOException e )
1406         {
1407             String path = toPath( reference );
1408             log.error("Could not read directory from repository {} - {}: ", getId(), path, e.getMessage(), e);
1409             throw new ContentAccessException( "Could not read path from repository " + getId( ) + ": " + path, e );
1410
1411         }
1412     }
1413
1414     private boolean hasArtifact( VersionedReference reference )
1415
1416     {
1417         try(Stream<ArtifactReference> stream = newArtifactStream( reference ))
1418         {
1419             return stream.anyMatch( e -> true );
1420         } catch (ContentNotFoundException e) {
1421             return false;
1422         } catch ( LayoutException | IOException e) {
1423             // We throw the runtime exception for better stream handling
1424             throw new RuntimeException(e);
1425         }
1426     }
1427
1428     public void setFiletypes( FileTypes filetypes )
1429     {
1430         this.filetypes = filetypes;
1431     }
1432
1433     public void setMavenContentHelper( MavenContentHelper contentHelper) {
1434         this.mavenContentHelper = contentHelper;
1435     }
1436
1437
1438 }