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