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