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