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