]> source.dussan.org Git - archiva.git/blob
dfc0f04d550b6d4903d09593127359ce6060cbba
[archiva.git] /
1 package org.apache.archiva.repository.maven.content;
2
3 /*
4  * Licensed to the Apache Software Foundation (ASF) under one
5  * or more contributor license agreements.  See the NOTICE file
6  * distributed with this work for additional information
7  * regarding copyright ownership.  The ASF licenses this file
8  * to you under the Apache License, Version 2.0 (the
9  * "License"); you may not use this file except in compliance
10  * with the License.  You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  */
20
21 import org.apache.archiva.common.filelock.FileLockManager;
22 import org.apache.archiva.common.utils.FileUtils;
23 import org.apache.archiva.common.utils.VersionUtil;
24 import org.apache.archiva.configuration.FileTypes;
25 import org.apache.archiva.maven2.metadata.MavenMetadataReader;
26 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
27 import org.apache.archiva.repository.maven.metadata.storage.ArtifactMappingProvider;
28 import org.apache.archiva.repository.maven.metadata.storage.DefaultArtifactMappingProvider;
29 import org.apache.archiva.model.ArchivaArtifact;
30 import org.apache.archiva.model.ArtifactReference;
31 import org.apache.archiva.model.ProjectReference;
32 import org.apache.archiva.model.VersionedReference;
33 import org.apache.archiva.repository.ContentAccessException;
34 import org.apache.archiva.repository.ContentNotFoundException;
35 import org.apache.archiva.repository.EditableManagedRepository;
36 import org.apache.archiva.repository.LayoutException;
37 import org.apache.archiva.repository.ManagedRepository;
38 import org.apache.archiva.repository.ManagedRepositoryContent;
39 import org.apache.archiva.repository.content.Artifact;
40 import org.apache.archiva.repository.content.ContentItem;
41 import org.apache.archiva.repository.content.ItemNotFoundException;
42 import org.apache.archiva.repository.content.ItemSelector;
43 import org.apache.archiva.repository.content.Namespace;
44 import org.apache.archiva.repository.content.Project;
45 import org.apache.archiva.repository.content.Version;
46 import org.apache.archiva.repository.content.base.ArchivaNamespace;
47 import org.apache.archiva.repository.content.base.ArchivaProject;
48 import org.apache.archiva.repository.content.base.ArchivaVersion;
49 import org.apache.archiva.repository.content.base.builder.ArtifactOptBuilder;
50 import org.apache.archiva.repository.storage.RepositoryStorage;
51 import org.apache.archiva.repository.storage.StorageAsset;
52 import org.apache.archiva.repository.storage.util.StorageUtil;
53 import org.apache.commons.collections4.map.ReferenceMap;
54 import org.apache.commons.lang3.StringUtils;
55
56 import javax.inject.Inject;
57 import javax.inject.Named;
58 import java.io.IOException;
59 import java.net.URI;
60 import java.nio.file.Files;
61 import java.nio.file.Path;
62 import java.nio.file.Paths;
63 import java.util.Collections;
64 import java.util.List;
65 import java.util.Objects;
66 import java.util.Optional;
67 import java.util.Set;
68 import java.util.function.Predicate;
69 import java.util.regex.Matcher;
70 import java.util.regex.Pattern;
71 import java.util.stream.Collectors;
72 import java.util.stream.Stream;
73
74 /**
75  * ManagedDefaultRepositoryContent
76  */
77 public class ManagedDefaultRepositoryContent
78     extends AbstractDefaultRepositoryContent
79     implements ManagedRepositoryContent
80 {
81
82     public  static final String METADATA_FILENAME = "maven-metadata.xml";
83     private FileTypes filetypes;
84
85     public void setFileTypes(FileTypes fileTypes) {
86         this.filetypes = fileTypes;
87     }
88
89     private ManagedRepository repository;
90
91     private FileLockManager lockManager;
92
93     @Inject
94     @Named("repositoryPathTranslator#maven2")
95     private RepositoryPathTranslator pathTranslator;
96
97     @Inject
98     @Named( "metadataReader#maven" )
99     MavenMetadataReader metadataReader;
100
101     @Inject
102     @Named( "MavenContentHelper" )
103     MavenContentHelper mavenContentHelper;
104
105     public static final String SNAPSHOT = "SNAPSHOT";
106
107     public static final Pattern UNIQUE_SNAPSHOT_PATTERN = Pattern.compile( "^(SNAPSHOT|[0-9]{8}\\.[0-9]{6}-[0-9]+)(.*)" );
108     public static final Pattern CLASSIFIER_PATTERN = Pattern.compile( "^-([^.]+)(\\..*)" );
109
110     public static final Pattern TIMESTAMP_PATTERN = Pattern.compile( "^([0-9]{8})\\.([0-9]{6})$" );
111
112     public static final Pattern GENERIC_SNAPSHOT_PATTERN = Pattern.compile( "^(.*)-" + SNAPSHOT );
113
114     /**
115      * We are caching content items in a weak reference map. To avoid always recreating the
116      * the hierarchical structure.
117      * TODO: Better use a object cache? E.g. our spring cache implementation?
118      */
119     private ReferenceMap<String, Namespace> namespaceMap = new ReferenceMap<>( );
120     private ReferenceMap<StorageAsset, Project> projectMap = new ReferenceMap<>( );
121     private ReferenceMap<StorageAsset, Version> versionMap = new ReferenceMap<>( );
122     private ReferenceMap<StorageAsset, Artifact> artifactMap = new ReferenceMap<>( );
123
124     public ManagedDefaultRepositoryContent() {
125         super(Collections.singletonList( new DefaultArtifactMappingProvider() ));
126     }
127
128     public ManagedDefaultRepositoryContent(ManagedRepository repository, FileTypes fileTypes, FileLockManager lockManager) {
129         super(Collections.singletonList( new DefaultArtifactMappingProvider() ));
130         setFileTypes( fileTypes );
131         this.lockManager = lockManager;
132         setRepository( repository );
133     }
134
135     public ManagedDefaultRepositoryContent( ManagedRepository repository, List<? extends ArtifactMappingProvider> artifactMappingProviders, FileTypes fileTypes, FileLockManager lockManager )
136     {
137         super(artifactMappingProviders==null ? Collections.singletonList( new DefaultArtifactMappingProvider() ) : artifactMappingProviders);
138         setFileTypes( fileTypes );
139         this.lockManager = lockManager;
140         setRepository( repository );
141
142     }
143
144     private StorageAsset getAssetByPath(String assetPath) {
145         return getStorage( ).getAsset( assetPath );
146     }
147
148     private StorageAsset getAsset(String namespace) {
149         String namespacePath = formatAsDirectory( namespace.trim() );
150         if (StringUtils.isEmpty( namespacePath )) {
151             namespacePath = "";
152         }
153         return getAssetByPath(namespacePath);
154     }
155
156     private StorageAsset getAsset(String namespace, String project) {
157         return getAsset( namespace ).resolve( project );
158     }
159
160     private StorageAsset getAsset(String namespace, String project, String version) {
161         return getAsset( namespace, project ).resolve( version );
162     }
163
164     private StorageAsset getAsset(String namespace, String project, String version, String fileName) {
165         return getAsset( namespace, project, version ).resolve( fileName );
166     }
167
168
169     /// ************* End of new generation interface ******************
170     @Override
171     public void deleteItem( ContentItem item ) throws ItemNotFoundException, ContentAccessException
172     {
173         final Path baseDirectory = getRepoDir( );
174         final Path itemPath = item.getAsset( ).getFilePath( );
175         if ( !Files.exists( itemPath ) )
176         {
177             throw new ItemNotFoundException( "The item " + item.toString() + "does not exist in the repository " + getId( ) );
178         }
179         if ( !itemPath.toAbsolutePath().startsWith( baseDirectory.toAbsolutePath() ) )
180         {
181             log.error( "The namespace {} to delete from repository {} is not a subdirectory of the repository base.", item, getId( ) );
182             log.error( "Namespace directory: {}", itemPath );
183             log.error( "Repository directory: {}", baseDirectory );
184             throw new ContentAccessException( "Inconsistent directories found. Could not delete namespace." );
185         }
186         try
187         {
188             if (Files.isDirectory( itemPath ))
189             {
190                 FileUtils.deleteDirectory( itemPath );
191             } else {
192                 Files.deleteIfExists( itemPath );
193             }
194         }
195         catch ( IOException e )
196         {
197             log.error( "Could not delete namespace directory {}: {}", itemPath, e.getMessage( ), e );
198             throw new ContentAccessException( "Error occured while deleting namespace " + item + ": " + e.getMessage( ), e );
199         }
200     }
201
202     @Override
203     public ContentItem getItem( ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
204     {
205         if (selector.hasVersion() && selector.hasArtifactId()) {
206             return getArtifact( selector );
207         } else if (selector.hasProjectId() && selector.hasVersion()) {
208             return getVersion( selector );
209         } else if (selector.hasProjectId()) {
210             return getProject( selector );
211         } else {
212             return getNamespace( selector );
213         }
214     }
215
216     @Override
217     public Namespace getNamespace( final ItemSelector namespaceSelector ) throws ContentAccessException, IllegalArgumentException
218     {
219         return namespaceMap.computeIfAbsent( namespaceSelector.getNamespace(),
220             namespace -> {
221                 StorageAsset nsPath = getAsset( namespace );
222                 return ArchivaNamespace.withRepository( this ).withAsset( nsPath ).
223                     withNamespace( namespace ).build( );
224             });
225     }
226
227
228     @Override
229     public Project getProject( final ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
230     {
231         if (!selector.hasProjectId()) {
232             throw new IllegalArgumentException( "Project id must be set" );
233         }
234         final StorageAsset path = getAsset( selector.getNamespace( ), selector.getProjectId( ) );
235         return projectMap.computeIfAbsent( path, projectPath -> {
236             final Namespace ns = getNamespace( selector );
237             return ArchivaProject.withAsset( projectPath ).withNamespace( ns ).withId( selector.getProjectId( ) ).build( );
238         }
239         );
240     }
241
242
243     @Override
244     public Version getVersion( final ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
245     {
246         if (!selector.hasProjectId()) {
247             throw new IllegalArgumentException( "Project id must be set" );
248         }
249         if (!selector.hasVersion() ) {
250             throw new IllegalArgumentException( "Version must be set" );
251         }
252         final StorageAsset path = getAsset(selector.getNamespace(), selector.getProjectId(), selector.getVersion());
253         return versionMap.computeIfAbsent( path, versionPath -> {
254             final Project project = getProject( selector );
255             return ArchivaVersion.withAsset( path )
256                 .withProject( project )
257                 .withVersion( selector.getVersion( ) ).build();
258         } );
259     }
260
261
262
263     public Artifact createArtifact(final StorageAsset artifactPath, final ItemSelector selector,
264         final String classifier, final String extension) {
265         Version version = getVersion(selector);
266         ArtifactOptBuilder builder = org.apache.archiva.repository.content.base.ArchivaArtifact.withAsset( artifactPath )
267             .withVersion( version )
268             .withId( selector.getArtifactId( ) )
269             .withArtifactVersion( mavenContentHelper.getArtifactVersion( artifactPath, selector ) )
270             .withClassifier( classifier );
271         if (selector.hasType()) {
272             builder.withType( selector.getType( ) );
273         }
274         return builder.build( );
275     }
276
277     public Namespace getNamespaceFromArtifactPath( final StorageAsset artifactPath) {
278         final StorageAsset namespacePath = artifactPath.getParent( ).getParent( ).getParent( );
279         final String namespace = MavenContentHelper.getNamespaceFromNamespacePath( namespacePath );
280         return namespaceMap.computeIfAbsent( namespace,
281             myNamespace -> ArchivaNamespace.withRepository( this )
282                 .withAsset( namespacePath )
283                 .withNamespace( namespace )
284                 .build( ) );
285     }
286
287     private Project getProjectFromArtifactPath( final StorageAsset artifactPath) {
288         final StorageAsset projectPath = artifactPath.getParent( ).getParent( );
289         return projectMap.computeIfAbsent( projectPath,
290             myProjectPath -> ArchivaProject.withAsset( projectPath )
291                 .withNamespace( getNamespaceFromArtifactPath( artifactPath ) )
292                 .withId( projectPath.getName( ) ).build( )
293         );
294     }
295
296     private Version getVersionFromArtifactPath( final StorageAsset artifactPath) {
297         final StorageAsset versionPath = artifactPath.getParent( );
298         return versionMap.computeIfAbsent( versionPath,
299             myVersionPath -> ArchivaVersion.withAsset( versionPath )
300                 .withProject( getProjectFromArtifactPath( artifactPath ) )
301                 .withVersion( versionPath.getName( ) ).build( ) );
302     }
303
304     private Artifact getArtifactFromPath(final StorageAsset artifactPath) {
305         final Version version = getVersionFromArtifactPath( artifactPath );
306         final ArtifactInfo info  = getArtifactInfoFromPath( version.getVersion(), artifactPath );
307         return artifactMap.computeIfAbsent( artifactPath, myArtifactPath ->
308             org.apache.archiva.repository.content.base.ArchivaArtifact.withAsset( artifactPath )
309                 .withVersion( version )
310                 .withId( info.id )
311                 .withClassifier( info.classifier )
312                 .withRemainder( info.remainder )
313                 .withType( info.type )
314                 .withArtifactVersion( info.version )
315                 .withContentType( info.contentType )
316                 .build( )
317         );
318     }
319
320     private ContentItem getItemFromPath(final StorageAsset itemPath) {
321         if (itemPath.isLeaf()) {
322             return getArtifactFromPath( itemPath );
323         } else {
324             if (versionMap.containsKey( itemPath )) {
325                 return versionMap.get( itemPath );
326             }
327             if (projectMap.containsKey( itemPath )) {
328                 return projectMap.get( itemPath );
329             }
330             String ns = MavenContentHelper.getNamespaceFromNamespacePath( itemPath );
331             if (namespaceMap.containsKey( ns )) {
332                 return namespaceMap.get( ns );
333             }
334             // No cached item, so we have to gather more information:
335             // Check for version directory (contains at least a pom or metadata file)
336             if (itemPath.list( ).stream( ).map(a -> a.getName().toLowerCase()).anyMatch( n ->
337                 n.endsWith( ".pom" )
338                 || n.startsWith( "maven-metadata" )
339             )) {
340                 return versionMap.computeIfAbsent( itemPath,
341                     myVersionPath -> ArchivaVersion.withAsset( itemPath )
342                         .withProject( (Project)getItemFromPath( itemPath.getParent() ) )
343                         .withVersion( itemPath.getName() ).build());
344             } else {
345                 // We have to dig further and find the next directory with a pom
346                 Optional<StorageAsset> foundFile = StorageUtil.newAssetStream( itemPath )
347                     .filter( a -> a.getName().toLowerCase().endsWith( ".pom" )
348                         || a.getName().toLowerCase().startsWith( "maven-metadata" ) )
349                     .findFirst( );
350                 if (foundFile.isPresent())
351                 {
352                     int level = 0;
353                     StorageAsset current = foundFile.get( );
354                     while (current.hasParent() && !current.equals(itemPath)) {
355                         level++;
356                         current = current.getParent( );
357                     }
358                     // Project path if it is one level up from the found file
359                     if (level==2) {
360                         return projectMap.computeIfAbsent( itemPath,
361                             myItemPath -> getProjectFromArtifactPath( foundFile.get( ) ) );
362                     } else {
363                         // All other paths are treated as namespace
364                         return namespaceMap.computeIfAbsent( ns,
365                             myNamespace -> ArchivaNamespace.withRepository( this )
366                                 .withAsset( itemPath )
367                                 .withNamespace( ns )
368                                 .build( ) );
369                     }
370                 } else {
371                     // Don't know what to do with it, so we treat it as namespace path
372                     return namespaceMap.computeIfAbsent( ns,
373                         myNamespace -> ArchivaNamespace.withRepository( this )
374                             .withAsset( itemPath )
375                             .withNamespace( ns )
376                             .build( ) );
377                 }
378
379             }
380         }
381     }
382
383     // Simple object to hold artifact information
384     private class ArtifactInfo  {
385         private String id;
386         private String version;
387         private String extension;
388         private String remainder;
389         private String type;
390         private String classifier;
391         private String contentType;
392     }
393
394     private ArtifactInfo getArtifactInfoFromPath(String genericVersion, StorageAsset path) {
395         final ArtifactInfo info = new ArtifactInfo( );
396         info.id = path.getParent( ).getParent( ).getName( );
397         final String fileName = path.getName( );
398         if ( genericVersion.endsWith( "-" + SNAPSHOT ) )
399         {
400             String baseVersion = StringUtils.substringBeforeLast( genericVersion, "-" + SNAPSHOT );
401             String prefix = info.id+"-"+baseVersion+"-";
402             if (fileName.startsWith( prefix ))
403             {
404                 String versionPostfix = StringUtils.removeStart( fileName, prefix );
405                 Matcher matcher = UNIQUE_SNAPSHOT_PATTERN.matcher( versionPostfix );
406                 if (matcher.matches()) {
407                     info.version = baseVersion + "-" + matcher.group( 1 );
408                     String newPrefix = prefix + info.version;
409                     if (fileName.startsWith( newPrefix ))
410                     {
411                         String classPostfix = StringUtils.removeStart( fileName, newPrefix );
412                         Matcher cMatch = CLASSIFIER_PATTERN.matcher( classPostfix );
413                         if (cMatch.matches()) {
414                             info.classifier = cMatch.group( 1 );
415                             info.remainder = cMatch.group( 2 );
416                         } else {
417                             info.classifier = "";
418                             info.remainder = classPostfix;
419                         }
420                     } else {
421                         log.error( "Artifact does not match the maven name pattern {}", path );
422                         info.classifier = "";
423                         info.remainder = StringUtils.substringAfter( fileName, prefix );
424                     }
425                 } else {
426                     log.error( "Artifact does not match the snapshot version pattern {}", path );
427                     info.version = "";
428                     info.classifier = "";
429                     info.remainder = StringUtils.substringAfter( fileName, prefix );
430                 }
431             } else {
432                 log.error( "Artifact does not match the maven name pattern: {}", path );
433                 info.version = "";
434                 info.classifier = "";
435                 info.remainder = StringUtils.substringAfterLast( fileName, "." );
436             }
437         } else {
438             String prefix = info.id+"-"+genericVersion;
439             if (fileName.startsWith( prefix ))
440             {
441                 info.version=genericVersion;
442                 String classPostfix = StringUtils.removeStart( fileName, prefix );
443                 Matcher cMatch = CLASSIFIER_PATTERN.matcher( classPostfix );
444                 if (cMatch.matches()) {
445                     info.classifier = cMatch.group( 1 );
446                     info.remainder = cMatch.group( 2 );
447                 } else {
448                     info.classifier = "";
449                     info.remainder = classPostfix;
450                 }
451             } else {
452                 log.error( "Artifact does not match the version pattern {}", path );
453                 info.version = "";
454                 info.classifier = "";
455                 info.remainder = StringUtils.substringAfterLast( fileName, "." );
456             }
457         }
458         info.extension = StringUtils.substringAfterLast( fileName, "." );
459         info.type = MavenContentHelper.getTypeFromClassifierAndExtension( info.classifier, info.extension );
460         try {
461             info.contentType = Files.probeContentType( path.getFilePath( ) );
462         } catch (IOException e) {
463             info.contentType = "";
464             //
465         }
466         return info;
467
468     }
469
470     @Override
471     public Artifact getArtifact( final ItemSelector selector ) throws ContentAccessException
472     {
473         if (!selector.hasProjectId( )) {
474             throw new IllegalArgumentException( "Project id must be set" );
475         }
476         if (!selector.hasVersion( )) {
477             throw new IllegalArgumentException( "Version must be set" );
478         }
479         if (!selector.hasArtifactId( )) {
480             throw new IllegalArgumentException( "Artifact Id must be set" );
481         }
482         final StorageAsset artifactDir = getAsset(selector.getNamespace(), selector.getProjectId(),
483             selector.getVersion());
484         final String artifactVersion = mavenContentHelper.getArtifactVersion( artifactDir, selector );
485         final String classifier = MavenContentHelper.getClassifier( selector );
486         final String extension = MavenContentHelper.getArtifactExtension( selector );
487         final String artifactId = StringUtils.isEmpty( selector.getArtifactId( ) ) ? selector.getProjectId( ) : selector.getArtifactId( );
488         final String fileName = MavenContentHelper.getArtifactFileName( artifactId, artifactVersion, classifier, extension );
489         final StorageAsset path = getAsset( selector.getNamespace( ), selector.getProjectId( ),
490             selector.getVersion( ), fileName );
491         return artifactMap.computeIfAbsent( path, artifactPath -> createArtifact( path, selector, classifier, extension ) );
492     }
493
494     private StorageAsset getBasePathFromSelector(ItemSelector selector) {
495         StringBuilder path = new StringBuilder( );
496         if (selector.hasNamespace()) {
497             path.append(String.join( "/", getNamespace( selector ).getNamespacePath( ) ));
498         }
499         if (selector.hasProjectId()) {
500             path.append( "/" ).append( selector.getProjectId( ) );
501         }
502         if (selector.hasVersion()) {
503             path.append( "/" ).append( selector.getVersion( ) );
504         }
505         return getStorage( ).getAsset( path.toString( ) );
506     }
507
508     /*
509      * File filter to select certain artifacts using the selector data.
510      */
511     private Predicate<StorageAsset> getFileFilterFromSelector(final ItemSelector selector) {
512         Predicate<StorageAsset> p = a -> a.isLeaf( );
513         if (selector.hasArtifactId()) {
514             final String pattern = selector.getArtifactId( );
515             p = p.and( a -> StringUtils.startsWithIgnoreCase( a.getName( ),  pattern ) );
516         }
517         if (selector.hasArtifactVersion()) {
518             final String pattern = selector.getArtifactVersion( );
519             p = p.and( a -> StringUtils.containsIgnoreCase( a.getName( ),  pattern ) );
520         }
521         if (selector.hasExtension()) {
522             final String pattern = "."+selector.getExtension( );
523             p = p.and( a -> StringUtils.endsWithIgnoreCase( a.getName( ), pattern ) );
524         } else if (selector.hasType()) {
525             final String pattern = "."+ MavenContentHelper.getArtifactExtension( selector );
526             p = p.and( a -> StringUtils.endsWithIgnoreCase( a.getName( ), pattern ) );
527         }
528         if (selector.hasClassifier()) {
529             final String pattern = "-" + selector.getClassifier( ) + ".";
530             p = p.and( a -> StringUtils.containsIgnoreCase( a.getName( ), pattern ) );
531         } else if (selector.hasType()) {
532             final String pattern = "-" + MavenContentHelper.getClassifierFromType( selector.getType( ) ) + ".";
533             p = p.and( a -> StringUtils.containsIgnoreCase( a.getName( ).toLowerCase( ), pattern ) );
534         }
535         return p;
536     }
537
538     /*
539         TBD
540      */
541     @Override
542     public List<? extends Artifact> getAllArtifacts( ItemSelector selector ) throws ContentAccessException
543     {
544         return null;
545     }
546
547     /*
548         TBD
549      */
550     @Override
551     public Stream<? extends Artifact> getAllArtifactStream( ItemSelector selector ) throws ContentAccessException
552     {
553         return null;
554     }
555
556     /*
557         TBD
558      */
559     @Override
560     public List<? extends Project> getProjects( Namespace namespace )
561     {
562         return null;
563     }
564
565     /*
566         TBD
567      */
568     @Override
569     public List<? extends Version> getVersions( Project project )
570     {
571         return null;
572     }
573
574     /*
575         TBD
576      */
577     @Override
578     public List<? extends Artifact> getArtifacts( ContentItem item )
579     {
580         return null;
581     }
582
583     /*
584         TBD
585      */
586     @Override
587     public List<? extends Artifact> getArtifacts( Namespace namespace, boolean recurse )
588     {
589         return null;
590     }
591
592     /*
593         TBD
594      */
595     @Override
596     public Stream<? extends Artifact> getArtifactStream( ContentItem item )
597     {
598         return null;
599     }
600
601     /*
602         TBD
603      */
604     @Override
605     public Stream<? extends Artifact> getArtifactStream( Namespace namespace, boolean recurse )
606     {
607         return null;
608     }
609
610     /*
611         TBD
612      */
613     @Override
614     public boolean hasContent( ItemSelector selector )
615     {
616         return false;
617     }
618
619     /*
620         TBD
621      */
622     @Override
623     public void copyArtifact( Path sourceFile, ContentItem destination ) throws IllegalArgumentException
624     {
625
626     }
627
628     /**
629      * TBD
630      * @param path the path string that points to the item
631      * @return
632      * @throws LayoutException
633      */
634     @Override
635     public ContentItem toItem( String path ) throws LayoutException
636     {
637         ItemSelector selector = getPathParser( ).toItemSelector( path );
638         return getItem( selector );
639     }
640
641     @Override
642     public ContentItem toItem( StorageAsset assetPath ) throws LayoutException
643     {
644         return toItem( assetPath.getPath( ) );
645     }
646
647     /// ************* End of new generation interface ******************
648
649     /**
650      * Returns a version reference from the coordinates
651      * @param groupId the group id
652      * @param artifactId the artifact id
653      * @param version the version
654      * @return the versioned reference object
655      */
656     @Override
657     public VersionedReference toVersion( String groupId, String artifactId, String version ) {
658         return new VersionedReference().groupId( groupId ).artifactId( artifactId ).version( version );
659     }
660
661     @Override
662     public VersionedReference toGenericVersion( ArtifactReference artifactReference )
663     {
664         return toVersion( artifactReference.getGroupId( ), artifactReference.getArtifactId( ), VersionUtil.getBaseVersion( artifactReference.getVersion( ) ));
665     }
666
667     /**
668      * Return the version the artifact is part of
669      * @param artifactReference
670      * @return
671      */
672     public VersionedReference toVersion( ArtifactReference artifactReference) {
673         return toVersion( artifactReference.getGroupId( ), artifactReference.getArtifactId( ), artifactReference.getVersion( ) );
674     }
675
676     @Override
677     public ArtifactReference toArtifact( String groupId, String artifactId, String version, String type, String classifier) {
678         return new ArtifactReference( ).groupId( groupId ).artifactId( artifactId ).version( version ).type( type ).classifier( classifier );
679     }
680
681
682     @Override
683     public void deleteVersion( VersionedReference ref ) throws ContentNotFoundException, ContentAccessException
684     {
685         final String path = toPath( ref );
686         final Path deleteTarget = getRepoDir().resolve(path);
687         if ( !Files.exists(deleteTarget) )
688         {
689             log.warn( "Version path for repository {} does not exist: {}", getId(), deleteTarget );
690             throw new ContentNotFoundException( "Version not found for repository "+getId()+": "+path );
691         }
692         if ( Files.isDirectory(deleteTarget) )
693         {
694             try
695             {
696                 org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
697             }
698             catch ( IOException e )
699             {
700                 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
701                 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
702             }
703         } else {
704             log.warn( "Version path for repository {} is not a directory {}", getId(), deleteTarget );
705             throw new ContentNotFoundException( "Version path for repository "+getId()+" is not directory: " + path );
706         }
707     }
708
709     @Override
710     public void deleteProject( ProjectReference ref )
711         throws ContentNotFoundException, ContentAccessException
712     {
713         final String path = toPath( ref );
714         final Path deleteTarget = getRepoDir( ).resolve( path );
715         if ( !Files.exists(deleteTarget) )
716         {
717             log.warn( "Project path for repository {} does not exist: {}", getId(), deleteTarget );
718             throw new ContentNotFoundException( "Project not found for repository "+getId()+": "+path );
719         }
720         if ( Files.isDirectory(deleteTarget) )
721         {
722             try
723             {
724                 org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
725             }
726             catch ( IOException e )
727             {
728                 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
729                 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
730             }
731         }
732         else
733         {
734             log.warn( "Project path for repository {} is not a directory {}", getId(), deleteTarget );
735             throw new ContentNotFoundException( "Project path for repository "+getId()+" is not directory: " + path );
736         }
737
738     }
739
740     @Override
741     public void deleteProject( String namespace, String projectId ) throws ContentNotFoundException, ContentAccessException
742     {
743         this.deleteProject( new ProjectReference().groupId( namespace ).artifactId( projectId ) );
744     }
745
746     @Override
747     public void deleteArtifact( ArtifactReference ref ) throws ContentNotFoundException, ContentAccessException
748     {
749         final String path = toPath( ref );
750         final Path repoDir = getRepoDir( );
751         Path deleteTarget = repoDir.resolve( path );
752         if ( Files.exists(deleteTarget) )
753         {
754             try
755             {
756                 if (Files.isDirectory( deleteTarget ))
757                 {
758                     org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
759                 } else {
760                     Files.delete( deleteTarget );
761                 }
762             }
763             catch ( IOException e )
764             {
765                 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
766                 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
767             }
768         } else {
769             log.warn( "Artifact path for repository {} does not exist: {}", getId(), deleteTarget );
770             throw new ContentNotFoundException( "Artifact not found for repository "+getId()+": "+path );
771         }
772
773     }
774
775     @Override
776     public void deleteGroupId( String groupId )
777         throws ContentNotFoundException, ContentAccessException
778     {
779         final String path = toPath( groupId );
780         final Path deleteTarget = getRepoDir( ).resolve( path );
781         if (!Files.exists(deleteTarget)) {
782             log.warn( "Namespace path for repository {} does not exist: {}", getId(), deleteTarget );
783             throw new ContentNotFoundException( "Namespace not found for repository "+getId()+": "+path );
784         }
785         if ( Files.isDirectory(deleteTarget) )
786         {
787             try
788             {
789                 org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
790             }
791             catch ( IOException e )
792             {
793                 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
794                 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
795             }
796         } else {
797             log.warn( "Namespace path for repository {} is not a directory {}", getId(), deleteTarget );
798             throw new ContentNotFoundException( "Namespace path for repository "+getId()+" is not directory: " + path );
799
800         }
801     }
802
803     @Override
804     public String getId()
805     {
806         return repository.getId();
807     }
808
809     @Override
810     public List<ArtifactReference> getRelatedArtifacts( VersionedReference reference )
811         throws ContentNotFoundException, LayoutException, ContentAccessException
812     {
813         StorageAsset artifactDir = toFile( reference );
814         if ( !artifactDir.exists())
815         {
816             throw new ContentNotFoundException(
817                 "Unable to get related artifacts using a non-existant directory: " + artifactDir.getPath() );
818         }
819
820         if ( !artifactDir.isContainer() )
821         {
822             throw new ContentNotFoundException(
823                 "Unable to get related artifacts using a non-directory: " + artifactDir.getPath() );
824         }
825
826         // First gather up the versions found as artifacts in the managed repository.
827
828         try (Stream<? extends StorageAsset> stream = artifactDir.list().stream() ) {
829             return stream.filter(asset -> !asset.isContainer()).map(path -> {
830                 try {
831                     ArtifactReference artifact = toArtifactReference(path.getPath());
832                     if( artifact.getGroupId().equals( reference.getGroupId() ) && artifact.getArtifactId().equals(
833                             reference.getArtifactId() ) && artifact.getVersion().equals( reference.getVersion() )) {
834                         return artifact;
835                     } else {
836                         return null;
837                     }
838                 } catch (LayoutException e) {
839                     log.debug( "Not processing file that is not an artifact: {}", e.getMessage() );
840                     return null;
841                 }
842             }).filter(Objects::nonNull).collect(Collectors.toList());
843         } catch (RuntimeException e) {
844             Throwable cause = e.getCause( );
845             if (cause!=null) {
846                 if (cause instanceof LayoutException) {
847                     throw (LayoutException)cause;
848                 } else
849                 {
850                     throw new ContentAccessException( cause.getMessage( ), cause );
851                 }
852             } else {
853                 throw new ContentAccessException( e.getMessage( ), e );
854             }
855         }
856     }
857
858     /*
859      * Create the filter for various combinations of classifier and type
860      */
861     private Predicate<ArtifactReference> getChecker(ArtifactReference referenceObject, String extension) {
862         // TODO: Check, if extension is the correct parameter here
863         // We compare type with extension which works for artifacts like .jar.md5 but may
864         // be not the best way.
865
866         if (referenceObject.getClassifier()!=null && referenceObject.getType()!=null) {
867             return ((ArtifactReference a) ->
868                 referenceObject.getGroupId().equals( a.getGroupId() )
869                 && referenceObject.getArtifactId().equals( a.getArtifactId() )
870                 && referenceObject.getVersion( ).equals( a.getVersion( ) )
871                 && ( (a.getType()==null)
872                     || referenceObject.getType().equals( a.getType() )
873                     || a.getType().startsWith(extension) )
874                 && referenceObject.getClassifier().equals( a.getClassifier() )
875             );
876         } else if (referenceObject.getClassifier()!=null && referenceObject.getType()==null){
877             return ((ArtifactReference a) ->
878                 referenceObject.getGroupId().equals( a.getGroupId() )
879                     && referenceObject.getArtifactId().equals( a.getArtifactId() )
880                     && referenceObject.getVersion( ).equals( a.getVersion( ) )
881                     && referenceObject.getClassifier().equals( a.getClassifier() )
882             );
883         } else if (referenceObject.getClassifier()==null && referenceObject.getType()!=null){
884             return ((ArtifactReference a) ->
885                 referenceObject.getGroupId().equals( a.getGroupId() )
886                     && referenceObject.getArtifactId().equals( a.getArtifactId() )
887                     && referenceObject.getVersion( ).equals( a.getVersion( ) )
888                     && ( (a.getType()==null)
889                     || referenceObject.getType().equals( a.getType() )
890                     || a.getType().startsWith(extension) )
891             );
892         } else {
893             return ((ArtifactReference a) ->
894                 referenceObject.getGroupId().equals( a.getGroupId() )
895                     && referenceObject.getArtifactId().equals( a.getArtifactId() )
896                     && referenceObject.getVersion( ).equals( a.getVersion( ) )
897             );
898         }
899
900
901     }
902
903     @Override
904     public List<ArtifactReference> getRelatedArtifacts( ArtifactReference reference )
905         throws ContentNotFoundException, LayoutException, ContentAccessException
906     {
907         if ( StringUtils.isEmpty( reference.getType() ) && StringUtils.isEmpty( reference.getClassifier() ) ) {
908             return getRelatedArtifacts( toVersion( reference ) );
909         }
910
911         StorageAsset artifactFile = toFile( reference );
912         StorageAsset repoDir = artifactFile.getParent();
913         String ext;
914         if (!artifactFile.isContainer()) {
915             ext = StringUtils.substringAfterLast( artifactFile.getName(), ".");
916         } else {
917             ext = "";
918         }
919
920         if ( !repoDir.exists())
921         {
922             throw new ContentNotFoundException(
923                 "Unable to get related artifacts using a non-existant directory: " + repoDir.getPath() );
924         }
925
926         if ( !repoDir.isContainer() )
927         {
928             throw new ContentNotFoundException(
929                 "Unable to get related artifacts using a non-directory: " + repoDir.getPath() );
930         }
931
932         // First gather up the versions found as artifacts in the managed repository.
933
934         try (Stream<? extends StorageAsset> stream = repoDir.list().stream() ) {
935             return stream.filter(
936                 asset -> !asset.isContainer())
937                 .map(path -> {
938                 try {
939                     return toArtifactReference(path.getPath());
940                 } catch (LayoutException e) {
941                     log.debug( "Not processing file that is not an artifact: {}", e.getMessage() );
942                     return null;
943                 }
944             }).filter(Objects::nonNull).filter(getChecker( reference, ext )).collect(Collectors.toList());
945         } catch (RuntimeException e) {
946             Throwable cause = e.getCause( );
947             if (cause!=null) {
948                 if (cause instanceof LayoutException) {
949                     throw (LayoutException)cause;
950                 } else
951                 {
952                     throw new ContentAccessException( cause.getMessage( ), cause );
953                 }
954             } else {
955                 throw new ContentAccessException( e.getMessage( ), e );
956             }
957         }
958     }
959
960     @Override
961     public List<StorageAsset> getRelatedAssets( ArtifactReference reference ) throws ContentNotFoundException, LayoutException, ContentAccessException
962     {
963         return null;
964     }
965
966     @Override
967     public String getRepoRoot()
968     {
969         return convertUriToPath( repository.getLocation() );
970     }
971
972     private String convertUriToPath( URI uri ) {
973         if (uri.getScheme()==null) {
974             return Paths.get(uri.getPath()).toString();
975         } else if ("file".equals(uri.getScheme())) {
976             return Paths.get(uri).toString();
977         } else {
978             return uri.toString();
979         }
980     }
981
982     @Override
983     public ManagedRepository getRepository()
984     {
985         return repository;
986     }
987
988     /**
989      * Gather the Available Versions (on disk) for a specific Project Reference, based on filesystem
990      * information.
991      *
992      * @return the Set of available versions, based on the project reference.
993      * @throws LayoutException
994      */
995     @Override
996     public Set<String> getVersions( ProjectReference reference )
997         throws ContentNotFoundException, LayoutException, ContentAccessException
998     {
999         final String path = toPath( reference );
1000         final Path projDir = getRepoDir().resolve(toPath(reference));
1001         if ( !Files.exists(projDir) )
1002         {
1003             throw new ContentNotFoundException(
1004                 "Unable to get Versions on a non-existant directory for repository "+getId()+": " + path );
1005         }
1006
1007         if ( !Files.isDirectory(projDir) )
1008         {
1009             throw new ContentNotFoundException(
1010                 "Unable to get Versions on a non-directory for repository "+getId()+": " + path );
1011         }
1012
1013         final String groupId = reference.getGroupId();
1014         final String artifactId = reference.getArtifactId();
1015         try(Stream<Path> stream = Files.list(projDir)) {
1016             return stream.filter(Files::isDirectory).map(
1017                     p -> toVersion(groupId, artifactId, p.getFileName().toString())
1018             ).filter(this::hasArtifact).map(ref -> ref.getVersion())
1019                     .collect(Collectors.toSet());
1020         } catch (IOException e) {
1021             log.error("Could not read directory {}: {}", projDir, e.getMessage(), e);
1022             throw new ContentAccessException( "Could not read path for repository "+getId()+": "+ path, e );
1023         } catch (RuntimeException e) {
1024             Throwable cause = e.getCause( );
1025             if (cause!=null)
1026             {
1027                 if ( cause instanceof LayoutException )
1028                 {
1029                     throw (LayoutException) cause;
1030                 } else {
1031                     log.error("Could not read directory {}: {}", projDir, cause.getMessage(), cause);
1032                     throw new ContentAccessException( "Could not read path for repository "+getId()+": "+ path, cause );
1033                 }
1034             } else {
1035                 log.error("Could not read directory {}: {}", projDir, e.getMessage(), e);
1036                 throw new ContentAccessException( "Could not read path for repository "+getId()+": "+ path, cause );
1037             }
1038         }
1039     }
1040
1041     @Override
1042     public Set<String> getVersions( VersionedReference reference )
1043         throws ContentNotFoundException, ContentAccessException, LayoutException
1044     {
1045         try(Stream<ArtifactReference> stream = getArtifactStream( reference ))
1046         {
1047             return stream.filter( Objects::nonNull )
1048                 .map( ar -> ar.getVersion( ) )
1049                 .collect( Collectors.toSet( ) );
1050         } catch (IOException e) {
1051             final String path = toPath( reference );
1052             log.error("Could not read directory from repository {} - {}: ", getId(), path, e.getMessage(), e);
1053             throw new ContentAccessException( "Could not read path for repository "+getId()+": "+ path, e );
1054         }
1055     }
1056
1057     @Override
1058     public boolean hasContent( ArtifactReference reference ) throws ContentAccessException
1059     {
1060         StorageAsset artifactFile = toFile( reference );
1061         return artifactFile.exists() && !artifactFile.isContainer();
1062     }
1063
1064     @Override
1065     public boolean hasContent( ProjectReference reference ) throws ContentAccessException
1066     {
1067         try
1068         {
1069             Set<String> versions = getVersions( reference );
1070             return !versions.isEmpty();
1071         }
1072         catch ( ContentNotFoundException | LayoutException e )
1073         {
1074             return false;
1075         }
1076     }
1077
1078     @Override
1079     public boolean hasContent( VersionedReference reference ) throws ContentAccessException
1080     {
1081         try
1082         {
1083             return ( getFirstArtifact( reference ) != null );
1084         }
1085         catch ( LayoutException | ContentNotFoundException e )
1086         {
1087             return false;
1088         }
1089         catch ( IOException e )
1090         {
1091             String path = toPath( reference );
1092             log.error("Could not read directory from repository {} - {}: ", getId(), path, e.getMessage(), e);
1093             throw new ContentAccessException( "Could not read path from repository " + getId( ) + ": " + path, e );
1094         }
1095     }
1096
1097     @Override
1098     public void setRepository( final ManagedRepository repo )
1099     {
1100         this.repository = repo;
1101         if (repo!=null) {
1102             if (repository instanceof EditableManagedRepository) {
1103                 ((EditableManagedRepository) repository).setContent(this);
1104             }
1105         }
1106     }
1107
1108     private Path getRepoDir() {
1109         return repository.getAsset( "" ).getFilePath( );
1110     }
1111
1112     private RepositoryStorage getStorage() {
1113         return repository.getAsset( "" ).getStorage( );
1114     }
1115
1116     /**
1117      * Convert a path to an artifact reference.
1118      *
1119      * @param path the path to convert. (relative or full location path)
1120      * @throws LayoutException if the path cannot be converted to an artifact reference.
1121      */
1122     @Override
1123     public ArtifactReference toArtifactReference( String path )
1124         throws LayoutException
1125     {
1126         String repoPath = convertUriToPath( repository.getLocation() );
1127         if ( ( path != null ) && path.startsWith( repoPath ) && repoPath.length() > 0 )
1128         {
1129             return super.toArtifactReference( path.substring( repoPath.length() + 1 ) );
1130         } else {
1131             repoPath = path;
1132             if (repoPath!=null) {
1133                 while (repoPath.startsWith("/")) {
1134                     repoPath = repoPath.substring(1);
1135                 }
1136             }
1137             return super.toArtifactReference( repoPath );
1138         }
1139     }
1140
1141
1142     // The variant with runtime exception for stream usage
1143     private ArtifactReference toArtifactRef(String path) {
1144         try {
1145             return toArtifactReference(path);
1146         } catch (LayoutException e) {
1147             throw new RuntimeException(e);
1148         }
1149     }
1150
1151
1152
1153     @Override
1154     public StorageAsset toFile( ArtifactReference reference )
1155     {
1156         return repository.getAsset(toPath(reference));
1157     }
1158
1159     @Override
1160     public StorageAsset toFile( ArchivaArtifact reference )
1161     {
1162         return repository.getAsset( toPath( reference ) );
1163     }
1164
1165     @Override
1166     public StorageAsset toFile( VersionedReference reference )
1167     {
1168         return repository.getAsset( toPath( reference ) );
1169     }
1170
1171     /**
1172      * Get the first Artifact found in the provided VersionedReference location.
1173      *
1174      * @param reference the reference to the versioned reference to search within
1175      * @return the ArtifactReference to the first artifact located within the versioned reference. or null if
1176      *         no artifact was found within the versioned reference.
1177      * @throws java.io.IOException     if the versioned reference is invalid (example: doesn't exist, or isn't a directory)
1178      * @throws LayoutException
1179      */
1180     private ArtifactReference getFirstArtifact( VersionedReference reference )
1181         throws ContentNotFoundException, LayoutException, IOException
1182     {
1183         try(Stream<ArtifactReference> stream = getArtifactStream( reference ))
1184         {
1185             return stream.findFirst( ).orElse( null );
1186         } catch (RuntimeException e) {
1187             throw new ContentNotFoundException( e.getMessage( ), e.getCause( ) );
1188         }
1189     }
1190
1191     private Stream<ArtifactReference> getArtifactStream(VersionedReference reference) throws ContentNotFoundException, LayoutException, IOException {
1192         final Path repoBase = getRepoDir( );
1193         String path = toMetadataPath( reference );
1194         Path versionDir = repoBase.resolve( path ).getParent();
1195         if ( !Files.exists(versionDir) )
1196         {
1197             throw new ContentNotFoundException( "Unable to gather the list of artifacts on a non-existant directory: "
1198                 + versionDir.toAbsolutePath() );
1199         }
1200
1201         if ( !Files.isDirectory(versionDir) )
1202         {
1203             throw new ContentNotFoundException(
1204                 "Unable to gather the list of snapshot versions on a non-directory: " + versionDir.toAbsolutePath() );
1205         }
1206         return Files.list(versionDir).filter(Files::isRegularFile)
1207                 .map(p -> repoBase.relativize(p).toString())
1208                 .filter(p -> !filetypes.matchesDefaultExclusions(p))
1209                 .filter(filetypes::matchesArtifactPattern)
1210                 .map(this::toArtifactRef);
1211     }
1212
1213     public List<ArtifactReference> getArtifacts(VersionedReference reference) throws ContentNotFoundException, LayoutException, ContentAccessException
1214     {
1215         try (Stream<ArtifactReference> stream = getArtifactStream( reference ))
1216         {
1217             return stream.collect( Collectors.toList( ) );
1218         } catch ( IOException e )
1219         {
1220             String path = toPath( reference );
1221             log.error("Could not read directory from repository {} - {}: ", getId(), path, e.getMessage(), e);
1222             throw new ContentAccessException( "Could not read path from repository " + getId( ) + ": " + path, e );
1223
1224         }
1225     }
1226
1227     private boolean hasArtifact( VersionedReference reference )
1228
1229     {
1230         try(Stream<ArtifactReference> stream = getArtifactStream( reference ))
1231         {
1232             return stream.anyMatch( e -> true );
1233         } catch (ContentNotFoundException e) {
1234             return false;
1235         } catch ( LayoutException | IOException e) {
1236             // We throw the runtime exception for better stream handling
1237             throw new RuntimeException(e);
1238         }
1239     }
1240
1241     public void setFiletypes( FileTypes filetypes )
1242     {
1243         this.filetypes = filetypes;
1244     }
1245
1246     public void setMavenContentHelper( MavenContentHelper contentHelper) {
1247         this.mavenContentHelper = contentHelper;
1248     }
1249
1250
1251 }