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