1 package org.apache.archiva.repository.maven.content;
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
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
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.metadata.maven.MavenMetadataReader;
26 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
27 import org.apache.archiva.model.ArchivaArtifact;
28 import org.apache.archiva.model.ArtifactReference;
29 import org.apache.archiva.model.ProjectReference;
30 import org.apache.archiva.model.VersionedReference;
31 import org.apache.archiva.repository.ContentAccessException;
32 import org.apache.archiva.repository.ContentNotFoundException;
33 import org.apache.archiva.repository.EditableManagedRepository;
34 import org.apache.archiva.repository.LayoutException;
35 import org.apache.archiva.repository.ManagedRepository;
36 import org.apache.archiva.repository.ManagedRepositoryContent;
37 import org.apache.archiva.repository.content.Artifact;
38 import org.apache.archiva.repository.content.ContentItem;
39 import org.apache.archiva.repository.content.ItemNotFoundException;
40 import org.apache.archiva.repository.content.ItemSelector;
41 import org.apache.archiva.repository.content.Namespace;
42 import org.apache.archiva.repository.content.Project;
43 import org.apache.archiva.repository.content.Version;
44 import org.apache.archiva.repository.content.base.ArchivaItemSelector;
45 import org.apache.archiva.repository.content.base.ArchivaNamespace;
46 import org.apache.archiva.repository.content.base.ArchivaProject;
47 import org.apache.archiva.repository.content.base.ArchivaVersion;
48 import org.apache.archiva.repository.content.base.builder.ArtifactOptBuilder;
49 import org.apache.archiva.repository.maven.metadata.storage.ArtifactMappingProvider;
50 import org.apache.archiva.repository.maven.metadata.storage.DefaultArtifactMappingProvider;
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;
57 import javax.inject.Inject;
58 import javax.inject.Named;
59 import javax.naming.Name;
60 import java.io.IOException;
62 import java.nio.file.Files;
63 import java.nio.file.Path;
64 import java.nio.file.Paths;
65 import java.util.Collections;
66 import java.util.List;
67 import java.util.Objects;
68 import java.util.Optional;
70 import java.util.function.Predicate;
71 import java.util.regex.Matcher;
72 import java.util.regex.Pattern;
73 import java.util.stream.Collectors;
74 import java.util.stream.Stream;
75 import java.util.stream.StreamSupport;
78 * ManagedDefaultRepositoryContent
80 public class ManagedDefaultRepositoryContent
81 extends AbstractDefaultRepositoryContent
82 implements ManagedRepositoryContent
85 // attribute flag that marks version objects that point to a snapshot artifact version
86 public static final String SNAPSHOT_ARTIFACT_VERSION = "maven.snav";
88 private FileTypes filetypes;
90 public void setFileTypes(FileTypes fileTypes) {
91 this.filetypes = fileTypes;
94 private ManagedRepository repository;
96 private FileLockManager lockManager;
99 @Named("repositoryPathTranslator#maven2")
100 private RepositoryPathTranslator pathTranslator;
103 @Named( "metadataReader#maven" )
104 MavenMetadataReader metadataReader;
107 @Named( "MavenContentHelper" )
108 MavenContentHelper mavenContentHelper;
110 public static final String SNAPSHOT = "SNAPSHOT";
112 public static final Pattern UNIQUE_SNAPSHOT_PATTERN = Pattern.compile( "^(SNAPSHOT|[0-9]{8}\\.[0-9]{6}-[0-9]+)(.*)" );
113 public static final Pattern CLASSIFIER_PATTERN = Pattern.compile( "^-([^.]+)(\\..*)" );
115 public static final Pattern TIMESTAMP_PATTERN = Pattern.compile( "^([0-9]{8})\\.([0-9]{6})$" );
117 public static final Pattern GENERIC_SNAPSHOT_PATTERN = Pattern.compile( "^(.*)-" + SNAPSHOT );
120 * We are caching content items in a weak reference map. To avoid always recreating the
121 * the hierarchical structure.
122 * TODO: Better use a object cache? E.g. our spring cache implementation?
124 private ReferenceMap<String, Namespace> namespaceMap = new ReferenceMap<>( );
125 private ReferenceMap<StorageAsset, Project> projectMap = new ReferenceMap<>( );
126 private ReferenceMap<StorageAsset, Version> versionMap = new ReferenceMap<>( );
127 private ReferenceMap<StorageAsset, Artifact> artifactMap = new ReferenceMap<>( );
129 public ManagedDefaultRepositoryContent() {
130 super(Collections.singletonList( new DefaultArtifactMappingProvider() ));
133 public ManagedDefaultRepositoryContent(ManagedRepository repository, FileTypes fileTypes, FileLockManager lockManager) {
134 super(Collections.singletonList( new DefaultArtifactMappingProvider() ));
135 setFileTypes( fileTypes );
136 this.lockManager = lockManager;
137 setRepository( repository );
140 public ManagedDefaultRepositoryContent( ManagedRepository repository, List<? extends ArtifactMappingProvider> artifactMappingProviders, FileTypes fileTypes, FileLockManager lockManager )
142 super(artifactMappingProviders==null ? Collections.singletonList( new DefaultArtifactMappingProvider() ) : artifactMappingProviders);
143 setFileTypes( fileTypes );
144 this.lockManager = lockManager;
145 setRepository( repository );
149 private StorageAsset getAssetByPath(String assetPath) {
150 return getStorage( ).getAsset( assetPath );
153 private StorageAsset getAsset(String namespace) {
154 String namespacePath = formatAsDirectory( namespace.trim() );
155 if (StringUtils.isEmpty( namespacePath )) {
158 return getAssetByPath(namespacePath);
161 private StorageAsset getAsset(String namespace, String project) {
162 return getAsset( namespace ).resolve( project );
165 private StorageAsset getAsset(String namespace, String project, String version) {
166 return getAsset( namespace, project ).resolve( version );
169 private StorageAsset getAsset(String namespace, String project, String version, String fileName) {
170 return getAsset( namespace, project, version ).resolve( fileName );
174 /// ************* End of new generation interface ******************
176 public void deleteItem( ContentItem item ) throws ItemNotFoundException, ContentAccessException
178 final Path baseDirectory = getRepoDir( );
179 final Path itemPath = item.getAsset( ).getFilePath( );
180 if ( !Files.exists( itemPath ) )
182 throw new ItemNotFoundException( "The item " + item.toString() + "does not exist in the repository " + getId( ) );
184 if ( !itemPath.toAbsolutePath().startsWith( baseDirectory.toAbsolutePath() ) )
186 log.error( "The namespace {} to delete from repository {} is not a subdirectory of the repository base.", item, getId( ) );
187 log.error( "Namespace directory: {}", itemPath );
188 log.error( "Repository directory: {}", baseDirectory );
189 throw new ContentAccessException( "Inconsistent directories found. Could not delete namespace." );
193 if (Files.isDirectory( itemPath ))
195 FileUtils.deleteDirectory( itemPath );
197 Files.deleteIfExists( itemPath );
200 catch ( IOException e )
202 log.error( "Could not delete namespace directory {}: {}", itemPath, e.getMessage( ), e );
203 throw new ContentAccessException( "Error occured while deleting namespace " + item + ": " + e.getMessage( ), e );
208 public ContentItem getItem( ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
210 if (selector.hasVersion() && selector.hasArtifactId()) {
211 return getArtifact( selector );
212 } else if (selector.hasProjectId() && selector.hasVersion()) {
213 return getVersion( selector );
214 } else if (selector.hasProjectId()) {
215 return getProject( selector );
217 return getNamespace( selector );
222 public Namespace getNamespace( final ItemSelector namespaceSelector ) throws ContentAccessException, IllegalArgumentException
224 return namespaceMap.computeIfAbsent( namespaceSelector.getNamespace(),
226 StorageAsset nsPath = getAsset( namespace );
227 return ArchivaNamespace.withRepository( this ).withAsset( nsPath ).
228 withNamespace( namespace ).build( );
234 public Project getProject( final ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
236 if (!selector.hasProjectId()) {
237 throw new IllegalArgumentException( "Project id must be set" );
239 final StorageAsset path = getAsset( selector.getNamespace( ), selector.getProjectId( ) );
240 return projectMap.computeIfAbsent( path, projectPath -> {
241 final Namespace ns = getNamespace( selector );
242 return ArchivaProject.withAsset( projectPath ).withNamespace( ns ).withId( selector.getProjectId( ) ).build( );
249 public Version getVersion( final ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
251 if (!selector.hasProjectId()) {
252 throw new IllegalArgumentException( "Project id must be set" );
254 if (!selector.hasVersion() ) {
255 throw new IllegalArgumentException( "Version must be set" );
257 final StorageAsset path = getAsset(selector.getNamespace(), selector.getProjectId(), selector.getVersion());
258 return versionMap.computeIfAbsent( path, versionPath -> {
259 final Project project = getProject( selector );
260 return ArchivaVersion.withAsset( path )
261 .withProject( project )
262 .withVersion( selector.getVersion( ) ).build();
270 public Artifact createArtifact(final StorageAsset artifactPath, final ItemSelector selector,
271 final String classifier, final String extension) {
272 Version version = getVersion(selector);
273 ArtifactOptBuilder builder = org.apache.archiva.repository.content.base.ArchivaArtifact.withAsset( artifactPath )
274 .withVersion( version )
275 .withId( selector.getArtifactId( ) )
276 .withArtifactVersion( mavenContentHelper.getArtifactVersion( artifactPath, selector ) )
277 .withClassifier( classifier );
278 if (selector.hasType()) {
279 builder.withType( selector.getType( ) );
281 return builder.build( );
284 public Namespace getNamespaceFromArtifactPath( final StorageAsset artifactPath) {
285 final StorageAsset namespacePath = artifactPath.getParent( ).getParent( ).getParent( );
286 final String namespace = MavenContentHelper.getNamespaceFromNamespacePath( namespacePath );
287 return namespaceMap.computeIfAbsent( namespace,
288 myNamespace -> ArchivaNamespace.withRepository( this )
289 .withAsset( namespacePath )
290 .withNamespace( namespace )
294 private Project getProjectFromArtifactPath( final StorageAsset artifactPath) {
295 final StorageAsset projectPath = artifactPath.getParent( ).getParent( );
296 return projectMap.computeIfAbsent( projectPath,
297 myProjectPath -> ArchivaProject.withAsset( projectPath )
298 .withNamespace( getNamespaceFromArtifactPath( artifactPath ) )
299 .withId( projectPath.getName( ) ).build( )
303 private Version getVersionFromArtifactPath( final StorageAsset artifactPath) {
304 final StorageAsset versionPath = artifactPath.getParent( );
305 return versionMap.computeIfAbsent( versionPath,
306 myVersionPath -> ArchivaVersion.withAsset( versionPath )
307 .withProject( getProjectFromArtifactPath( artifactPath ) )
308 .withVersion( versionPath.getName( ) ).build( ) );
311 private Artifact getArtifactFromPath(final StorageAsset artifactPath) {
312 final Version version = getVersionFromArtifactPath( artifactPath );
313 final ArtifactInfo info = getArtifactInfoFromPath( version.getVersion(), artifactPath );
314 return artifactMap.computeIfAbsent( artifactPath, myArtifactPath ->
315 org.apache.archiva.repository.content.base.ArchivaArtifact.withAsset( artifactPath )
316 .withVersion( version )
318 .withClassifier( info.classifier )
319 .withRemainder( info.remainder )
320 .withType( info.type )
321 .withArtifactVersion( info.version )
322 .withContentType( info.contentType )
327 private ContentItem getItemFromPath(final StorageAsset itemPath) {
328 if (itemPath.isLeaf()) {
329 return getArtifactFromPath( itemPath );
331 if (versionMap.containsKey( itemPath )) {
332 return versionMap.get( itemPath );
334 if (projectMap.containsKey( itemPath )) {
335 return projectMap.get( itemPath );
337 String ns = MavenContentHelper.getNamespaceFromNamespacePath( itemPath );
338 if (namespaceMap.containsKey( ns )) {
339 return namespaceMap.get( ns );
341 // No cached item, so we have to gather more information:
342 // Check for version directory (contains at least a pom or metadata file)
343 if (itemPath.list( ).stream( ).map(a -> a.getName().toLowerCase()).anyMatch( n ->
345 || n.startsWith( "maven-metadata" )
347 return versionMap.computeIfAbsent( itemPath,
348 myVersionPath -> ArchivaVersion.withAsset( itemPath )
349 .withProject( (Project)getItemFromPath( itemPath.getParent() ) )
350 .withVersion( itemPath.getName() ).build());
352 // We have to dig further and find the next directory with a pom
353 Optional<StorageAsset> foundFile = StorageUtil.newAssetStream( itemPath )
354 .filter( a -> a.getName().toLowerCase().endsWith( ".pom" )
355 || a.getName().toLowerCase().startsWith( "maven-metadata" ) )
357 if (foundFile.isPresent())
360 StorageAsset current = foundFile.get( );
361 while (current.hasParent() && !current.equals(itemPath)) {
363 current = current.getParent( );
365 // Project path if it is one level up from the found file
367 return projectMap.computeIfAbsent( itemPath,
368 myItemPath -> getProjectFromArtifactPath( foundFile.get( ) ) );
370 // All other paths are treated as namespace
371 return namespaceMap.computeIfAbsent( ns,
372 myNamespace -> ArchivaNamespace.withRepository( this )
373 .withAsset( itemPath )
378 // Don't know what to do with it, so we treat it as namespace path
379 return namespaceMap.computeIfAbsent( ns,
380 myNamespace -> ArchivaNamespace.withRepository( this )
381 .withAsset( itemPath )
390 // Simple object to hold artifact information
391 private class ArtifactInfo {
393 private String version;
394 private String extension;
395 private String remainder;
397 private String classifier;
398 private String contentType;
399 private StorageAsset asset;
402 private ArtifactInfo getArtifactInfoFromPath(String genericVersion, StorageAsset path) {
403 final ArtifactInfo info = new ArtifactInfo( );
405 info.id = path.getParent( ).getParent( ).getName( );
406 final String fileName = path.getName( );
407 if ( genericVersion.endsWith( "-" + SNAPSHOT ) )
409 String baseVersion = StringUtils.substringBeforeLast( genericVersion, "-" + SNAPSHOT );
410 String prefix = info.id+"-"+baseVersion+"-";
411 if (fileName.startsWith( prefix ))
413 String versionPostfix = StringUtils.removeStart( fileName, prefix );
414 Matcher matcher = UNIQUE_SNAPSHOT_PATTERN.matcher( versionPostfix );
415 if (matcher.matches()) {
416 info.version = baseVersion + "-" + matcher.group( 1 );
417 String newPrefix = prefix + info.version;
418 if (fileName.startsWith( newPrefix ))
420 String classPostfix = StringUtils.removeStart( fileName, newPrefix );
421 Matcher cMatch = CLASSIFIER_PATTERN.matcher( classPostfix );
422 if (cMatch.matches()) {
423 info.classifier = cMatch.group( 1 );
424 info.remainder = cMatch.group( 2 );
426 info.classifier = "";
427 info.remainder = classPostfix;
430 log.error( "Artifact does not match the maven name pattern {}", path );
431 info.classifier = "";
432 info.remainder = StringUtils.substringAfter( fileName, prefix );
435 log.error( "Artifact does not match the snapshot version pattern {}", path );
437 info.classifier = "";
438 info.remainder = StringUtils.substringAfter( fileName, prefix );
441 log.error( "Artifact does not match the maven name pattern: {}", path );
443 info.classifier = "";
444 info.remainder = StringUtils.substringAfterLast( fileName, "." );
447 String prefix = info.id+"-"+genericVersion;
448 if (fileName.startsWith( prefix ))
450 info.version=genericVersion;
451 String classPostfix = StringUtils.removeStart( fileName, prefix );
452 Matcher cMatch = CLASSIFIER_PATTERN.matcher( classPostfix );
453 if (cMatch.matches()) {
454 info.classifier = cMatch.group( 1 );
455 info.remainder = cMatch.group( 2 );
457 info.classifier = "";
458 info.remainder = classPostfix;
461 log.error( "Artifact does not match the version pattern {}", path );
463 info.classifier = "";
464 info.remainder = StringUtils.substringAfterLast( fileName, "." );
467 info.extension = StringUtils.substringAfterLast( fileName, "." );
468 info.type = MavenContentHelper.getTypeFromClassifierAndExtension( info.classifier, info.extension );
470 info.contentType = Files.probeContentType( path.getFilePath( ) );
471 } catch (IOException e) {
472 info.contentType = "";
480 public Artifact getArtifact( final ItemSelector selector ) throws ContentAccessException
482 if (!selector.hasProjectId( )) {
483 throw new IllegalArgumentException( "Project id must be set" );
485 if (!selector.hasVersion( )) {
486 throw new IllegalArgumentException( "Version must be set" );
488 if (!selector.hasArtifactId( )) {
489 throw new IllegalArgumentException( "Artifact Id must be set" );
491 final StorageAsset artifactDir = getAsset(selector.getNamespace(), selector.getProjectId(),
492 selector.getVersion());
493 final String artifactVersion = mavenContentHelper.getArtifactVersion( artifactDir, selector );
494 final String classifier = MavenContentHelper.getClassifier( selector );
495 final String extension = MavenContentHelper.getArtifactExtension( selector );
496 final String artifactId = StringUtils.isEmpty( selector.getArtifactId( ) ) ? selector.getProjectId( ) : selector.getArtifactId( );
497 final String fileName = MavenContentHelper.getArtifactFileName( artifactId, artifactVersion, classifier, extension );
498 final StorageAsset path = getAsset( selector.getNamespace( ), selector.getProjectId( ),
499 selector.getVersion( ), fileName );
500 return artifactMap.computeIfAbsent( path, artifactPath -> createArtifact( path, selector, classifier, extension ) );
503 private StorageAsset getBasePathFromSelector(ItemSelector selector) {
504 StringBuilder path = new StringBuilder( );
505 if (selector.hasNamespace()) {
506 path.append(String.join( "/", getNamespace( selector ).getNamespacePath( ) ));
508 if (selector.hasProjectId()) {
509 path.append( "/" ).append( selector.getProjectId( ) );
511 if (selector.hasVersion()) {
512 path.append( "/" ).append( selector.getVersion( ) );
514 return getStorage( ).getAsset( path.toString( ) );
519 * Returns all the subdirectories of the given namespace directory as project.
522 public List<? extends Project> getProjects( Namespace namespace )
524 return namespace.getAsset( ).list( ).stream( )
525 .filter( a -> a.isContainer( ) )
526 .map( a -> getProjectFromArtifactPath( a ) )
527 .collect( Collectors.toList());
531 public List<? extends Project> getProjects( ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
533 return getProjects( getNamespace( selector ) );
537 * Returns a version object for each directory that is a direct child of the project directory.
538 * @param project the project for which the versions should be returned
539 * @return the list of versions or a empty list, if not version was found
542 public List<? extends Version> getVersions( final Project project )
544 StorageAsset asset = getAsset( project.getNamespace( ).getNamespace( ), project.getId( ) );
545 return asset.list( ).stream( ).filter( a -> a.isContainer( ) )
546 .map( a -> ArchivaVersion.withAsset( a )
547 .withProject( project )
548 .withVersion( a.getName() ).build() )
549 .collect( Collectors.toList( ) );
553 * If the selector specifies a version, all artifact versions are returned, which means for snapshot
554 * versions the artifact versions are returned too.
556 * @param selector the item selector. At least namespace and projectId must be set.
557 * @return the list of version objects or a empty list, if the selector does not match a version
558 * @throws ContentAccessException if the access to the underlying backend failed
559 * @throws IllegalArgumentException if the selector has no projectId specified
562 public List<? extends Version> getVersions( final ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
564 if (StringUtils.isEmpty( selector.getProjectId() )) {
565 log.error( "Bad item selector for version list: {}", selector );
566 throw new IllegalArgumentException( "Project ID not set, while retrieving versions." );
568 final Project project = getProject( selector );
569 if (selector.hasVersion()) {
570 final StorageAsset asset = getAsset( selector.getNamespace( ), selector.getProjectId( ), selector.getVersion( ) );
571 return asset.list( ).stream( ).map( a -> getArtifactInfoFromPath( selector.getVersion( ), a ) )
572 .filter( ai -> StringUtils.isNotEmpty( ai.version ) )
573 .map( v -> ArchivaVersion.withAsset( v.asset.getParent() )
574 .withProject( project ).withVersion( v.version )
575 .withAttribute(SNAPSHOT_ARTIFACT_VERSION,"true").build() )
577 .collect( Collectors.toList( ) );
579 return getVersions( project );
585 * See {@link #newArtifactStream(ItemSelector)}. This method collects the stream into a list.
587 * @param selector the selector for the artifacts
588 * @return the list of artifacts
589 * @throws ContentAccessException if the access to the underlying filesystem failed
592 public List<? extends Artifact> getArtifacts( ItemSelector selector ) throws ContentAccessException
594 try(Stream<? extends Artifact> stream = newArtifactStream( selector )) {
595 return stream.collect( Collectors.toList());
601 * File filter to select certain artifacts using the selector data.
603 private Predicate<StorageAsset> getFileFilterFromSelector(final ItemSelector selector) {
604 Predicate<StorageAsset> p = a -> a.isLeaf( );
605 StringBuilder fileNamePattern = new StringBuilder("^" );
606 if (selector.hasArtifactId()) {
607 fileNamePattern.append( Pattern.quote(selector.getArtifactId( )) ).append("-");
609 fileNamePattern.append("[A-Za-z0-9_\\-.]+-");
611 if (selector.hasArtifactVersion()) {
612 fileNamePattern.append( Pattern.quote(selector.getArtifactVersion( )) );
614 fileNamePattern.append( "[A-Za-z0-9_\\-.]+" );
616 String classifier = selector.hasClassifier( ) ? selector.getClassifier( ) :
617 ( selector.hasType( ) ? MavenContentHelper.getClassifierFromType( selector.getType( ) ) : null );
618 if (classifier != null)
620 if ( "*".equals( classifier ) )
622 fileNamePattern.append( "-[A-Za-z0-9]+\\." );
626 fileNamePattern.append("-").append( Pattern.quote( classifier ) ).append( "\\." );
629 fileNamePattern.append( "\\." );
631 String extension = selector.hasExtension( ) ? selector.getExtension( ) :
632 ( selector.hasType( ) ? MavenContentHelper.getArtifactExtension( selector ) : null );
633 if (extension != null) {
634 if (selector.includeRelatedArtifacts())
636 fileNamePattern.append( Pattern.quote( extension ) ).append("(\\.[A-Za-z0-9]+)?");
638 fileNamePattern.append( Pattern.quote( extension ) );
641 fileNamePattern.append( "[A-Za-z0-9]+" );
643 final Pattern pattern = Pattern.compile( fileNamePattern.toString() );
644 return p.and( a -> pattern.matcher( a.getName( ) ).matches());
649 * Returns the artifacts. The number of artifacts returned depend on the selector.
650 * If the selector sets the flag {@link ItemSelector#includeRelatedArtifacts()} to <code>true</code>,
651 * additional to the matching artifacts, related artifacts like hash values or signatures are included in the artifact
653 * If the selector sets the flag {@link ItemSelector#recurse()} to <code>true</code>, artifacts of the given
654 * namespace and from all sub namespaces that start with the given namespace are returned.
656 * <li>If only a namespace is given, all artifacts with the given namespace or starting with the given
657 * namespace (see {@link ItemSelector#recurse()} are returned.</li>
658 * <li>If a namespace and a project id, or artifact id is given, the artifacts of all versions of the given
659 * namespace and project are returned.</li>
660 * <li>If a namespace and a project id or artifact id and a version is given, the artifacts of the given
661 * version are returned</li>
664 * There is no determinate order of the elements in the stream.
666 * Returned streams are auto closable and should be used in a try-with-resources statement.
668 * @param selector the item selector
669 * @throws ContentAccessException if the access to the underlying filesystem failed
672 public Stream<? extends Artifact> newArtifactStream( ItemSelector selector ) throws ContentAccessException
674 String projectId = selector.hasProjectId( ) ? selector.getProjectId( ) : ( selector.hasArtifactId( ) ? selector.getArtifactId( )
676 final Predicate<StorageAsset> filter = getFileFilterFromSelector( selector );
677 if (projectId!=null && selector.hasVersion()) {
678 return getAsset( selector.getNamespace( ), projectId, selector.getVersion( ) )
679 .list( ).stream( ).filter( filter )
680 .map( this::getArtifactFromPath );
681 } else if (projectId!=null) {
682 final StorageAsset projDir = getAsset( selector.getNamespace( ), projectId );
683 return projDir.list( ).stream( ).filter( StorageAsset::isContainer )
684 .map( StorageAsset::list )
685 .flatMap( List::stream )
687 .map( this::getArtifactFromPath );
690 StorageAsset namespaceDir = getAsset( selector.getNamespace( ) );
691 if (selector.recurse())
693 return StorageUtil.newAssetStream( namespaceDir, true )
695 .map( this::getArtifactFromPath );
698 return namespaceDir.list( ).stream( )
699 .filter( StorageAsset::isContainer )
700 .map( StorageAsset::list )
701 .flatMap( List::stream )
702 .filter( StorageAsset::isContainer )
703 .map( StorageAsset::list )
704 .flatMap( List::stream )
706 .map(this::getArtifactFromPath);
712 * Same as {@link #newArtifactStream(ContentItem)} but returns the collected stream as list.
714 * @param item the item the parent item
715 * @return the list of artifacts or a empty list of no artifacts where found
718 public List<? extends Artifact> getArtifacts( ContentItem item )
720 try(Stream<? extends Artifact> stream = newArtifactStream( item )) {
721 return stream.collect( Collectors.toList());
726 * Returns all artifacts
729 * @throws ContentAccessException
731 public Stream<? extends Artifact> newArtifactStream( Namespace item ) throws ContentAccessException
733 return newArtifactStream( ArchivaItemSelector.builder( ).withNamespace( item.getNamespace( ) ).build( ) );
736 public Stream<? extends Artifact> newArtifactStream( Project item ) throws ContentAccessException
738 return newArtifactStream( ArchivaItemSelector.builder( ).withNamespace( item.getNamespace( ).getNamespace() )
739 .withProjectId( item.getId() ).build( ) );
742 public Stream<? extends Artifact> newArtifactStream( Version item ) throws ContentAccessException
744 return newArtifactStream( ArchivaItemSelector.builder( ).withNamespace( item.getProject().getNamespace( ).getNamespace() )
745 .withProjectId( item.getProject().getId() )
746 .withVersion( item.getVersion() ).build( ) );
750 * Returns all related artifacts that match the given artifact. That means all artifacts that have
751 * the same filename plus an additional extension, e.g. ${fileName}.sha2
752 * @param item the artifact
753 * @return the stream of artifacts
754 * @throws ContentAccessException
756 public Stream<? extends Artifact> newArtifactStream( Artifact item ) throws ContentAccessException
758 final Version v = item.getVersion( );
759 final String fileName = item.getFileName( );
760 final Predicate<StorageAsset> filter = ( StorageAsset a ) ->
761 a.getName( ).startsWith( fileName + "." );
762 return v.getAsset( ).list( ).stream( ).filter( filter )
763 .map( a -> getArtifactFromPath( a ) );
766 * Returns the stream of artifacts that are children of the given item.
768 * @param item the item from where the artifacts should be returned
770 * @throws ContentAccessException
773 public Stream<? extends Artifact> newArtifactStream( ContentItem item ) throws ContentAccessException
775 if (item instanceof Namespace) {
776 return newArtifactStream( ( (Namespace) item ) );
777 } else if (item instanceof Project) {
778 return newArtifactStream( (Project) item );
779 } else if (item instanceof Version) {
780 return newArtifactStream( (Version) item );
781 } else if (item instanceof Artifact) {
782 return newArtifactStream( (Artifact) item );
785 log.warn( "newArtifactStream for unsupported item requested: {}", item.getClass( ).getName( ) );
786 return Stream.empty( );
791 * Checks, if the asset/file queried by the given selector exists.
794 public boolean hasContent( ItemSelector selector )
796 return getItem( selector ).getAsset( ).exists( );
803 public void copyArtifact( Path sourceFile, ContentItem destination ) throws IllegalArgumentException
810 * @param path the path string that points to the item
812 * @throws LayoutException
815 public ContentItem toItem( String path ) throws LayoutException
817 ItemSelector selector = getPathParser( ).toItemSelector( path );
818 return getItem( selector );
822 public ContentItem toItem( StorageAsset assetPath ) throws LayoutException
824 return toItem( assetPath.getPath( ) );
827 /// ************* End of new generation interface ******************
830 * Returns a version reference from the coordinates
831 * @param groupId the group id
832 * @param artifactId the artifact id
833 * @param version the version
834 * @return the versioned reference object
837 public VersionedReference toVersion( String groupId, String artifactId, String version ) {
838 return new VersionedReference().groupId( groupId ).artifactId( artifactId ).version( version );
842 public VersionedReference toGenericVersion( ArtifactReference artifactReference )
844 return toVersion( artifactReference.getGroupId( ), artifactReference.getArtifactId( ), VersionUtil.getBaseVersion( artifactReference.getVersion( ) ));
848 * Return the version the artifact is part of
849 * @param artifactReference
852 public VersionedReference toVersion( ArtifactReference artifactReference) {
853 return toVersion( artifactReference.getGroupId( ), artifactReference.getArtifactId( ), artifactReference.getVersion( ) );
857 public ArtifactReference toArtifact( String groupId, String artifactId, String version, String type, String classifier) {
858 return new ArtifactReference( ).groupId( groupId ).artifactId( artifactId ).version( version ).type( type ).classifier( classifier );
863 public void deleteVersion( VersionedReference ref ) throws ContentNotFoundException, ContentAccessException
865 final String path = toPath( ref );
866 final Path deleteTarget = getRepoDir().resolve(path);
867 if ( !Files.exists(deleteTarget) )
869 log.warn( "Version path for repository {} does not exist: {}", getId(), deleteTarget );
870 throw new ContentNotFoundException( "Version not found for repository "+getId()+": "+path );
872 if ( Files.isDirectory(deleteTarget) )
876 org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
878 catch ( IOException e )
880 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
881 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
884 log.warn( "Version path for repository {} is not a directory {}", getId(), deleteTarget );
885 throw new ContentNotFoundException( "Version path for repository "+getId()+" is not directory: " + path );
890 public void deleteProject( ProjectReference ref )
891 throws ContentNotFoundException, ContentAccessException
893 final String path = toPath( ref );
894 final Path deleteTarget = getRepoDir( ).resolve( path );
895 if ( !Files.exists(deleteTarget) )
897 log.warn( "Project path for repository {} does not exist: {}", getId(), deleteTarget );
898 throw new ContentNotFoundException( "Project not found for repository "+getId()+": "+path );
900 if ( Files.isDirectory(deleteTarget) )
904 org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
906 catch ( IOException e )
908 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
909 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
914 log.warn( "Project path for repository {} is not a directory {}", getId(), deleteTarget );
915 throw new ContentNotFoundException( "Project path for repository "+getId()+" is not directory: " + path );
921 public void deleteProject( String namespace, String projectId ) throws ContentNotFoundException, ContentAccessException
923 this.deleteProject( new ProjectReference().groupId( namespace ).artifactId( projectId ) );
927 public void deleteArtifact( ArtifactReference ref ) throws ContentNotFoundException, ContentAccessException
929 final String path = toPath( ref );
930 final Path repoDir = getRepoDir( );
931 Path deleteTarget = repoDir.resolve( path );
932 if ( Files.exists(deleteTarget) )
936 if (Files.isDirectory( deleteTarget ))
938 org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
940 Files.delete( deleteTarget );
943 catch ( IOException e )
945 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
946 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
949 log.warn( "Artifact path for repository {} does not exist: {}", getId(), deleteTarget );
950 throw new ContentNotFoundException( "Artifact not found for repository "+getId()+": "+path );
956 public void deleteGroupId( String groupId )
957 throws ContentNotFoundException, ContentAccessException
959 final String path = toPath( groupId );
960 final Path deleteTarget = getRepoDir( ).resolve( path );
961 if (!Files.exists(deleteTarget)) {
962 log.warn( "Namespace path for repository {} does not exist: {}", getId(), deleteTarget );
963 throw new ContentNotFoundException( "Namespace not found for repository "+getId()+": "+path );
965 if ( Files.isDirectory(deleteTarget) )
969 org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
971 catch ( IOException e )
973 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
974 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
977 log.warn( "Namespace path for repository {} is not a directory {}", getId(), deleteTarget );
978 throw new ContentNotFoundException( "Namespace path for repository "+getId()+" is not directory: " + path );
984 public String getId()
986 return repository.getId();
990 public List<ArtifactReference> getRelatedArtifacts( VersionedReference reference )
991 throws ContentNotFoundException, LayoutException, ContentAccessException
993 StorageAsset artifactDir = toFile( reference );
994 if ( !artifactDir.exists())
996 throw new ContentNotFoundException(
997 "Unable to get related artifacts using a non-existant directory: " + artifactDir.getPath() );
1000 if ( !artifactDir.isContainer() )
1002 throw new ContentNotFoundException(
1003 "Unable to get related artifacts using a non-directory: " + artifactDir.getPath() );
1006 // First gather up the versions found as artifacts in the managed repository.
1008 try (Stream<? extends StorageAsset> stream = artifactDir.list().stream() ) {
1009 return stream.filter(asset -> !asset.isContainer()).map(path -> {
1011 ArtifactReference artifact = toArtifactReference(path.getPath());
1012 if( artifact.getGroupId().equals( reference.getGroupId() ) && artifact.getArtifactId().equals(
1013 reference.getArtifactId() ) && artifact.getVersion().equals( reference.getVersion() )) {
1018 } catch (LayoutException e) {
1019 log.debug( "Not processing file that is not an artifact: {}", e.getMessage() );
1022 }).filter(Objects::nonNull).collect(Collectors.toList());
1023 } catch (RuntimeException e) {
1024 Throwable cause = e.getCause( );
1026 if (cause instanceof LayoutException) {
1027 throw (LayoutException)cause;
1030 throw new ContentAccessException( cause.getMessage( ), cause );
1033 throw new ContentAccessException( e.getMessage( ), e );
1039 * Create the filter for various combinations of classifier and type
1041 private Predicate<ArtifactReference> getChecker(ArtifactReference referenceObject, String extension) {
1042 // TODO: Check, if extension is the correct parameter here
1043 // We compare type with extension which works for artifacts like .jar.md5 but may
1044 // be not the best way.
1046 if (referenceObject.getClassifier()!=null && referenceObject.getType()!=null) {
1047 return ((ArtifactReference a) ->
1048 referenceObject.getGroupId().equals( a.getGroupId() )
1049 && referenceObject.getArtifactId().equals( a.getArtifactId() )
1050 && referenceObject.getVersion( ).equals( a.getVersion( ) )
1051 && ( (a.getType()==null)
1052 || referenceObject.getType().equals( a.getType() )
1053 || a.getType().startsWith(extension) )
1054 && referenceObject.getClassifier().equals( a.getClassifier() )
1056 } else if (referenceObject.getClassifier()!=null && referenceObject.getType()==null){
1057 return ((ArtifactReference a) ->
1058 referenceObject.getGroupId().equals( a.getGroupId() )
1059 && referenceObject.getArtifactId().equals( a.getArtifactId() )
1060 && referenceObject.getVersion( ).equals( a.getVersion( ) )
1061 && referenceObject.getClassifier().equals( a.getClassifier() )
1063 } else if (referenceObject.getClassifier()==null && referenceObject.getType()!=null){
1064 return ((ArtifactReference a) ->
1065 referenceObject.getGroupId().equals( a.getGroupId() )
1066 && referenceObject.getArtifactId().equals( a.getArtifactId() )
1067 && referenceObject.getVersion( ).equals( a.getVersion( ) )
1068 && ( (a.getType()==null)
1069 || referenceObject.getType().equals( a.getType() )
1070 || a.getType().startsWith(extension) )
1073 return ((ArtifactReference a) ->
1074 referenceObject.getGroupId().equals( a.getGroupId() )
1075 && referenceObject.getArtifactId().equals( a.getArtifactId() )
1076 && referenceObject.getVersion( ).equals( a.getVersion( ) )
1084 public List<ArtifactReference> getRelatedArtifacts( ArtifactReference reference )
1085 throws ContentNotFoundException, LayoutException, ContentAccessException
1087 if ( StringUtils.isEmpty( reference.getType() ) && StringUtils.isEmpty( reference.getClassifier() ) ) {
1088 return getRelatedArtifacts( toVersion( reference ) );
1091 StorageAsset artifactFile = toFile( reference );
1092 StorageAsset repoDir = artifactFile.getParent();
1094 if (!artifactFile.isContainer()) {
1095 ext = StringUtils.substringAfterLast( artifactFile.getName(), ".");
1100 if ( !repoDir.exists())
1102 throw new ContentNotFoundException(
1103 "Unable to get related artifacts using a non-existant directory: " + repoDir.getPath() );
1106 if ( !repoDir.isContainer() )
1108 throw new ContentNotFoundException(
1109 "Unable to get related artifacts using a non-directory: " + repoDir.getPath() );
1112 // First gather up the versions found as artifacts in the managed repository.
1114 try (Stream<? extends StorageAsset> stream = repoDir.list().stream() ) {
1115 return stream.filter(
1116 asset -> !asset.isContainer())
1119 return toArtifactReference(path.getPath());
1120 } catch (LayoutException e) {
1121 log.debug( "Not processing file that is not an artifact: {}", e.getMessage() );
1124 }).filter(Objects::nonNull).filter(getChecker( reference, ext )).collect(Collectors.toList());
1125 } catch (RuntimeException e) {
1126 Throwable cause = e.getCause( );
1128 if (cause instanceof LayoutException) {
1129 throw (LayoutException)cause;
1132 throw new ContentAccessException( cause.getMessage( ), cause );
1135 throw new ContentAccessException( e.getMessage( ), e );
1141 public List<StorageAsset> getRelatedAssets( ArtifactReference reference ) throws ContentNotFoundException, LayoutException, ContentAccessException
1147 public String getRepoRoot()
1149 return convertUriToPath( repository.getLocation() );
1152 private String convertUriToPath( URI uri ) {
1153 if (uri.getScheme()==null) {
1154 return Paths.get(uri.getPath()).toString();
1155 } else if ("file".equals(uri.getScheme())) {
1156 return Paths.get(uri).toString();
1158 return uri.toString();
1163 public ManagedRepository getRepository()
1169 * Gather the Available Versions (on disk) for a specific Project Reference, based on filesystem
1172 * @return the Set of available versions, based on the project reference.
1173 * @throws LayoutException
1176 public Set<String> getVersions( ProjectReference reference )
1177 throws ContentNotFoundException, LayoutException, ContentAccessException
1179 final String path = toPath( reference );
1180 final Path projDir = getRepoDir().resolve(toPath(reference));
1181 if ( !Files.exists(projDir) )
1183 throw new ContentNotFoundException(
1184 "Unable to get Versions on a non-existant directory for repository "+getId()+": " + path );
1187 if ( !Files.isDirectory(projDir) )
1189 throw new ContentNotFoundException(
1190 "Unable to get Versions on a non-directory for repository "+getId()+": " + path );
1193 final String groupId = reference.getGroupId();
1194 final String artifactId = reference.getArtifactId();
1195 try(Stream<Path> stream = Files.list(projDir)) {
1196 return stream.filter(Files::isDirectory).map(
1197 p -> toVersion(groupId, artifactId, p.getFileName().toString())
1198 ).filter(this::hasArtifact).map(ref -> ref.getVersion())
1199 .collect(Collectors.toSet());
1200 } catch (IOException e) {
1201 log.error("Could not read directory {}: {}", projDir, e.getMessage(), e);
1202 throw new ContentAccessException( "Could not read path for repository "+getId()+": "+ path, e );
1203 } catch (RuntimeException e) {
1204 Throwable cause = e.getCause( );
1207 if ( cause instanceof LayoutException )
1209 throw (LayoutException) cause;
1211 log.error("Could not read directory {}: {}", projDir, cause.getMessage(), cause);
1212 throw new ContentAccessException( "Could not read path for repository "+getId()+": "+ path, cause );
1215 log.error("Could not read directory {}: {}", projDir, e.getMessage(), e);
1216 throw new ContentAccessException( "Could not read path for repository "+getId()+": "+ path, cause );
1222 public Set<String> getVersions( VersionedReference reference )
1223 throws ContentNotFoundException, ContentAccessException, LayoutException
1225 try(Stream<ArtifactReference> stream = newArtifactStream( reference ))
1227 return stream.filter( Objects::nonNull )
1228 .map( ar -> ar.getVersion( ) )
1229 .collect( Collectors.toSet( ) );
1230 } catch (IOException e) {
1231 final String path = toPath( reference );
1232 log.error("Could not read directory from repository {} - {}: ", getId(), path, e.getMessage(), e);
1233 throw new ContentAccessException( "Could not read path for repository "+getId()+": "+ path, e );
1238 public boolean hasContent( ArtifactReference reference ) throws ContentAccessException
1240 StorageAsset artifactFile = toFile( reference );
1241 return artifactFile.exists() && !artifactFile.isContainer();
1245 public boolean hasContent( ProjectReference reference ) throws ContentAccessException
1249 Set<String> versions = getVersions( reference );
1250 return !versions.isEmpty();
1252 catch ( ContentNotFoundException | LayoutException e )
1259 public boolean hasContent( VersionedReference reference ) throws ContentAccessException
1263 return ( getFirstArtifact( reference ) != null );
1265 catch ( LayoutException | ContentNotFoundException e )
1269 catch ( IOException e )
1271 String path = toPath( reference );
1272 log.error("Could not read directory from repository {} - {}: ", getId(), path, e.getMessage(), e);
1273 throw new ContentAccessException( "Could not read path from repository " + getId( ) + ": " + path, e );
1278 public void setRepository( final ManagedRepository repo )
1280 this.repository = repo;
1282 if (repository instanceof EditableManagedRepository) {
1283 ((EditableManagedRepository) repository).setContent(this);
1288 private Path getRepoDir() {
1289 return repository.getAsset( "" ).getFilePath( );
1292 private RepositoryStorage getStorage() {
1293 return repository.getAsset( "" ).getStorage( );
1297 * Convert a path to an artifact reference.
1299 * @param path the path to convert. (relative or full location path)
1300 * @throws LayoutException if the path cannot be converted to an artifact reference.
1303 public ArtifactReference toArtifactReference( String path )
1304 throws LayoutException
1306 String repoPath = convertUriToPath( repository.getLocation() );
1307 if ( ( path != null ) && path.startsWith( repoPath ) && repoPath.length() > 0 )
1309 return super.toArtifactReference( path.substring( repoPath.length() + 1 ) );
1312 if (repoPath!=null) {
1313 while (repoPath.startsWith("/")) {
1314 repoPath = repoPath.substring(1);
1317 return super.toArtifactReference( repoPath );
1322 // The variant with runtime exception for stream usage
1323 private ArtifactReference toArtifactRef(String path) {
1325 return toArtifactReference(path);
1326 } catch (LayoutException e) {
1327 throw new RuntimeException(e);
1334 public StorageAsset toFile( ArtifactReference reference )
1336 return repository.getAsset(toPath(reference));
1340 public StorageAsset toFile( ArchivaArtifact reference )
1342 return repository.getAsset( toPath( reference ) );
1346 public StorageAsset toFile( VersionedReference reference )
1348 return repository.getAsset( toPath( reference ) );
1352 * Get the first Artifact found in the provided VersionedReference location.
1354 * @param reference the reference to the versioned reference to search within
1355 * @return the ArtifactReference to the first artifact located within the versioned reference. or null if
1356 * no artifact was found within the versioned reference.
1357 * @throws java.io.IOException if the versioned reference is invalid (example: doesn't exist, or isn't a directory)
1358 * @throws LayoutException
1360 private ArtifactReference getFirstArtifact( VersionedReference reference )
1361 throws ContentNotFoundException, LayoutException, IOException
1363 try(Stream<ArtifactReference> stream = newArtifactStream( reference ))
1365 return stream.findFirst( ).orElse( null );
1366 } catch (RuntimeException e) {
1367 throw new ContentNotFoundException( e.getMessage( ), e.getCause( ) );
1371 private Stream<ArtifactReference> newArtifactStream( VersionedReference reference) throws ContentNotFoundException, LayoutException, IOException {
1372 final Path repoBase = getRepoDir( );
1373 String path = toMetadataPath( reference );
1374 Path versionDir = repoBase.resolve( path ).getParent();
1375 if ( !Files.exists(versionDir) )
1377 throw new ContentNotFoundException( "Unable to gather the list of artifacts on a non-existant directory: "
1378 + versionDir.toAbsolutePath() );
1381 if ( !Files.isDirectory(versionDir) )
1383 throw new ContentNotFoundException(
1384 "Unable to gather the list of snapshot versions on a non-directory: " + versionDir.toAbsolutePath() );
1386 return Files.list(versionDir).filter(Files::isRegularFile)
1387 .map(p -> repoBase.relativize(p).toString())
1388 .filter(p -> !filetypes.matchesDefaultExclusions(p))
1389 .filter(filetypes::matchesArtifactPattern)
1390 .map(this::toArtifactRef);
1393 public List<ArtifactReference> getArtifacts(VersionedReference reference) throws ContentNotFoundException, LayoutException, ContentAccessException
1395 try (Stream<ArtifactReference> stream = newArtifactStream( reference ))
1397 return stream.collect( Collectors.toList( ) );
1398 } catch ( IOException e )
1400 String path = toPath( reference );
1401 log.error("Could not read directory from repository {} - {}: ", getId(), path, e.getMessage(), e);
1402 throw new ContentAccessException( "Could not read path from repository " + getId( ) + ": " + path, e );
1407 private boolean hasArtifact( VersionedReference reference )
1410 try(Stream<ArtifactReference> stream = newArtifactStream( reference ))
1412 return stream.anyMatch( e -> true );
1413 } catch (ContentNotFoundException e) {
1415 } catch ( LayoutException | IOException e) {
1416 // We throw the runtime exception for better stream handling
1417 throw new RuntimeException(e);
1421 public void setFiletypes( FileTypes filetypes )
1423 this.filetypes = filetypes;
1426 public void setMavenContentHelper( MavenContentHelper contentHelper) {
1427 this.mavenContentHelper = contentHelper;