1 package org.apache.archiva.metadata.repository.storage.maven2;
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
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
22 import org.apache.archiva.admin.model.RepositoryAdminException;
23 import org.apache.archiva.admin.model.beans.ManagedRepository;
24 import org.apache.archiva.admin.model.beans.NetworkProxy;
25 import org.apache.archiva.admin.model.beans.ProxyConnector;
26 import org.apache.archiva.admin.model.beans.RemoteRepository;
27 import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin;
28 import org.apache.archiva.admin.model.networkproxy.NetworkProxyAdmin;
29 import org.apache.archiva.admin.model.proxyconnector.ProxyConnectorAdmin;
30 import org.apache.archiva.admin.model.remote.RemoteRepositoryAdmin;
31 import org.apache.archiva.checksum.ChecksumAlgorithm;
32 import org.apache.archiva.checksum.ChecksummedFile;
33 import org.apache.archiva.common.Try;
34 import org.apache.archiva.common.utils.VersionUtil;
35 import org.apache.archiva.maven2.metadata.MavenMetadataReader;
36 import org.apache.archiva.metadata.model.ArtifactMetadata;
37 import org.apache.archiva.metadata.model.ProjectMetadata;
38 import org.apache.archiva.metadata.model.ProjectVersionMetadata;
39 import org.apache.archiva.metadata.model.facets.RepositoryProblemFacet;
40 import org.apache.archiva.metadata.repository.filter.Filter;
41 import org.apache.archiva.metadata.repository.storage.*;
42 import org.apache.archiva.model.ArchivaRepositoryMetadata;
43 import org.apache.archiva.model.ArtifactReference;
44 import org.apache.archiva.model.SnapshotVersion;
45 import org.apache.archiva.policies.ProxyDownloadException;
46 import org.apache.archiva.proxy.common.WagonFactory;
47 import org.apache.archiva.proxy.model.RepositoryProxyConnectors;
48 import org.apache.archiva.repository.ManagedRepositoryContent;
49 import org.apache.archiva.repository.content.PathParser;
50 import org.apache.archiva.repository.layout.LayoutException;
51 import org.apache.archiva.xml.XMLException;
52 import org.apache.commons.lang.ArrayUtils;
53 import org.apache.commons.lang.StringUtils;
54 import org.apache.maven.model.*;
55 import org.apache.maven.model.building.*;
56 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
57 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
60 import org.springframework.context.ApplicationContext;
61 import org.springframework.stereotype.Service;
63 import javax.annotation.PostConstruct;
64 import javax.inject.Inject;
65 import javax.inject.Named;
66 import java.io.FileNotFoundException;
67 import java.io.IOException;
68 import java.io.Reader;
69 import java.nio.charset.Charset;
70 import java.nio.file.Files;
71 import java.nio.file.NoSuchFileException;
72 import java.nio.file.Path;
73 import java.nio.file.Paths;
75 import java.util.function.Predicate;
76 import java.util.stream.Collectors;
77 import java.util.stream.Stream;
79 // import java.io.FileNotFoundException;
83 * Maven 2 repository format storage implementation. This class currently takes parameters to indicate the repository to
84 * deal with rather than being instantiated per-repository.
85 * FIXME: instantiate one per repository and allocate permanently from a factory (which can be obtained within the session).
88 * The session is passed in as an argument to obtain any necessary resources, rather than the class being instantiated
89 * within the session in the context of a single managed repository's resolution needs.
92 @Service( "repositoryStorage#maven2" )
93 public class Maven2RepositoryStorage
94 implements RepositoryStorage
97 private static final Logger LOGGER = LoggerFactory.getLogger( Maven2RepositoryStorage.class );
99 private ModelBuilder builder;
102 private RemoteRepositoryAdmin remoteRepositoryAdmin;
105 private ManagedRepositoryAdmin managedRepositoryAdmin;
108 private ProxyConnectorAdmin proxyConnectorAdmin;
111 private NetworkProxyAdmin networkProxyAdmin;
114 @Named( "repositoryPathTranslator#maven2" )
115 private RepositoryPathTranslator pathTranslator;
118 private WagonFactory wagonFactory;
121 private ApplicationContext applicationContext;
124 @Named( "pathParser#default" )
125 private PathParser pathParser;
127 private static final String METADATA_FILENAME_START = "maven-metadata";
129 private static final String METADATA_FILENAME = METADATA_FILENAME_START + ".xml";
131 // This array must be lexically sorted
132 private static final String[] IGNORED_FILES = { METADATA_FILENAME, "resolver-status.properties" };
134 private static final MavenXpp3Reader MAVEN_XPP_3_READER = new MavenXpp3Reader();
138 public void initialize()
140 builder = new DefaultModelBuilderFactory().newInstance();
145 public ProjectMetadata readProjectMetadata( String repoId, String namespace, String projectId )
147 // TODO: could natively implement the "shared model" concept from the browse action to avoid needing it there?
152 public ProjectVersionMetadata readProjectVersionMetadata( ReadMetadataRequest readMetadataRequest )
153 throws RepositoryStorageMetadataNotFoundException, RepositoryStorageMetadataInvalidException,
154 RepositoryStorageRuntimeException
158 ManagedRepository managedRepository =
159 managedRepositoryAdmin.getManagedRepository( readMetadataRequest.getRepositoryId() );
160 String artifactVersion = readMetadataRequest.getProjectVersion();
161 // olamy: in case of browsing via the ui we can mix repos (parent of a SNAPSHOT can come from release repo)
162 if ( !readMetadataRequest.isBrowsingRequest() )
164 if ( VersionUtil.isSnapshot( artifactVersion ) )
166 // skygo trying to improve speed by honoring managed configuration MRM-1658
167 if ( managedRepository.isReleases() && !managedRepository.isSnapshots() )
169 throw new RepositoryStorageRuntimeException( "lookforsnaponreleaseonly",
170 "managed repo is configured for release only" );
175 if ( !managedRepository.isReleases() && managedRepository.isSnapshots() )
177 throw new RepositoryStorageRuntimeException( "lookforsreleaseonsneponly",
178 "managed repo is configured for snapshot only" );
182 Path basedir = Paths.get( managedRepository.getLocation() );
183 if ( VersionUtil.isSnapshot( artifactVersion ) )
185 Path metadataFile = pathTranslator.toFile( basedir, readMetadataRequest.getNamespace(),
186 readMetadataRequest.getProjectId(), artifactVersion,
190 ArchivaRepositoryMetadata metadata = MavenMetadataReader.read( metadataFile );
192 // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
193 SnapshotVersion snapshotVersion = metadata.getSnapshotVersion();
194 if ( snapshotVersion != null )
197 artifactVersion.substring( 0, artifactVersion.length() - 8 ); // remove SNAPSHOT from end
199 artifactVersion + snapshotVersion.getTimestamp() + "-" + snapshotVersion.getBuildNumber();
202 catch ( XMLException e )
204 // unable to parse metadata - LOGGER it, and continue with the version as the original SNAPSHOT version
205 LOGGER.warn( "Invalid metadata: {} - {}", metadataFile, e.getMessage() );
209 // TODO: won't work well with some other layouts, might need to convert artifact parts to ID by path translator
210 String id = readMetadataRequest.getProjectId() + "-" + artifactVersion + ".pom";
212 pathTranslator.toFile( basedir, readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(),
213 readMetadataRequest.getProjectVersion(), id );
215 if ( !Files.exists(file) )
217 // metadata could not be resolved
218 throw new RepositoryStorageMetadataNotFoundException(
219 "The artifact's POM file '" + file.toAbsolutePath() + "' was missing" );
222 // TODO: this is a workaround until we can properly resolve using proxies as well - this doesn't cache
224 List<RemoteRepository> remoteRepositories = new ArrayList<>();
225 Map<String, NetworkProxy> networkProxies = new HashMap<>();
227 Map<String, List<ProxyConnector>> proxyConnectorsMap = proxyConnectorAdmin.getProxyConnectorAsMap();
228 List<ProxyConnector> proxyConnectors = proxyConnectorsMap.get( readMetadataRequest.getRepositoryId() );
229 if ( proxyConnectors != null )
231 for ( ProxyConnector proxyConnector : proxyConnectors )
233 RemoteRepository remoteRepoConfig =
234 remoteRepositoryAdmin.getRemoteRepository( proxyConnector.getTargetRepoId() );
236 if ( remoteRepoConfig != null )
238 remoteRepositories.add( remoteRepoConfig );
240 NetworkProxy networkProxyConfig =
241 networkProxyAdmin.getNetworkProxy( proxyConnector.getProxyId() );
243 if ( networkProxyConfig != null )
245 // key/value: remote repo ID/proxy info
246 networkProxies.put( proxyConnector.getTargetRepoId(), networkProxyConfig );
252 // That's a browsing request so we can a mix of SNAPSHOT and release artifacts (especially with snapshots which
253 // can have released parent pom
254 if ( readMetadataRequest.isBrowsingRequest() )
256 remoteRepositories.addAll( remoteRepositoryAdmin.getRemoteRepositories() );
259 ModelBuildingRequest req =
260 new DefaultModelBuildingRequest().setProcessPlugins( false ).setPomFile( file.toFile() ).setTwoPhaseBuilding(
261 false ).setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL );
263 //MRM-1607. olamy this will resolve jdk profiles on the current running archiva jvm
264 req.setSystemProperties( System.getProperties() );
267 req.setModelResolver(
268 new RepositoryModelResolver( managedRepository, pathTranslator, wagonFactory, remoteRepositories,
269 networkProxies, managedRepository ) );
274 model = builder.build( req ).getEffectiveModel();
276 catch ( ModelBuildingException e )
278 String msg = "The artifact's POM file '" + file + "' was invalid: " + e.getMessage();
280 List<ModelProblem> modelProblems = e.getProblems();
281 for ( ModelProblem problem : modelProblems )
283 // MRM-1411, related to MRM-1335
284 // this means that the problem was that the parent wasn't resolved!
285 // olamy really hackhish but fail with java profile so use error message
286 // || ( StringUtils.startsWith( problem.getMessage(), "Failed to determine Java version for profile" ) )
287 // but setTwoPhaseBuilding(true) fix that
288 if ( ( (problem.getException() instanceof FileNotFoundException
289 || problem.getException() instanceof NoSuchFileException
290 ) && e.getModelId() != null &&
291 !e.getModelId().equals( problem.getModelId() ) ) )
293 LOGGER.warn( "The artifact's parent POM file '{}' cannot be resolved. "
294 + "Using defaults for project version metadata..", file );
296 ProjectVersionMetadata metadata = new ProjectVersionMetadata();
297 metadata.setId( readMetadataRequest.getProjectVersion() );
299 MavenProjectFacet facet = new MavenProjectFacet();
300 facet.setGroupId( readMetadataRequest.getNamespace() );
301 facet.setArtifactId( readMetadataRequest.getProjectId() );
302 facet.setPackaging( "jar" );
303 metadata.addFacet( facet );
306 "Error in resolving artifact's parent POM file. " + ( problem.getException() == null
307 ? problem.getMessage()
308 : problem.getException().getMessage() );
309 RepositoryProblemFacet repoProblemFacet = new RepositoryProblemFacet();
310 repoProblemFacet.setRepositoryId( readMetadataRequest.getRepositoryId() );
311 repoProblemFacet.setId( readMetadataRequest.getRepositoryId() );
312 repoProblemFacet.setMessage( errMsg );
313 repoProblemFacet.setProblem( errMsg );
314 repoProblemFacet.setProject( readMetadataRequest.getProjectId() );
315 repoProblemFacet.setVersion( readMetadataRequest.getProjectVersion() );
316 repoProblemFacet.setNamespace( readMetadataRequest.getNamespace() );
318 metadata.addFacet( repoProblemFacet );
324 throw new RepositoryStorageMetadataInvalidException( "invalid-pom", msg, e );
327 // Check if the POM is in the correct location
328 boolean correctGroupId = readMetadataRequest.getNamespace().equals( model.getGroupId() );
329 boolean correctArtifactId = readMetadataRequest.getProjectId().equals( model.getArtifactId() );
330 boolean correctVersion = readMetadataRequest.getProjectVersion().equals( model.getVersion() );
331 if ( !correctGroupId || !correctArtifactId || !correctVersion )
333 StringBuilder message = new StringBuilder( "Incorrect POM coordinates in '" + file + "':" );
334 if ( !correctGroupId )
336 message.append( "\nIncorrect group ID: " ).append( model.getGroupId() );
338 if ( !correctArtifactId )
340 message.append( "\nIncorrect artifact ID: " ).append( model.getArtifactId() );
342 if ( !correctVersion )
344 message.append( "\nIncorrect version: " ).append( model.getVersion() );
347 throw new RepositoryStorageMetadataInvalidException( "mislocated-pom", message.toString() );
350 ProjectVersionMetadata metadata = new ProjectVersionMetadata();
351 metadata.setCiManagement( convertCiManagement( model.getCiManagement() ) );
352 metadata.setDescription( model.getDescription() );
353 metadata.setId( readMetadataRequest.getProjectVersion() );
354 metadata.setIssueManagement( convertIssueManagement( model.getIssueManagement() ) );
355 metadata.setLicenses( convertLicenses( model.getLicenses() ) );
356 metadata.setMailingLists( convertMailingLists( model.getMailingLists() ) );
357 metadata.setDependencies( convertDependencies( model.getDependencies() ) );
358 metadata.setName( model.getName() );
359 metadata.setOrganization( convertOrganization( model.getOrganization() ) );
360 metadata.setScm( convertScm( model.getScm() ) );
361 metadata.setUrl( model.getUrl() );
362 metadata.setProperties( model.getProperties() );
364 MavenProjectFacet facet = new MavenProjectFacet();
365 facet.setGroupId( model.getGroupId() != null ? model.getGroupId() : model.getParent().getGroupId() );
366 facet.setArtifactId( model.getArtifactId() );
367 facet.setPackaging( model.getPackaging() );
368 if ( model.getParent() != null )
370 MavenProjectParent parent = new MavenProjectParent();
371 parent.setGroupId( model.getParent().getGroupId() );
372 parent.setArtifactId( model.getParent().getArtifactId() );
373 parent.setVersion( model.getParent().getVersion() );
374 facet.setParent( parent );
376 metadata.addFacet( facet );
380 catch ( RepositoryAdminException e )
382 throw new RepositoryStorageRuntimeException( "repo-admin", e.getMessage(), e );
386 public void setWagonFactory( WagonFactory wagonFactory )
388 this.wagonFactory = wagonFactory;
391 private List<org.apache.archiva.metadata.model.Dependency> convertDependencies( List<Dependency> dependencies )
393 List<org.apache.archiva.metadata.model.Dependency> l = new ArrayList<>();
394 for ( Dependency dependency : dependencies )
396 org.apache.archiva.metadata.model.Dependency newDependency =
397 new org.apache.archiva.metadata.model.Dependency();
398 newDependency.setArtifactId( dependency.getArtifactId() );
399 newDependency.setClassifier( dependency.getClassifier() );
400 newDependency.setGroupId( dependency.getGroupId() );
401 newDependency.setOptional( dependency.isOptional() );
402 newDependency.setScope( dependency.getScope() );
403 newDependency.setSystemPath( dependency.getSystemPath() );
404 newDependency.setType( dependency.getType() );
405 newDependency.setVersion( dependency.getVersion() );
406 l.add( newDependency );
411 private org.apache.archiva.metadata.model.Scm convertScm( Scm scm )
413 org.apache.archiva.metadata.model.Scm newScm = null;
416 newScm = new org.apache.archiva.metadata.model.Scm();
417 newScm.setConnection( scm.getConnection() );
418 newScm.setDeveloperConnection( scm.getDeveloperConnection() );
419 newScm.setUrl( scm.getUrl() );
424 private org.apache.archiva.metadata.model.Organization convertOrganization( Organization organization )
426 org.apache.archiva.metadata.model.Organization org = null;
427 if ( organization != null )
429 org = new org.apache.archiva.metadata.model.Organization();
430 org.setName( organization.getName() );
431 org.setUrl( organization.getUrl() );
436 private List<org.apache.archiva.metadata.model.License> convertLicenses( List<License> licenses )
438 List<org.apache.archiva.metadata.model.License> l = new ArrayList<>();
439 for ( License license : licenses )
441 org.apache.archiva.metadata.model.License newLicense = new org.apache.archiva.metadata.model.License();
442 newLicense.setName( license.getName() );
443 newLicense.setUrl( license.getUrl() );
449 private List<org.apache.archiva.metadata.model.MailingList> convertMailingLists( List<MailingList> mailingLists )
451 List<org.apache.archiva.metadata.model.MailingList> l = new ArrayList<>();
452 for ( MailingList mailingList : mailingLists )
454 org.apache.archiva.metadata.model.MailingList newMailingList =
455 new org.apache.archiva.metadata.model.MailingList();
456 newMailingList.setName( mailingList.getName() );
457 newMailingList.setMainArchiveUrl( mailingList.getArchive() );
458 newMailingList.setPostAddress( mailingList.getPost() );
459 newMailingList.setSubscribeAddress( mailingList.getSubscribe() );
460 newMailingList.setUnsubscribeAddress( mailingList.getUnsubscribe() );
461 newMailingList.setOtherArchives( mailingList.getOtherArchives() );
462 l.add( newMailingList );
467 private org.apache.archiva.metadata.model.IssueManagement convertIssueManagement( IssueManagement issueManagement )
469 org.apache.archiva.metadata.model.IssueManagement im = null;
470 if ( issueManagement != null )
472 im = new org.apache.archiva.metadata.model.IssueManagement();
473 im.setSystem( issueManagement.getSystem() );
474 im.setUrl( issueManagement.getUrl() );
479 private org.apache.archiva.metadata.model.CiManagement convertCiManagement( CiManagement ciManagement )
481 org.apache.archiva.metadata.model.CiManagement ci = null;
482 if ( ciManagement != null )
484 ci = new org.apache.archiva.metadata.model.CiManagement();
485 ci.setSystem( ciManagement.getSystem() );
486 ci.setUrl( ciManagement.getUrl() );
492 public Collection<String> listRootNamespaces( String repoId, Filter<String> filter )
493 throws RepositoryStorageRuntimeException
495 Path dir = getRepositoryBasedir( repoId );
497 return getSortedFiles( dir, filter );
500 private static Collection<String> getSortedFiles( Path dir, Filter<String> filter )
503 try(Stream<Path> stream = Files.list(dir)) {
504 final Predicate<Path> dFilter = new DirectoryFilter( filter );
505 return stream.filter(Files::isDirectory)
507 .map(path -> path.getFileName().toString())
508 .sorted().collect(Collectors.toList());
510 } catch (IOException e) {
511 LOGGER.error("Could not read directory list {}: {}", dir, e.getMessage(),e);
512 return Collections.emptyList();
516 private Path getRepositoryBasedir( String repoId )
517 throws RepositoryStorageRuntimeException
521 ManagedRepository repositoryConfiguration = managedRepositoryAdmin.getManagedRepository( repoId );
523 return Paths.get( repositoryConfiguration.getLocation() );
525 catch ( RepositoryAdminException e )
527 throw new RepositoryStorageRuntimeException( "repo-admin", e.getMessage(), e );
532 public Collection<String> listNamespaces( String repoId, String namespace, Filter<String> filter )
533 throws RepositoryStorageRuntimeException
535 Path dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace );
536 if (!(Files.exists(dir) && Files.isDirectory(dir))) {
537 return Collections.emptyList();
539 // scan all the directories which are potential namespaces. Any directories known to be projects are excluded
540 Predicate<Path> dFilter = new DirectoryFilter(filter);
541 try(Stream<Path> stream = Files.list(dir)) {
542 return stream.filter(dFilter).filter(path -> !isProject(path, filter)).map(path -> path.getFileName().toString())
543 .sorted().collect(Collectors.toList());
544 } catch (IOException e) {
545 LOGGER.error("Could not read directory {}: {}", dir, e.getMessage(), e);
546 return Collections.emptyList();
551 public Collection<String> listProjects( String repoId, String namespace, Filter<String> filter )
552 throws RepositoryStorageRuntimeException
554 Path dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace );
555 if (!(Files.exists(dir) && Files.isDirectory(dir))) {
556 return Collections.emptyList();
558 // scan all directories in the namespace, and only include those that are known to be projects
559 final Predicate<Path> dFilter = new DirectoryFilter(filter);
560 try(Stream<Path> stream = Files.list(dir)) {
561 return stream.filter(dFilter).filter(path -> isProject(path, filter)).map(path -> path.getFileName().toString())
562 .sorted().collect(Collectors.toList());
563 } catch (IOException e) {
564 LOGGER.error("Could not read directory {}: {}", dir, e.getMessage(), e);
565 return Collections.emptyList();
571 public Collection<String> listProjectVersions( String repoId, String namespace, String projectId,
572 Filter<String> filter )
573 throws RepositoryStorageRuntimeException
575 Path dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace, projectId );
576 if (!(Files.exists(dir) && Files.isDirectory(dir))) {
577 return Collections.emptyList();
580 // all directories in a project directory can be considered a version
581 return getSortedFiles( dir, filter );
585 public Collection<ArtifactMetadata> readArtifactsMetadata( ReadMetadataRequest readMetadataRequest )
586 throws RepositoryStorageRuntimeException
588 Path dir = pathTranslator.toFile( getRepositoryBasedir( readMetadataRequest.getRepositoryId() ),
589 readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(),
590 readMetadataRequest.getProjectVersion() );
591 if (!(Files.exists(dir) && Files.isDirectory(dir))) {
592 return Collections.emptyList();
595 // all files that are not metadata and not a checksum / signature are considered artifacts
596 final Predicate<Path> dFilter = new ArtifactDirectoryFilter(readMetadataRequest.getFilter());
597 try(Stream<Path> stream = Files.list(dir)) {
598 // Returns a map TRUE -> (success values), FALSE -> (Exceptions)
599 Map<Boolean, List<Try<ArtifactMetadata>>> result = stream.filter(dFilter).map(path -> {
601 return Try.success(getArtifactFromFile(readMetadataRequest.getRepositoryId(), readMetadataRequest.getNamespace(),
602 readMetadataRequest.getProjectId(), readMetadataRequest.getProjectVersion(),
604 } catch (Exception e) {
605 LOGGER.debug("Could not create metadata for {}: {}", path, e.getMessage(), e);
606 return Try.<ArtifactMetadata>failure(e);
609 ).collect(Collectors.groupingBy(Try::isSuccess));
610 if (result.containsKey(Boolean.FALSE) && result.get(Boolean.FALSE).size()>0 && (!result.containsKey(Boolean.TRUE) || result.get(Boolean.TRUE).size()==0)) {
611 LOGGER.error("Could not get artifact metadata. Directory: {}. Number of errors {}.", dir, result.get(Boolean.FALSE).size());
612 Try<ArtifactMetadata> failure = result.get(Boolean.FALSE).get(0);
613 LOGGER.error("Sample exception {}", failure.getError().getMessage(), failure.getError());
614 throw new RepositoryStorageRuntimeException(readMetadataRequest.getRepositoryId(), "Could not retrieve metadata of the files");
616 if (!result.containsKey(Boolean.TRUE) || result.get(Boolean.TRUE) == null) {
617 return Collections.emptyList();
619 return result.get(Boolean.TRUE).stream().map(tr -> tr.get()).collect(Collectors.toList());
621 } catch (IOException e) {
622 LOGGER.error("Could not read directory {}: {}", dir, e.getMessage(), e);
624 return Collections.emptyList();
629 public ArtifactMetadata readArtifactMetadataFromPath( String repoId, String path )
630 throws RepositoryStorageRuntimeException
632 ArtifactMetadata metadata = pathTranslator.getArtifactForPath( repoId, path );
635 populateArtifactMetadataFromFile( metadata, getRepositoryBasedir( repoId ).resolve( path ) );
636 } catch (IOException e) {
637 throw new RepositoryStorageRuntimeException(repoId, "Error during metadata retrieval of "+path+" :"+e.getMessage(), e);
643 private ArtifactMetadata getArtifactFromFile( String repoId, String namespace, String projectId,
644 String projectVersion, Path file ) throws IOException {
645 ArtifactMetadata metadata =
646 pathTranslator.getArtifactFromId( repoId, namespace, projectId, projectVersion, file.getFileName().toString() );
648 populateArtifactMetadataFromFile( metadata, file );
654 public void applyServerSideRelocation( ManagedRepositoryContent managedRepository, ArtifactReference artifact )
655 throws ProxyDownloadException
657 if ( "pom".equals( artifact.getType() ) )
662 // Build the artifact POM reference
663 ArtifactReference pomReference = new ArtifactReference();
664 pomReference.setGroupId( artifact.getGroupId() );
665 pomReference.setArtifactId( artifact.getArtifactId() );
666 pomReference.setVersion( artifact.getVersion() );
667 pomReference.setType( "pom" );
669 RepositoryProxyConnectors connectors =
670 applicationContext.getBean( "repositoryProxyConnectors#default", RepositoryProxyConnectors.class );
672 // Get the artifact POM from proxied repositories if needed
673 connectors.fetchFromProxies( managedRepository, pomReference );
675 // Open and read the POM from the managed repo
676 Path pom = managedRepository.toFile( pomReference );
678 if ( !Files.exists(pom) )
685 // MavenXpp3Reader leaves the file open, so we need to close it ourselves.
688 try (Reader reader = Files.newBufferedReader( pom, Charset.defaultCharset() ))
690 model = MAVEN_XPP_3_READER.read( reader );
693 DistributionManagement dist = model.getDistributionManagement();
696 Relocation relocation = dist.getRelocation();
697 if ( relocation != null )
699 // artifact is relocated : update the repositoryPath
700 if ( relocation.getGroupId() != null )
702 artifact.setGroupId( relocation.getGroupId() );
704 if ( relocation.getArtifactId() != null )
706 artifact.setArtifactId( relocation.getArtifactId() );
708 if ( relocation.getVersion() != null )
710 artifact.setVersion( relocation.getVersion() );
715 catch ( IOException e )
717 // Unable to read POM : ignore.
719 catch ( XmlPullParserException e )
721 // Invalid POM : ignore
727 public String getFilePath( String requestPath, org.apache.archiva.repository.ManagedRepository managedRepository )
729 // managedRepository can be null
730 // extract artifact reference from url
731 // groupId:artifactId:version:packaging:classifier
732 //org/apache/archiva/archiva-checksum/1.4-M4-SNAPSHOT/archiva-checksum-1.4-M4-SNAPSHOT.jar
733 String logicalResource = null;
734 String requestPathInfo = StringUtils.defaultString( requestPath );
736 //remove prefix ie /repository/blah becomes /blah
737 requestPathInfo = removePrefix( requestPathInfo );
739 // Remove prefixing slash as the repository id doesn't contain it;
740 if ( requestPathInfo.startsWith( "/" ) )
742 requestPathInfo = requestPathInfo.substring( 1 );
745 int slash = requestPathInfo.indexOf( '/' );
748 logicalResource = requestPathInfo.substring( slash );
750 if ( logicalResource.endsWith( "/.." ) )
752 logicalResource += "/";
755 if ( logicalResource != null && logicalResource.startsWith( "//" ) )
757 logicalResource = logicalResource.substring( 1 );
760 if ( logicalResource == null )
762 logicalResource = "/";
767 logicalResource = "/";
769 return logicalResource;
774 public String getFilePathWithVersion( final String requestPath, ManagedRepositoryContent managedRepositoryContent )
775 throws XMLException, RelocationException
778 if ( StringUtils.endsWith( requestPath, METADATA_FILENAME ) )
780 return getFilePath( requestPath, managedRepositoryContent.getRepository() );
783 String filePath = getFilePath( requestPath, managedRepositoryContent.getRepository() );
785 ArtifactReference artifactReference = null;
788 artifactReference = pathParser.toArtifactReference( filePath );
790 catch ( LayoutException e )
795 if ( StringUtils.endsWith( artifactReference.getVersion(), VersionUtil.SNAPSHOT ) )
797 // read maven metadata to get last timestamp
798 Path metadataDir = Paths.get( managedRepositoryContent.getRepoRoot(), filePath ).getParent();
799 if ( !Files.exists(metadataDir) )
803 Path metadataFile = metadataDir.resolve( METADATA_FILENAME );
804 if ( !Files.exists(metadataFile) )
808 ArchivaRepositoryMetadata archivaRepositoryMetadata = MavenMetadataReader.read( metadataFile );
809 int buildNumber = archivaRepositoryMetadata.getSnapshotVersion().getBuildNumber();
810 String timestamp = archivaRepositoryMetadata.getSnapshotVersion().getTimestamp();
813 if ( buildNumber < 1 && timestamp == null )
818 // org/apache/archiva/archiva-checksum/1.4-M4-SNAPSHOT/archiva-checksum-1.4-M4-SNAPSHOT.jar
819 // -> archiva-checksum-1.4-M4-20130425.081822-1.jar
821 filePath = StringUtils.replace( filePath, //
822 artifactReference.getArtifactId() //
823 + "-" + artifactReference.getVersion(), //
824 artifactReference.getArtifactId() //
825 + "-" + StringUtils.remove( artifactReference.getVersion(),
826 "-" + VersionUtil.SNAPSHOT ) //
828 + "-" + buildNumber );
830 throw new RelocationException( "/repository/" + managedRepositoryContent.getRepository().getId() +
831 ( StringUtils.startsWith( filePath, "/" ) ? "" : "/" ) + filePath,
832 RelocationException.RelocationType.TEMPORARY );
839 //-----------------------------
841 //-----------------------------
849 private static String removePrefix( final String href )
851 String[] parts = StringUtils.split( href, '/' );
852 parts = (String[]) ArrayUtils.subarray( parts, 1, parts.length );
853 if ( parts == null || parts.length == 0 )
858 String joinedString = StringUtils.join( parts, '/' );
859 if ( href.endsWith( "/" ) )
861 joinedString = joinedString + "/";
867 private static void populateArtifactMetadataFromFile( ArtifactMetadata metadata, Path file ) throws IOException {
868 metadata.setWhenGathered( new Date() );
869 metadata.setFileLastModified( Files.getLastModifiedTime(file).toMillis() );
870 ChecksummedFile checksummedFile = new ChecksummedFile( file );
873 metadata.setMd5( checksummedFile.calculateChecksum( ChecksumAlgorithm.MD5 ) );
875 catch ( IOException e )
877 LOGGER.error( "Unable to checksum file {}: {},MD5", file, e.getMessage() );
881 metadata.setSha1( checksummedFile.calculateChecksum( ChecksumAlgorithm.SHA1 ) );
883 catch ( IOException e )
885 LOGGER.error( "Unable to checksum file {}: {},SHA1", file, e.getMessage() );
887 metadata.setSize( Files.size(file) );
890 private boolean isProject( Path dir, Filter<String> filter )
892 // scan directories for a valid project version subdirectory, meaning this must be a project directory
893 final Predicate<Path> dFilter = new DirectoryFilter(filter);
894 try(Stream<Path> stream = Files.list(dir)) {
895 boolean projFound = stream.filter(dFilter)
896 .anyMatch(path -> isProjectVersion(path));
900 } catch (IOException e) {
901 LOGGER.error("Could not read directory list {}: {}", dir, e.getMessage(), e);
904 // if a metadata file is present, check if this is the "artifactId" directory, marking it as a project
905 ArchivaRepositoryMetadata metadata = readMetadata( dir );
906 if ( metadata != null && dir.getFileName().toString().equals( metadata.getArtifactId() ) )
914 private boolean isProjectVersion( Path dir )
916 final String artifactId = dir.getParent().getFileName().toString();
917 final String projectVersion = dir.getFileName().toString();
919 // check if there is a POM artifact file to ensure it is a version directory
921 Predicate<Path> filter;
922 if ( VersionUtil.isSnapshot( projectVersion ) )
924 filter = new PomFilenameFilter(artifactId, projectVersion);
928 final String pomFile = artifactId + "-" + projectVersion + ".pom";
929 filter = new PomFileFilter(pomFile);
931 try(Stream<Path> stream = Files.list(dir)) {
932 if (stream.filter(Files::isRegularFile).anyMatch(filter)){
935 } catch (IOException e) {
936 LOGGER.error("Could not list directory {}: {}", dir, e.getMessage(), e);
939 // if a metadata file is present, check if this is the "version" directory, marking it as a project version
940 ArchivaRepositoryMetadata metadata = readMetadata( dir );
941 if ( metadata != null && projectVersion.equals( metadata.getVersion() ) )
949 private ArchivaRepositoryMetadata readMetadata( Path directory )
951 ArchivaRepositoryMetadata metadata = null;
952 Path metadataFile = directory.resolve( METADATA_FILENAME );
953 if ( Files.exists(metadataFile) )
957 metadata = MavenMetadataReader.read( metadataFile );
959 catch ( XMLException e )
961 // ignore missing or invalid metadata
967 private static class DirectoryFilter
968 implements Predicate<Path>
970 private final Filter<String> filter;
972 public DirectoryFilter( Filter<String> filter )
974 this.filter = filter;
978 public boolean test( Path dir )
980 final String name = dir.getFileName().toString();
981 if ( !filter.accept( name ) )
985 else if ( name.startsWith( "." ) )
989 else if ( !Files.isDirectory(dir))
997 private static class ArtifactDirectoryFilter
998 implements Predicate<Path>
1000 private final Filter<String> filter;
1002 private ArtifactDirectoryFilter( Filter<String> filter )
1004 this.filter = filter;
1008 public boolean test( Path dir )
1010 final String name = dir.getFileName().toString();
1011 // TODO compare to logic in maven-repository-layer
1012 if ( !filter.accept( name ) )
1016 else if ( name.startsWith( "." ) )
1020 else if ( name.endsWith( ".md5" ) || name.endsWith( ".sha1" ) || name.endsWith( ".asc" ) )
1024 else if ( Arrays.binarySearch(IGNORED_FILES, name)>=0 )
1028 else if ( Files.isDirectory(dir) )
1032 // some files from remote repositories can have name like maven-metadata-archiva-vm-all-public.xml
1033 else if ( StringUtils.startsWith( name, METADATA_FILENAME_START ) && StringUtils.endsWith( name, ".xml" ) )
1044 private static final class PomFilenameFilter
1045 implements Predicate<Path>
1048 private final String artifactId, projectVersion;
1050 private PomFilenameFilter( String artifactId, String projectVersion )
1052 this.artifactId = artifactId;
1053 this.projectVersion = projectVersion;
1057 public boolean test( Path dir )
1059 final String name = dir.getFileName().toString();
1060 if ( name.startsWith( artifactId + "-" ) && name.endsWith( ".pom" ) )
1062 String v = name.substring( artifactId.length() + 1, name.length() - 4 );
1063 v = VersionUtil.getBaseVersion( v );
1064 if ( v.equals( projectVersion ) )
1074 private static class PomFileFilter
1075 implements Predicate<Path>
1077 private final String pomFile;
1079 private PomFileFilter( String pomFile )
1081 this.pomFile = pomFile;
1085 public boolean test( Path dir )
1087 return pomFile.equals( dir.getFileName().toString());
1092 public PathParser getPathParser()
1097 public void setPathParser( PathParser pathParser )
1099 this.pathParser = pathParser;