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