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.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;
56 import javax.inject.Inject;
57 import javax.inject.Named;
58 import java.io.IOException;
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;
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;
75 * ManagedDefaultRepositoryContent
77 public class ManagedDefaultRepositoryContent
78 extends AbstractDefaultRepositoryContent
79 implements ManagedRepositoryContent
82 // attribute flag that marks version objects that point to a snapshot artifact version
83 public static final String SNAPSHOT_ARTIFACT_VERSION = "maven.snav";
85 private FileTypes filetypes;
87 public void setFileTypes(FileTypes fileTypes) {
88 this.filetypes = fileTypes;
91 private ManagedRepository repository;
93 private FileLockManager lockManager;
96 @Named("repositoryPathTranslator#maven2")
97 private RepositoryPathTranslator pathTranslator;
100 @Named( "metadataReader#maven" )
101 MavenMetadataReader metadataReader;
104 @Named( "MavenContentHelper" )
105 MavenContentHelper mavenContentHelper;
107 public static final String SNAPSHOT = "SNAPSHOT";
109 public static final Pattern UNIQUE_SNAPSHOT_PATTERN = Pattern.compile( "^(SNAPSHOT|[0-9]{8}\\.[0-9]{6}-[0-9]+)(.*)" );
110 public static final Pattern CLASSIFIER_PATTERN = Pattern.compile( "^-([^.]+)(\\..*)" );
112 public static final Pattern TIMESTAMP_PATTERN = Pattern.compile( "^([0-9]{8})\\.([0-9]{6})$" );
114 public static final Pattern GENERIC_SNAPSHOT_PATTERN = Pattern.compile( "^(.*)-" + SNAPSHOT );
117 * We are caching content items in a weak reference map. To avoid always recreating the
118 * the hierarchical structure.
119 * TODO: Better use a object cache? E.g. our spring cache implementation?
121 private ReferenceMap<String, Namespace> namespaceMap = new ReferenceMap<>( );
122 private ReferenceMap<StorageAsset, Project> projectMap = new ReferenceMap<>( );
123 private ReferenceMap<StorageAsset, Version> versionMap = new ReferenceMap<>( );
124 private ReferenceMap<StorageAsset, Artifact> artifactMap = new ReferenceMap<>( );
126 public ManagedDefaultRepositoryContent() {
127 super(Collections.singletonList( new DefaultArtifactMappingProvider() ));
130 public ManagedDefaultRepositoryContent(ManagedRepository repository, FileTypes fileTypes, FileLockManager lockManager) {
131 super(Collections.singletonList( new DefaultArtifactMappingProvider() ));
132 setFileTypes( fileTypes );
133 this.lockManager = lockManager;
134 setRepository( repository );
137 public ManagedDefaultRepositoryContent( ManagedRepository repository, List<? extends ArtifactMappingProvider> artifactMappingProviders, FileTypes fileTypes, FileLockManager lockManager )
139 super(artifactMappingProviders==null ? Collections.singletonList( new DefaultArtifactMappingProvider() ) : artifactMappingProviders);
140 setFileTypes( fileTypes );
141 this.lockManager = lockManager;
142 setRepository( repository );
146 private StorageAsset getAssetByPath(String assetPath) {
147 return getStorage( ).getAsset( assetPath );
150 private StorageAsset getAsset(String namespace) {
151 String namespacePath = formatAsDirectory( namespace.trim() );
152 if (StringUtils.isEmpty( namespacePath )) {
155 return getAssetByPath(namespacePath);
158 private StorageAsset getAsset(String namespace, String project) {
159 return getAsset( namespace ).resolve( project );
162 private StorageAsset getAsset(String namespace, String project, String version) {
163 return getAsset( namespace, project ).resolve( version );
166 private StorageAsset getAsset(String namespace, String project, String version, String fileName) {
167 return getAsset( namespace, project, version ).resolve( fileName );
171 /// ************* End of new generation interface ******************
173 public void deleteItem( ContentItem item ) throws ItemNotFoundException, ContentAccessException
175 final Path baseDirectory = getRepoDir( );
176 final Path itemPath = item.getAsset( ).getFilePath( );
177 if ( !Files.exists( itemPath ) )
179 throw new ItemNotFoundException( "The item " + item.toString() + "does not exist in the repository " + getId( ) );
181 if ( !itemPath.toAbsolutePath().startsWith( baseDirectory.toAbsolutePath() ) )
183 log.error( "The namespace {} to delete from repository {} is not a subdirectory of the repository base.", item, getId( ) );
184 log.error( "Namespace directory: {}", itemPath );
185 log.error( "Repository directory: {}", baseDirectory );
186 throw new ContentAccessException( "Inconsistent directories found. Could not delete namespace." );
190 if (Files.isDirectory( itemPath ))
192 FileUtils.deleteDirectory( itemPath );
194 Files.deleteIfExists( itemPath );
197 catch ( IOException e )
199 log.error( "Could not delete namespace directory {}: {}", itemPath, e.getMessage( ), e );
200 throw new ContentAccessException( "Error occured while deleting namespace " + item + ": " + e.getMessage( ), e );
205 public ContentItem getItem( ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
207 if (selector.hasVersion() && selector.hasArtifactId()) {
208 return getArtifact( selector );
209 } else if (selector.hasProjectId() && selector.hasVersion()) {
210 return getVersion( selector );
211 } else if (selector.hasProjectId()) {
212 return getProject( selector );
214 return getNamespace( selector );
219 public Namespace getNamespace( final ItemSelector namespaceSelector ) throws ContentAccessException, IllegalArgumentException
221 return namespaceMap.computeIfAbsent( namespaceSelector.getNamespace(),
223 StorageAsset nsPath = getAsset( namespace );
224 return ArchivaNamespace.withRepository( this ).withAsset( nsPath ).
225 withNamespace( namespace ).build( );
231 public Project getProject( final ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
233 if (!selector.hasProjectId()) {
234 throw new IllegalArgumentException( "Project id must be set" );
236 final StorageAsset path = getAsset( selector.getNamespace( ), selector.getProjectId( ) );
237 return projectMap.computeIfAbsent( path, projectPath -> {
238 final Namespace ns = getNamespace( selector );
239 return ArchivaProject.withAsset( projectPath ).withNamespace( ns ).withId( selector.getProjectId( ) ).build( );
246 public Version getVersion( final ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
248 if (!selector.hasProjectId()) {
249 throw new IllegalArgumentException( "Project id must be set" );
251 if (!selector.hasVersion() ) {
252 throw new IllegalArgumentException( "Version must be set" );
254 final StorageAsset path = getAsset(selector.getNamespace(), selector.getProjectId(), selector.getVersion());
255 return versionMap.computeIfAbsent( path, versionPath -> {
256 final Project project = getProject( selector );
257 return ArchivaVersion.withAsset( path )
258 .withProject( project )
259 .withVersion( selector.getVersion( ) ).build();
267 public Artifact createArtifact(final StorageAsset artifactPath, final ItemSelector selector,
268 final String classifier, final String extension) {
269 Version version = getVersion(selector);
270 ArtifactOptBuilder builder = org.apache.archiva.repository.content.base.ArchivaArtifact.withAsset( artifactPath )
271 .withVersion( version )
272 .withId( selector.getArtifactId( ) )
273 .withArtifactVersion( mavenContentHelper.getArtifactVersion( artifactPath, selector ) )
274 .withClassifier( classifier );
275 if (selector.hasType()) {
276 builder.withType( selector.getType( ) );
278 return builder.build( );
281 public Namespace getNamespaceFromArtifactPath( final StorageAsset artifactPath) {
282 final StorageAsset namespacePath = artifactPath.getParent( ).getParent( ).getParent( );
283 final String namespace = MavenContentHelper.getNamespaceFromNamespacePath( namespacePath );
284 return namespaceMap.computeIfAbsent( namespace,
285 myNamespace -> ArchivaNamespace.withRepository( this )
286 .withAsset( namespacePath )
287 .withNamespace( namespace )
291 private Project getProjectFromArtifactPath( final StorageAsset artifactPath) {
292 final StorageAsset projectPath = artifactPath.getParent( ).getParent( );
293 return projectMap.computeIfAbsent( projectPath,
294 myProjectPath -> ArchivaProject.withAsset( projectPath )
295 .withNamespace( getNamespaceFromArtifactPath( artifactPath ) )
296 .withId( projectPath.getName( ) ).build( )
300 private Version getVersionFromArtifactPath( final StorageAsset artifactPath) {
301 final StorageAsset versionPath = artifactPath.getParent( );
302 return versionMap.computeIfAbsent( versionPath,
303 myVersionPath -> ArchivaVersion.withAsset( versionPath )
304 .withProject( getProjectFromArtifactPath( artifactPath ) )
305 .withVersion( versionPath.getName( ) ).build( ) );
308 private Artifact getArtifactFromPath(final StorageAsset artifactPath) {
309 final Version version = getVersionFromArtifactPath( artifactPath );
310 final ArtifactInfo info = getArtifactInfoFromPath( version.getVersion(), artifactPath );
311 return artifactMap.computeIfAbsent( artifactPath, myArtifactPath ->
312 org.apache.archiva.repository.content.base.ArchivaArtifact.withAsset( artifactPath )
313 .withVersion( version )
315 .withClassifier( info.classifier )
316 .withRemainder( info.remainder )
317 .withType( info.type )
318 .withArtifactVersion( info.version )
319 .withContentType( info.contentType )
324 private ContentItem getItemFromPath(final StorageAsset itemPath) {
325 if (itemPath.isLeaf()) {
326 return getArtifactFromPath( itemPath );
328 if (versionMap.containsKey( itemPath )) {
329 return versionMap.get( itemPath );
331 if (projectMap.containsKey( itemPath )) {
332 return projectMap.get( itemPath );
334 String ns = MavenContentHelper.getNamespaceFromNamespacePath( itemPath );
335 if (namespaceMap.containsKey( ns )) {
336 return namespaceMap.get( ns );
338 // No cached item, so we have to gather more information:
339 // Check for version directory (contains at least a pom or metadata file)
340 if (itemPath.list( ).stream( ).map(a -> a.getName().toLowerCase()).anyMatch( n ->
342 || n.startsWith( "maven-metadata" )
344 return versionMap.computeIfAbsent( itemPath,
345 myVersionPath -> ArchivaVersion.withAsset( itemPath )
346 .withProject( (Project)getItemFromPath( itemPath.getParent() ) )
347 .withVersion( itemPath.getName() ).build());
349 // We have to dig further and find the next directory with a pom
350 Optional<StorageAsset> foundFile = StorageUtil.newAssetStream( itemPath )
351 .filter( a -> a.getName().toLowerCase().endsWith( ".pom" )
352 || a.getName().toLowerCase().startsWith( "maven-metadata" ) )
354 if (foundFile.isPresent())
357 StorageAsset current = foundFile.get( );
358 while (current.hasParent() && !current.equals(itemPath)) {
360 current = current.getParent( );
362 // Project path if it is one level up from the found file
364 return projectMap.computeIfAbsent( itemPath,
365 myItemPath -> getProjectFromArtifactPath( foundFile.get( ) ) );
367 // All other paths are treated as namespace
368 return namespaceMap.computeIfAbsent( ns,
369 myNamespace -> ArchivaNamespace.withRepository( this )
370 .withAsset( itemPath )
375 // Don't know what to do with it, so we treat it as namespace path
376 return namespaceMap.computeIfAbsent( ns,
377 myNamespace -> ArchivaNamespace.withRepository( this )
378 .withAsset( itemPath )
387 // Simple object to hold artifact information
388 private class ArtifactInfo {
390 private String version;
391 private String extension;
392 private String remainder;
394 private String classifier;
395 private String contentType;
396 private StorageAsset asset;
399 private ArtifactInfo getArtifactInfoFromPath(String genericVersion, StorageAsset path) {
400 final ArtifactInfo info = new ArtifactInfo( );
402 info.id = path.getParent( ).getParent( ).getName( );
403 final String fileName = path.getName( );
404 if ( genericVersion.endsWith( "-" + SNAPSHOT ) )
406 String baseVersion = StringUtils.substringBeforeLast( genericVersion, "-" + SNAPSHOT );
407 String prefix = info.id+"-"+baseVersion+"-";
408 if (fileName.startsWith( prefix ))
410 String versionPostfix = StringUtils.removeStart( fileName, prefix );
411 Matcher matcher = UNIQUE_SNAPSHOT_PATTERN.matcher( versionPostfix );
412 if (matcher.matches()) {
413 info.version = baseVersion + "-" + matcher.group( 1 );
414 String newPrefix = prefix + info.version;
415 if (fileName.startsWith( newPrefix ))
417 String classPostfix = StringUtils.removeStart( fileName, newPrefix );
418 Matcher cMatch = CLASSIFIER_PATTERN.matcher( classPostfix );
419 if (cMatch.matches()) {
420 info.classifier = cMatch.group( 1 );
421 info.remainder = cMatch.group( 2 );
423 info.classifier = "";
424 info.remainder = classPostfix;
427 log.error( "Artifact does not match the maven name pattern {}", path );
428 info.classifier = "";
429 info.remainder = StringUtils.substringAfter( fileName, prefix );
432 log.error( "Artifact does not match the snapshot version pattern {}", path );
434 info.classifier = "";
435 info.remainder = StringUtils.substringAfter( fileName, prefix );
438 log.error( "Artifact does not match the maven name pattern: {}", path );
440 info.classifier = "";
441 info.remainder = StringUtils.substringAfterLast( fileName, "." );
444 String prefix = info.id+"-"+genericVersion;
445 if (fileName.startsWith( prefix ))
447 info.version=genericVersion;
448 String classPostfix = StringUtils.removeStart( fileName, prefix );
449 Matcher cMatch = CLASSIFIER_PATTERN.matcher( classPostfix );
450 if (cMatch.matches()) {
451 info.classifier = cMatch.group( 1 );
452 info.remainder = cMatch.group( 2 );
454 info.classifier = "";
455 info.remainder = classPostfix;
458 log.error( "Artifact does not match the version pattern {}", path );
460 info.classifier = "";
461 info.remainder = StringUtils.substringAfterLast( fileName, "." );
464 info.extension = StringUtils.substringAfterLast( fileName, "." );
465 info.type = MavenContentHelper.getTypeFromClassifierAndExtension( info.classifier, info.extension );
467 info.contentType = Files.probeContentType( path.getFilePath( ) );
468 } catch (IOException e) {
469 info.contentType = "";
477 public Artifact getArtifact( final ItemSelector selector ) throws ContentAccessException
479 if (!selector.hasProjectId( )) {
480 throw new IllegalArgumentException( "Project id must be set" );
482 if (!selector.hasVersion( )) {
483 throw new IllegalArgumentException( "Version must be set" );
485 if (!selector.hasArtifactId( )) {
486 throw new IllegalArgumentException( "Artifact Id must be set" );
488 final StorageAsset artifactDir = getAsset(selector.getNamespace(), selector.getProjectId(),
489 selector.getVersion());
490 final String artifactVersion = mavenContentHelper.getArtifactVersion( artifactDir, selector );
491 final String classifier = MavenContentHelper.getClassifier( selector );
492 final String extension = MavenContentHelper.getArtifactExtension( selector );
493 final String artifactId = StringUtils.isEmpty( selector.getArtifactId( ) ) ? selector.getProjectId( ) : selector.getArtifactId( );
494 final String fileName = MavenContentHelper.getArtifactFileName( artifactId, artifactVersion, classifier, extension );
495 final StorageAsset path = getAsset( selector.getNamespace( ), selector.getProjectId( ),
496 selector.getVersion( ), fileName );
497 return artifactMap.computeIfAbsent( path, artifactPath -> createArtifact( path, selector, classifier, extension ) );
500 private StorageAsset getBasePathFromSelector(ItemSelector selector) {
501 StringBuilder path = new StringBuilder( );
502 if (selector.hasNamespace()) {
503 path.append(String.join( "/", getNamespace( selector ).getNamespacePath( ) ));
505 if (selector.hasProjectId()) {
506 path.append( "/" ).append( selector.getProjectId( ) );
508 if (selector.hasVersion()) {
509 path.append( "/" ).append( selector.getVersion( ) );
511 return getStorage( ).getAsset( path.toString( ) );
515 * File filter to select certain artifacts using the selector data.
517 private Predicate<StorageAsset> getFileFilterFromSelector(final ItemSelector selector) {
518 Predicate<StorageAsset> p = a -> a.isLeaf( );
519 StringBuilder fileNamePattern = new StringBuilder("^" );
520 if (selector.hasArtifactId()) {
521 fileNamePattern.append( Pattern.quote(selector.getArtifactId( )) ).append("-");
523 fileNamePattern.append("[A-Za-z0-9_\\-.]+-");
525 if (selector.hasArtifactVersion()) {
526 fileNamePattern.append( Pattern.quote(selector.getArtifactVersion( )) );
528 fileNamePattern.append( "[A-Za-z0-9_\\-.]+" );
530 String classifier = selector.hasClassifier( ) ? selector.getClassifier( ) :
531 ( selector.hasType( ) ? MavenContentHelper.getClassifierFromType( selector.getType( ) ) : null );
532 if (classifier != null)
534 if ( "*".equals( classifier ) )
536 fileNamePattern.append( "-[A-Za-z0-9]+\\." );
540 fileNamePattern.append("-").append( Pattern.quote( classifier ) ).append( "\\." );
543 fileNamePattern.append( "\\." );
545 String extension = selector.hasExtension( ) ? selector.getExtension( ) :
546 ( selector.hasType( ) ? MavenContentHelper.getArtifactExtension( selector ) : null );
547 if (extension != null) {
548 fileNamePattern.append( Pattern.quote( extension ) );
550 fileNamePattern.append( ".*" );
552 final Pattern pattern = Pattern.compile( fileNamePattern.toString() );
553 return p.and( a -> pattern.matcher( a.getName( ) ).matches());
558 * Returns all the subdirectories of the given namespace directory as project.
561 public List<? extends Project> getProjects( Namespace namespace )
563 return namespace.getAsset( ).list( ).stream( )
564 .filter( a -> a.isContainer( ) )
565 .map( a -> getProjectFromArtifactPath( a ) )
566 .collect( Collectors.toList());
570 public List<? extends Project> getProjects( ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
572 return getProjects( getNamespace( selector ) );
576 * Returns a version object for each directory that is a direct child of the project directory.
577 * @param project the project for which the versions should be returned
578 * @return the list of versions or a empty list, if not version was found
581 public List<? extends Version> getVersions( final Project project )
583 StorageAsset asset = getAsset( project.getNamespace( ).getNamespace( ), project.getId( ) );
584 return asset.list( ).stream( ).filter( a -> a.isContainer( ) )
585 .map( a -> ArchivaVersion.withAsset( a )
586 .withProject( project )
587 .withVersion( a.getName() ).build() )
588 .collect( Collectors.toList( ) );
592 * If the selector specifies a version, all artifact versions are returned, which means for snapshot
593 * versions the artifact versions are returned too.
595 * @param selector the item selector. At least namespace and projectId must be set.
596 * @return the list of version objects or a empty list, if the selector does not match a version
597 * @throws ContentAccessException if the access to the underlying backend failed
598 * @throws IllegalArgumentException if the selector has no projectId specified
601 public List<? extends Version> getVersions( final ItemSelector selector ) throws ContentAccessException, IllegalArgumentException
603 if (StringUtils.isEmpty( selector.getProjectId() )) {
604 log.error( "Bad item selector for version list: {}", selector );
605 throw new IllegalArgumentException( "Project ID not set, while retrieving versions." );
607 final Project project = getProject( selector );
608 if (selector.hasVersion()) {
609 final StorageAsset asset = getAsset( selector.getNamespace( ), selector.getProjectId( ), selector.getVersion( ) );
610 return asset.list( ).stream( ).map( a -> getArtifactInfoFromPath( selector.getVersion( ), a ) )
611 .filter( ai -> StringUtils.isNotEmpty( ai.version ) )
612 .map( v -> ArchivaVersion.withAsset( v.asset.getParent() )
613 .withProject( project ).withVersion( v.version )
614 .withAttribute(SNAPSHOT_ARTIFACT_VERSION,"true").build() )
616 .collect( Collectors.toList( ) );
618 return getVersions( project );
627 public List<? extends Artifact> getArtifacts( ItemSelector selector ) throws ContentAccessException
636 public Stream<? extends Artifact> getArtifactStream( ItemSelector selector ) throws ContentAccessException
645 public List<? extends Artifact> getArtifacts( ContentItem item )
655 public Stream<? extends Artifact> getArtifactStream( ContentItem item )
661 * Checks, if the asset/file queried by the given selector exists.
664 public boolean hasContent( ItemSelector selector )
666 return getItem( selector ).getAsset( ).exists( );
673 public void copyArtifact( Path sourceFile, ContentItem destination ) throws IllegalArgumentException
680 * @param path the path string that points to the item
682 * @throws LayoutException
685 public ContentItem toItem( String path ) throws LayoutException
687 ItemSelector selector = getPathParser( ).toItemSelector( path );
688 return getItem( selector );
692 public ContentItem toItem( StorageAsset assetPath ) throws LayoutException
694 return toItem( assetPath.getPath( ) );
697 /// ************* End of new generation interface ******************
700 * Returns a version reference from the coordinates
701 * @param groupId the group id
702 * @param artifactId the artifact id
703 * @param version the version
704 * @return the versioned reference object
707 public VersionedReference toVersion( String groupId, String artifactId, String version ) {
708 return new VersionedReference().groupId( groupId ).artifactId( artifactId ).version( version );
712 public VersionedReference toGenericVersion( ArtifactReference artifactReference )
714 return toVersion( artifactReference.getGroupId( ), artifactReference.getArtifactId( ), VersionUtil.getBaseVersion( artifactReference.getVersion( ) ));
718 * Return the version the artifact is part of
719 * @param artifactReference
722 public VersionedReference toVersion( ArtifactReference artifactReference) {
723 return toVersion( artifactReference.getGroupId( ), artifactReference.getArtifactId( ), artifactReference.getVersion( ) );
727 public ArtifactReference toArtifact( String groupId, String artifactId, String version, String type, String classifier) {
728 return new ArtifactReference( ).groupId( groupId ).artifactId( artifactId ).version( version ).type( type ).classifier( classifier );
733 public void deleteVersion( VersionedReference ref ) throws ContentNotFoundException, ContentAccessException
735 final String path = toPath( ref );
736 final Path deleteTarget = getRepoDir().resolve(path);
737 if ( !Files.exists(deleteTarget) )
739 log.warn( "Version path for repository {} does not exist: {}", getId(), deleteTarget );
740 throw new ContentNotFoundException( "Version not found for repository "+getId()+": "+path );
742 if ( Files.isDirectory(deleteTarget) )
746 org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
748 catch ( IOException e )
750 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
751 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
754 log.warn( "Version path for repository {} is not a directory {}", getId(), deleteTarget );
755 throw new ContentNotFoundException( "Version path for repository "+getId()+" is not directory: " + path );
760 public void deleteProject( ProjectReference ref )
761 throws ContentNotFoundException, ContentAccessException
763 final String path = toPath( ref );
764 final Path deleteTarget = getRepoDir( ).resolve( path );
765 if ( !Files.exists(deleteTarget) )
767 log.warn( "Project path for repository {} does not exist: {}", getId(), deleteTarget );
768 throw new ContentNotFoundException( "Project not found for repository "+getId()+": "+path );
770 if ( Files.isDirectory(deleteTarget) )
774 org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
776 catch ( IOException e )
778 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
779 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
784 log.warn( "Project path for repository {} is not a directory {}", getId(), deleteTarget );
785 throw new ContentNotFoundException( "Project path for repository "+getId()+" is not directory: " + path );
791 public void deleteProject( String namespace, String projectId ) throws ContentNotFoundException, ContentAccessException
793 this.deleteProject( new ProjectReference().groupId( namespace ).artifactId( projectId ) );
797 public void deleteArtifact( ArtifactReference ref ) throws ContentNotFoundException, ContentAccessException
799 final String path = toPath( ref );
800 final Path repoDir = getRepoDir( );
801 Path deleteTarget = repoDir.resolve( path );
802 if ( Files.exists(deleteTarget) )
806 if (Files.isDirectory( deleteTarget ))
808 org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
810 Files.delete( deleteTarget );
813 catch ( IOException e )
815 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
816 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
819 log.warn( "Artifact path for repository {} does not exist: {}", getId(), deleteTarget );
820 throw new ContentNotFoundException( "Artifact not found for repository "+getId()+": "+path );
826 public void deleteGroupId( String groupId )
827 throws ContentNotFoundException, ContentAccessException
829 final String path = toPath( groupId );
830 final Path deleteTarget = getRepoDir( ).resolve( path );
831 if (!Files.exists(deleteTarget)) {
832 log.warn( "Namespace path for repository {} does not exist: {}", getId(), deleteTarget );
833 throw new ContentNotFoundException( "Namespace not found for repository "+getId()+": "+path );
835 if ( Files.isDirectory(deleteTarget) )
839 org.apache.archiva.common.utils.FileUtils.deleteDirectory( deleteTarget );
841 catch ( IOException e )
843 log.error( "Could not delete file path {}: {}", deleteTarget, e.getMessage( ), e );
844 throw new ContentAccessException( "Error while trying to delete path "+path+" from repository "+getId()+": "+e.getMessage( ), e );
847 log.warn( "Namespace path for repository {} is not a directory {}", getId(), deleteTarget );
848 throw new ContentNotFoundException( "Namespace path for repository "+getId()+" is not directory: " + path );
854 public String getId()
856 return repository.getId();
860 public List<ArtifactReference> getRelatedArtifacts( VersionedReference reference )
861 throws ContentNotFoundException, LayoutException, ContentAccessException
863 StorageAsset artifactDir = toFile( reference );
864 if ( !artifactDir.exists())
866 throw new ContentNotFoundException(
867 "Unable to get related artifacts using a non-existant directory: " + artifactDir.getPath() );
870 if ( !artifactDir.isContainer() )
872 throw new ContentNotFoundException(
873 "Unable to get related artifacts using a non-directory: " + artifactDir.getPath() );
876 // First gather up the versions found as artifacts in the managed repository.
878 try (Stream<? extends StorageAsset> stream = artifactDir.list().stream() ) {
879 return stream.filter(asset -> !asset.isContainer()).map(path -> {
881 ArtifactReference artifact = toArtifactReference(path.getPath());
882 if( artifact.getGroupId().equals( reference.getGroupId() ) && artifact.getArtifactId().equals(
883 reference.getArtifactId() ) && artifact.getVersion().equals( reference.getVersion() )) {
888 } catch (LayoutException e) {
889 log.debug( "Not processing file that is not an artifact: {}", e.getMessage() );
892 }).filter(Objects::nonNull).collect(Collectors.toList());
893 } catch (RuntimeException e) {
894 Throwable cause = e.getCause( );
896 if (cause instanceof LayoutException) {
897 throw (LayoutException)cause;
900 throw new ContentAccessException( cause.getMessage( ), cause );
903 throw new ContentAccessException( e.getMessage( ), e );
909 * Create the filter for various combinations of classifier and type
911 private Predicate<ArtifactReference> getChecker(ArtifactReference referenceObject, String extension) {
912 // TODO: Check, if extension is the correct parameter here
913 // We compare type with extension which works for artifacts like .jar.md5 but may
914 // be not the best way.
916 if (referenceObject.getClassifier()!=null && referenceObject.getType()!=null) {
917 return ((ArtifactReference a) ->
918 referenceObject.getGroupId().equals( a.getGroupId() )
919 && referenceObject.getArtifactId().equals( a.getArtifactId() )
920 && referenceObject.getVersion( ).equals( a.getVersion( ) )
921 && ( (a.getType()==null)
922 || referenceObject.getType().equals( a.getType() )
923 || a.getType().startsWith(extension) )
924 && referenceObject.getClassifier().equals( a.getClassifier() )
926 } else if (referenceObject.getClassifier()!=null && referenceObject.getType()==null){
927 return ((ArtifactReference a) ->
928 referenceObject.getGroupId().equals( a.getGroupId() )
929 && referenceObject.getArtifactId().equals( a.getArtifactId() )
930 && referenceObject.getVersion( ).equals( a.getVersion( ) )
931 && referenceObject.getClassifier().equals( a.getClassifier() )
933 } else if (referenceObject.getClassifier()==null && referenceObject.getType()!=null){
934 return ((ArtifactReference a) ->
935 referenceObject.getGroupId().equals( a.getGroupId() )
936 && referenceObject.getArtifactId().equals( a.getArtifactId() )
937 && referenceObject.getVersion( ).equals( a.getVersion( ) )
938 && ( (a.getType()==null)
939 || referenceObject.getType().equals( a.getType() )
940 || a.getType().startsWith(extension) )
943 return ((ArtifactReference a) ->
944 referenceObject.getGroupId().equals( a.getGroupId() )
945 && referenceObject.getArtifactId().equals( a.getArtifactId() )
946 && referenceObject.getVersion( ).equals( a.getVersion( ) )
954 public List<ArtifactReference> getRelatedArtifacts( ArtifactReference reference )
955 throws ContentNotFoundException, LayoutException, ContentAccessException
957 if ( StringUtils.isEmpty( reference.getType() ) && StringUtils.isEmpty( reference.getClassifier() ) ) {
958 return getRelatedArtifacts( toVersion( reference ) );
961 StorageAsset artifactFile = toFile( reference );
962 StorageAsset repoDir = artifactFile.getParent();
964 if (!artifactFile.isContainer()) {
965 ext = StringUtils.substringAfterLast( artifactFile.getName(), ".");
970 if ( !repoDir.exists())
972 throw new ContentNotFoundException(
973 "Unable to get related artifacts using a non-existant directory: " + repoDir.getPath() );
976 if ( !repoDir.isContainer() )
978 throw new ContentNotFoundException(
979 "Unable to get related artifacts using a non-directory: " + repoDir.getPath() );
982 // First gather up the versions found as artifacts in the managed repository.
984 try (Stream<? extends StorageAsset> stream = repoDir.list().stream() ) {
985 return stream.filter(
986 asset -> !asset.isContainer())
989 return toArtifactReference(path.getPath());
990 } catch (LayoutException e) {
991 log.debug( "Not processing file that is not an artifact: {}", e.getMessage() );
994 }).filter(Objects::nonNull).filter(getChecker( reference, ext )).collect(Collectors.toList());
995 } catch (RuntimeException e) {
996 Throwable cause = e.getCause( );
998 if (cause instanceof LayoutException) {
999 throw (LayoutException)cause;
1002 throw new ContentAccessException( cause.getMessage( ), cause );
1005 throw new ContentAccessException( e.getMessage( ), e );
1011 public List<StorageAsset> getRelatedAssets( ArtifactReference reference ) throws ContentNotFoundException, LayoutException, ContentAccessException
1017 public String getRepoRoot()
1019 return convertUriToPath( repository.getLocation() );
1022 private String convertUriToPath( URI uri ) {
1023 if (uri.getScheme()==null) {
1024 return Paths.get(uri.getPath()).toString();
1025 } else if ("file".equals(uri.getScheme())) {
1026 return Paths.get(uri).toString();
1028 return uri.toString();
1033 public ManagedRepository getRepository()
1039 * Gather the Available Versions (on disk) for a specific Project Reference, based on filesystem
1042 * @return the Set of available versions, based on the project reference.
1043 * @throws LayoutException
1046 public Set<String> getVersions( ProjectReference reference )
1047 throws ContentNotFoundException, LayoutException, ContentAccessException
1049 final String path = toPath( reference );
1050 final Path projDir = getRepoDir().resolve(toPath(reference));
1051 if ( !Files.exists(projDir) )
1053 throw new ContentNotFoundException(
1054 "Unable to get Versions on a non-existant directory for repository "+getId()+": " + path );
1057 if ( !Files.isDirectory(projDir) )
1059 throw new ContentNotFoundException(
1060 "Unable to get Versions on a non-directory for repository "+getId()+": " + path );
1063 final String groupId = reference.getGroupId();
1064 final String artifactId = reference.getArtifactId();
1065 try(Stream<Path> stream = Files.list(projDir)) {
1066 return stream.filter(Files::isDirectory).map(
1067 p -> toVersion(groupId, artifactId, p.getFileName().toString())
1068 ).filter(this::hasArtifact).map(ref -> ref.getVersion())
1069 .collect(Collectors.toSet());
1070 } catch (IOException e) {
1071 log.error("Could not read directory {}: {}", projDir, e.getMessage(), e);
1072 throw new ContentAccessException( "Could not read path for repository "+getId()+": "+ path, e );
1073 } catch (RuntimeException e) {
1074 Throwable cause = e.getCause( );
1077 if ( cause instanceof LayoutException )
1079 throw (LayoutException) cause;
1081 log.error("Could not read directory {}: {}", projDir, cause.getMessage(), cause);
1082 throw new ContentAccessException( "Could not read path for repository "+getId()+": "+ path, cause );
1085 log.error("Could not read directory {}: {}", projDir, e.getMessage(), e);
1086 throw new ContentAccessException( "Could not read path for repository "+getId()+": "+ path, cause );
1092 public Set<String> getVersions( VersionedReference reference )
1093 throws ContentNotFoundException, ContentAccessException, LayoutException
1095 try(Stream<ArtifactReference> stream = getArtifactStream( reference ))
1097 return stream.filter( Objects::nonNull )
1098 .map( ar -> ar.getVersion( ) )
1099 .collect( Collectors.toSet( ) );
1100 } catch (IOException e) {
1101 final String path = toPath( reference );
1102 log.error("Could not read directory from repository {} - {}: ", getId(), path, e.getMessage(), e);
1103 throw new ContentAccessException( "Could not read path for repository "+getId()+": "+ path, e );
1108 public boolean hasContent( ArtifactReference reference ) throws ContentAccessException
1110 StorageAsset artifactFile = toFile( reference );
1111 return artifactFile.exists() && !artifactFile.isContainer();
1115 public boolean hasContent( ProjectReference reference ) throws ContentAccessException
1119 Set<String> versions = getVersions( reference );
1120 return !versions.isEmpty();
1122 catch ( ContentNotFoundException | LayoutException e )
1129 public boolean hasContent( VersionedReference reference ) throws ContentAccessException
1133 return ( getFirstArtifact( reference ) != null );
1135 catch ( LayoutException | ContentNotFoundException e )
1139 catch ( IOException e )
1141 String path = toPath( reference );
1142 log.error("Could not read directory from repository {} - {}: ", getId(), path, e.getMessage(), e);
1143 throw new ContentAccessException( "Could not read path from repository " + getId( ) + ": " + path, e );
1148 public void setRepository( final ManagedRepository repo )
1150 this.repository = repo;
1152 if (repository instanceof EditableManagedRepository) {
1153 ((EditableManagedRepository) repository).setContent(this);
1158 private Path getRepoDir() {
1159 return repository.getAsset( "" ).getFilePath( );
1162 private RepositoryStorage getStorage() {
1163 return repository.getAsset( "" ).getStorage( );
1167 * Convert a path to an artifact reference.
1169 * @param path the path to convert. (relative or full location path)
1170 * @throws LayoutException if the path cannot be converted to an artifact reference.
1173 public ArtifactReference toArtifactReference( String path )
1174 throws LayoutException
1176 String repoPath = convertUriToPath( repository.getLocation() );
1177 if ( ( path != null ) && path.startsWith( repoPath ) && repoPath.length() > 0 )
1179 return super.toArtifactReference( path.substring( repoPath.length() + 1 ) );
1182 if (repoPath!=null) {
1183 while (repoPath.startsWith("/")) {
1184 repoPath = repoPath.substring(1);
1187 return super.toArtifactReference( repoPath );
1192 // The variant with runtime exception for stream usage
1193 private ArtifactReference toArtifactRef(String path) {
1195 return toArtifactReference(path);
1196 } catch (LayoutException e) {
1197 throw new RuntimeException(e);
1204 public StorageAsset toFile( ArtifactReference reference )
1206 return repository.getAsset(toPath(reference));
1210 public StorageAsset toFile( ArchivaArtifact reference )
1212 return repository.getAsset( toPath( reference ) );
1216 public StorageAsset toFile( VersionedReference reference )
1218 return repository.getAsset( toPath( reference ) );
1222 * Get the first Artifact found in the provided VersionedReference location.
1224 * @param reference the reference to the versioned reference to search within
1225 * @return the ArtifactReference to the first artifact located within the versioned reference. or null if
1226 * no artifact was found within the versioned reference.
1227 * @throws java.io.IOException if the versioned reference is invalid (example: doesn't exist, or isn't a directory)
1228 * @throws LayoutException
1230 private ArtifactReference getFirstArtifact( VersionedReference reference )
1231 throws ContentNotFoundException, LayoutException, IOException
1233 try(Stream<ArtifactReference> stream = getArtifactStream( reference ))
1235 return stream.findFirst( ).orElse( null );
1236 } catch (RuntimeException e) {
1237 throw new ContentNotFoundException( e.getMessage( ), e.getCause( ) );
1241 private Stream<ArtifactReference> getArtifactStream(VersionedReference reference) throws ContentNotFoundException, LayoutException, IOException {
1242 final Path repoBase = getRepoDir( );
1243 String path = toMetadataPath( reference );
1244 Path versionDir = repoBase.resolve( path ).getParent();
1245 if ( !Files.exists(versionDir) )
1247 throw new ContentNotFoundException( "Unable to gather the list of artifacts on a non-existant directory: "
1248 + versionDir.toAbsolutePath() );
1251 if ( !Files.isDirectory(versionDir) )
1253 throw new ContentNotFoundException(
1254 "Unable to gather the list of snapshot versions on a non-directory: " + versionDir.toAbsolutePath() );
1256 return Files.list(versionDir).filter(Files::isRegularFile)
1257 .map(p -> repoBase.relativize(p).toString())
1258 .filter(p -> !filetypes.matchesDefaultExclusions(p))
1259 .filter(filetypes::matchesArtifactPattern)
1260 .map(this::toArtifactRef);
1263 public List<ArtifactReference> getArtifacts(VersionedReference reference) throws ContentNotFoundException, LayoutException, ContentAccessException
1265 try (Stream<ArtifactReference> stream = getArtifactStream( reference ))
1267 return stream.collect( Collectors.toList( ) );
1268 } catch ( IOException e )
1270 String path = toPath( reference );
1271 log.error("Could not read directory from repository {} - {}: ", getId(), path, e.getMessage(), e);
1272 throw new ContentAccessException( "Could not read path from repository " + getId( ) + ": " + path, e );
1277 private boolean hasArtifact( VersionedReference reference )
1280 try(Stream<ArtifactReference> stream = getArtifactStream( reference ))
1282 return stream.anyMatch( e -> true );
1283 } catch (ContentNotFoundException e) {
1285 } catch ( LayoutException | IOException e) {
1286 // We throw the runtime exception for better stream handling
1287 throw new RuntimeException(e);
1291 public void setFiletypes( FileTypes filetypes )
1293 this.filetypes = filetypes;
1296 public void setMavenContentHelper( MavenContentHelper contentHelper) {
1297 this.mavenContentHelper = contentHelper;