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