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