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.utils.VersionUtil;
34 import org.apache.archiva.maven2.metadata.MavenMetadataReader;
35 import org.apache.archiva.metadata.model.ArtifactMetadata;
36 import org.apache.archiva.metadata.model.ProjectMetadata;
37 import org.apache.archiva.metadata.model.ProjectVersionMetadata;
38 import org.apache.archiva.metadata.model.facets.RepositoryProblemFacet;
39 import org.apache.archiva.metadata.repository.filter.Filter;
40 import org.apache.archiva.metadata.repository.storage.ReadMetadataRequest;
41 import org.apache.archiva.metadata.repository.storage.RelocationException;
42 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
43 import org.apache.archiva.metadata.repository.storage.RepositoryStorage;
44 import org.apache.archiva.metadata.repository.storage.RepositoryStorageMetadataInvalidException;
45 import org.apache.archiva.metadata.repository.storage.RepositoryStorageMetadataNotFoundException;
46 import org.apache.archiva.metadata.repository.storage.RepositoryStorageRuntimeException;
47 import org.apache.archiva.model.ArchivaRepositoryMetadata;
48 import org.apache.archiva.model.ArtifactReference;
49 import org.apache.archiva.model.SnapshotVersion;
50 import org.apache.archiva.policies.ProxyDownloadException;
51 import org.apache.archiva.proxy.common.WagonFactory;
52 import org.apache.archiva.proxy.model.RepositoryProxyConnectors;
53 import org.apache.archiva.repository.ManagedRepositoryContent;
54 import org.apache.archiva.repository.content.PathParser;
55 import org.apache.archiva.repository.layout.LayoutException;
56 import org.apache.archiva.xml.XMLException;
57 import org.apache.commons.lang.ArrayUtils;
58 import org.apache.commons.lang.StringUtils;
59 import org.apache.maven.model.CiManagement;
60 import org.apache.maven.model.Dependency;
61 import org.apache.maven.model.DistributionManagement;
62 import org.apache.maven.model.IssueManagement;
63 import org.apache.maven.model.License;
64 import org.apache.maven.model.MailingList;
65 import org.apache.maven.model.Model;
66 import org.apache.maven.model.Organization;
67 import org.apache.maven.model.Relocation;
68 import org.apache.maven.model.Scm;
69 import org.apache.maven.model.building.DefaultModelBuilderFactory;
70 import org.apache.maven.model.building.DefaultModelBuildingRequest;
71 import org.apache.maven.model.building.ModelBuilder;
72 import org.apache.maven.model.building.ModelBuildingException;
73 import org.apache.maven.model.building.ModelBuildingRequest;
74 import org.apache.maven.model.building.ModelProblem;
75 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
76 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
77 import org.slf4j.Logger;
78 import org.slf4j.LoggerFactory;
79 import org.springframework.context.ApplicationContext;
80 import org.springframework.stereotype.Service;
82 import javax.annotation.PostConstruct;
83 import javax.inject.Inject;
84 import javax.inject.Named;
86 import java.io.FileNotFoundException;
87 import java.io.FilenameFilter;
88 import java.io.IOException;
89 import java.io.Reader;
90 import java.nio.charset.Charset;
91 import java.nio.file.Files;
92 import java.nio.file.Path;
93 import java.util.ArrayList;
94 import java.util.Arrays;
95 import java.util.Collection;
96 import java.util.Collections;
97 import java.util.Date;
98 import java.util.HashMap;
99 import java.util.List;
100 import java.util.Map;
104 * Maven 2 repository format storage implementation. This class currently takes parameters to indicate the repository to
105 * deal with rather than being instantiated per-repository.
106 * FIXME: instantiate one per repository and allocate permanently from a factory (which can be obtained within the session).
109 * The session is passed in as an argument to obtain any necessary resources, rather than the class being instantiated
110 * within the session in the context of a single managed repository's resolution needs.
113 @Service( "repositoryStorage#maven2" )
114 public class Maven2RepositoryStorage
115 implements RepositoryStorage
118 private static final Logger LOGGER = LoggerFactory.getLogger( Maven2RepositoryStorage.class );
120 private ModelBuilder builder;
123 private RemoteRepositoryAdmin remoteRepositoryAdmin;
126 private ManagedRepositoryAdmin managedRepositoryAdmin;
129 private ProxyConnectorAdmin proxyConnectorAdmin;
132 private NetworkProxyAdmin networkProxyAdmin;
135 @Named( "repositoryPathTranslator#maven2" )
136 private RepositoryPathTranslator pathTranslator;
139 private WagonFactory wagonFactory;
142 private ApplicationContext applicationContext;
145 @Named( "pathParser#default" )
146 private PathParser pathParser;
148 private static final String METADATA_FILENAME_START = "maven-metadata";
150 private static final String METADATA_FILENAME = METADATA_FILENAME_START + ".xml";
152 // This array must be lexically sorted
153 private static final String[] IGNORED_FILES = { METADATA_FILENAME, "resolver-status.properties" };
155 private static final MavenXpp3Reader MAVEN_XPP_3_READER = new MavenXpp3Reader();
159 public void initialize()
161 builder = new DefaultModelBuilderFactory().newInstance();
166 public ProjectMetadata readProjectMetadata( String repoId, String namespace, String projectId )
168 // TODO: could natively implement the "shared model" concept from the browse action to avoid needing it there?
173 public ProjectVersionMetadata readProjectVersionMetadata( ReadMetadataRequest readMetadataRequest )
174 throws RepositoryStorageMetadataNotFoundException, RepositoryStorageMetadataInvalidException,
175 RepositoryStorageRuntimeException
179 ManagedRepository managedRepository =
180 managedRepositoryAdmin.getManagedRepository( readMetadataRequest.getRepositoryId() );
181 String artifactVersion = readMetadataRequest.getProjectVersion();
182 // olamy: in case of browsing via the ui we can mix repos (parent of a SNAPSHOT can come from release repo)
183 if ( !readMetadataRequest.isBrowsingRequest() )
185 if ( VersionUtil.isSnapshot( artifactVersion ) )
187 // skygo trying to improve speed by honoring managed configuration MRM-1658
188 if ( managedRepository.isReleases() && !managedRepository.isSnapshots() )
190 throw new RepositoryStorageRuntimeException( "lookforsnaponreleaseonly",
191 "managed repo is configured for release only" );
196 if ( !managedRepository.isReleases() && managedRepository.isSnapshots() )
198 throw new RepositoryStorageRuntimeException( "lookforsreleaseonsneponly",
199 "managed repo is configured for snapshot only" );
203 File basedir = new File( managedRepository.getLocation() );
204 if ( VersionUtil.isSnapshot( artifactVersion ) )
206 File metadataFile = pathTranslator.toFile( basedir, readMetadataRequest.getNamespace(),
207 readMetadataRequest.getProjectId(), artifactVersion,
211 ArchivaRepositoryMetadata metadata = MavenMetadataReader.read( metadataFile );
213 // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
214 SnapshotVersion snapshotVersion = metadata.getSnapshotVersion();
215 if ( snapshotVersion != null )
218 artifactVersion.substring( 0, artifactVersion.length() - 8 ); // remove SNAPSHOT from end
220 artifactVersion + snapshotVersion.getTimestamp() + "-" + snapshotVersion.getBuildNumber();
223 catch ( XMLException e )
225 // unable to parse metadata - LOGGER it, and continue with the version as the original SNAPSHOT version
226 LOGGER.warn( "Invalid metadata: {} - {}", metadataFile, e.getMessage() );
230 // TODO: won't work well with some other layouts, might need to convert artifact parts to ID by path translator
231 String id = readMetadataRequest.getProjectId() + "-" + artifactVersion + ".pom";
233 pathTranslator.toFile( basedir, readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(),
234 readMetadataRequest.getProjectVersion(), id );
236 if ( !file.exists() )
238 // metadata could not be resolved
239 throw new RepositoryStorageMetadataNotFoundException(
240 "The artifact's POM file '" + file.getAbsolutePath() + "' was missing" );
243 // TODO: this is a workaround until we can properly resolve using proxies as well - this doesn't cache
245 List<RemoteRepository> remoteRepositories = new ArrayList<>();
246 Map<String, NetworkProxy> networkProxies = new HashMap<>();
248 Map<String, List<ProxyConnector>> proxyConnectorsMap = proxyConnectorAdmin.getProxyConnectorAsMap();
249 List<ProxyConnector> proxyConnectors = proxyConnectorsMap.get( readMetadataRequest.getRepositoryId() );
250 if ( proxyConnectors != null )
252 for ( ProxyConnector proxyConnector : proxyConnectors )
254 RemoteRepository remoteRepoConfig =
255 remoteRepositoryAdmin.getRemoteRepository( proxyConnector.getTargetRepoId() );
257 if ( remoteRepoConfig != null )
259 remoteRepositories.add( remoteRepoConfig );
261 NetworkProxy networkProxyConfig =
262 networkProxyAdmin.getNetworkProxy( proxyConnector.getProxyId() );
264 if ( networkProxyConfig != null )
266 // key/value: remote repo ID/proxy info
267 networkProxies.put( proxyConnector.getTargetRepoId(), networkProxyConfig );
273 // That's a browsing request so we can a mix of SNAPSHOT and release artifacts (especially with snapshots which
274 // can have released parent pom
275 if ( readMetadataRequest.isBrowsingRequest() )
277 remoteRepositories.addAll( remoteRepositoryAdmin.getRemoteRepositories() );
280 ModelBuildingRequest req =
281 new DefaultModelBuildingRequest().setProcessPlugins( false ).setPomFile( file ).setTwoPhaseBuilding(
282 false ).setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL );
284 //MRM-1607. olamy this will resolve jdk profiles on the current running archiva jvm
285 req.setSystemProperties( System.getProperties() );
288 req.setModelResolver(
289 new RepositoryModelResolver( managedRepository, pathTranslator, wagonFactory, remoteRepositories,
290 networkProxies, managedRepository ) );
295 model = builder.build( req ).getEffectiveModel();
297 catch ( ModelBuildingException e )
299 String msg = "The artifact's POM file '" + file + "' was invalid: " + e.getMessage();
301 List<ModelProblem> modelProblems = e.getProblems();
302 for ( ModelProblem problem : modelProblems )
304 // MRM-1411, related to MRM-1335
305 // this means that the problem was that the parent wasn't resolved!
306 // olamy really hackhish but fail with java profile so use error message
307 // || ( StringUtils.startsWith( problem.getMessage(), "Failed to determine Java version for profile" ) )
308 // but setTwoPhaseBuilding(true) fix that
309 if ( ( problem.getException() instanceof FileNotFoundException && e.getModelId() != null &&
310 !e.getModelId().equals( problem.getModelId() ) ) )
312 LOGGER.warn( "The artifact's parent POM file '{}' cannot be resolved. "
313 + "Using defaults for project version metadata..", file );
315 ProjectVersionMetadata metadata = new ProjectVersionMetadata();
316 metadata.setId( readMetadataRequest.getProjectVersion() );
318 MavenProjectFacet facet = new MavenProjectFacet();
319 facet.setGroupId( readMetadataRequest.getNamespace() );
320 facet.setArtifactId( readMetadataRequest.getProjectId() );
321 facet.setPackaging( "jar" );
322 metadata.addFacet( facet );
325 "Error in resolving artifact's parent POM file. " + ( problem.getException() == null
326 ? problem.getMessage()
327 : problem.getException().getMessage() );
328 RepositoryProblemFacet repoProblemFacet = new RepositoryProblemFacet();
329 repoProblemFacet.setRepositoryId( readMetadataRequest.getRepositoryId() );
330 repoProblemFacet.setId( readMetadataRequest.getRepositoryId() );
331 repoProblemFacet.setMessage( errMsg );
332 repoProblemFacet.setProblem( errMsg );
333 repoProblemFacet.setProject( readMetadataRequest.getProjectId() );
334 repoProblemFacet.setVersion( readMetadataRequest.getProjectVersion() );
335 repoProblemFacet.setNamespace( readMetadataRequest.getNamespace() );
337 metadata.addFacet( repoProblemFacet );
343 throw new RepositoryStorageMetadataInvalidException( "invalid-pom", msg, e );
346 // Check if the POM is in the correct location
347 boolean correctGroupId = readMetadataRequest.getNamespace().equals( model.getGroupId() );
348 boolean correctArtifactId = readMetadataRequest.getProjectId().equals( model.getArtifactId() );
349 boolean correctVersion = readMetadataRequest.getProjectVersion().equals( model.getVersion() );
350 if ( !correctGroupId || !correctArtifactId || !correctVersion )
352 StringBuilder message = new StringBuilder( "Incorrect POM coordinates in '" + file + "':" );
353 if ( !correctGroupId )
355 message.append( "\nIncorrect group ID: " ).append( model.getGroupId() );
357 if ( !correctArtifactId )
359 message.append( "\nIncorrect artifact ID: " ).append( model.getArtifactId() );
361 if ( !correctVersion )
363 message.append( "\nIncorrect version: " ).append( model.getVersion() );
366 throw new RepositoryStorageMetadataInvalidException( "mislocated-pom", message.toString() );
369 ProjectVersionMetadata metadata = new ProjectVersionMetadata();
370 metadata.setCiManagement( convertCiManagement( model.getCiManagement() ) );
371 metadata.setDescription( model.getDescription() );
372 metadata.setId( readMetadataRequest.getProjectVersion() );
373 metadata.setIssueManagement( convertIssueManagement( model.getIssueManagement() ) );
374 metadata.setLicenses( convertLicenses( model.getLicenses() ) );
375 metadata.setMailingLists( convertMailingLists( model.getMailingLists() ) );
376 metadata.setDependencies( convertDependencies( model.getDependencies() ) );
377 metadata.setName( model.getName() );
378 metadata.setOrganization( convertOrganization( model.getOrganization() ) );
379 metadata.setScm( convertScm( model.getScm() ) );
380 metadata.setUrl( model.getUrl() );
381 metadata.setProperties( model.getProperties() );
383 MavenProjectFacet facet = new MavenProjectFacet();
384 facet.setGroupId( model.getGroupId() != null ? model.getGroupId() : model.getParent().getGroupId() );
385 facet.setArtifactId( model.getArtifactId() );
386 facet.setPackaging( model.getPackaging() );
387 if ( model.getParent() != null )
389 MavenProjectParent parent = new MavenProjectParent();
390 parent.setGroupId( model.getParent().getGroupId() );
391 parent.setArtifactId( model.getParent().getArtifactId() );
392 parent.setVersion( model.getParent().getVersion() );
393 facet.setParent( parent );
395 metadata.addFacet( facet );
399 catch ( RepositoryAdminException e )
401 throw new RepositoryStorageRuntimeException( "repo-admin", e.getMessage(), e );
405 public void setWagonFactory( WagonFactory wagonFactory )
407 this.wagonFactory = wagonFactory;
410 private List<org.apache.archiva.metadata.model.Dependency> convertDependencies( List<Dependency> dependencies )
412 List<org.apache.archiva.metadata.model.Dependency> l = new ArrayList<>();
413 for ( Dependency dependency : dependencies )
415 org.apache.archiva.metadata.model.Dependency newDependency =
416 new org.apache.archiva.metadata.model.Dependency();
417 newDependency.setArtifactId( dependency.getArtifactId() );
418 newDependency.setClassifier( dependency.getClassifier() );
419 newDependency.setGroupId( dependency.getGroupId() );
420 newDependency.setOptional( dependency.isOptional() );
421 newDependency.setScope( dependency.getScope() );
422 newDependency.setSystemPath( dependency.getSystemPath() );
423 newDependency.setType( dependency.getType() );
424 newDependency.setVersion( dependency.getVersion() );
425 l.add( newDependency );
430 private org.apache.archiva.metadata.model.Scm convertScm( Scm scm )
432 org.apache.archiva.metadata.model.Scm newScm = null;
435 newScm = new org.apache.archiva.metadata.model.Scm();
436 newScm.setConnection( scm.getConnection() );
437 newScm.setDeveloperConnection( scm.getDeveloperConnection() );
438 newScm.setUrl( scm.getUrl() );
443 private org.apache.archiva.metadata.model.Organization convertOrganization( Organization organization )
445 org.apache.archiva.metadata.model.Organization org = null;
446 if ( organization != null )
448 org = new org.apache.archiva.metadata.model.Organization();
449 org.setName( organization.getName() );
450 org.setUrl( organization.getUrl() );
455 private List<org.apache.archiva.metadata.model.License> convertLicenses( List<License> licenses )
457 List<org.apache.archiva.metadata.model.License> l = new ArrayList<>();
458 for ( License license : licenses )
460 org.apache.archiva.metadata.model.License newLicense = new org.apache.archiva.metadata.model.License();
461 newLicense.setName( license.getName() );
462 newLicense.setUrl( license.getUrl() );
468 private List<org.apache.archiva.metadata.model.MailingList> convertMailingLists( List<MailingList> mailingLists )
470 List<org.apache.archiva.metadata.model.MailingList> l = new ArrayList<>();
471 for ( MailingList mailingList : mailingLists )
473 org.apache.archiva.metadata.model.MailingList newMailingList =
474 new org.apache.archiva.metadata.model.MailingList();
475 newMailingList.setName( mailingList.getName() );
476 newMailingList.setMainArchiveUrl( mailingList.getArchive() );
477 newMailingList.setPostAddress( mailingList.getPost() );
478 newMailingList.setSubscribeAddress( mailingList.getSubscribe() );
479 newMailingList.setUnsubscribeAddress( mailingList.getUnsubscribe() );
480 newMailingList.setOtherArchives( mailingList.getOtherArchives() );
481 l.add( newMailingList );
486 private org.apache.archiva.metadata.model.IssueManagement convertIssueManagement( IssueManagement issueManagement )
488 org.apache.archiva.metadata.model.IssueManagement im = null;
489 if ( issueManagement != null )
491 im = new org.apache.archiva.metadata.model.IssueManagement();
492 im.setSystem( issueManagement.getSystem() );
493 im.setUrl( issueManagement.getUrl() );
498 private org.apache.archiva.metadata.model.CiManagement convertCiManagement( CiManagement ciManagement )
500 org.apache.archiva.metadata.model.CiManagement ci = null;
501 if ( ciManagement != null )
503 ci = new org.apache.archiva.metadata.model.CiManagement();
504 ci.setSystem( ciManagement.getSystem() );
505 ci.setUrl( ciManagement.getUrl() );
511 public Collection<String> listRootNamespaces( String repoId, Filter<String> filter )
512 throws RepositoryStorageRuntimeException
514 File dir = getRepositoryBasedir( repoId );
516 return getSortedFiles( dir, filter );
519 private static Collection<String> getSortedFiles( File dir, Filter<String> filter )
521 List<String> fileNames;
522 String[] files = dir.list( new DirectoryFilter( filter ) );
525 fileNames = new ArrayList<>( Arrays.asList( files ) );
526 Collections.sort( fileNames );
530 fileNames = Collections.emptyList();
535 private File getRepositoryBasedir( String repoId )
536 throws RepositoryStorageRuntimeException
540 ManagedRepository repositoryConfiguration = managedRepositoryAdmin.getManagedRepository( repoId );
542 return new File( repositoryConfiguration.getLocation() );
544 catch ( RepositoryAdminException e )
546 throw new RepositoryStorageRuntimeException( "repo-admin", e.getMessage(), e );
551 public Collection<String> listNamespaces( String repoId, String namespace, Filter<String> filter )
552 throws RepositoryStorageRuntimeException
554 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace );
556 // scan all the directories which are potential namespaces. Any directories known to be projects are excluded
557 List<String> namespaces = new ArrayList<>();
558 File[] files = dir.listFiles( new DirectoryFilter( filter ) );
561 for ( File file : files )
563 if ( !isProject( file, filter ) )
565 namespaces.add( file.getName() );
569 Collections.sort( namespaces );
574 public Collection<String> listProjects( String repoId, String namespace, Filter<String> filter )
575 throws RepositoryStorageRuntimeException
577 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace );
579 // scan all directories in the namespace, and only include those that are known to be projects
580 List<String> projects = new ArrayList<>();
582 File[] files = dir.listFiles( new DirectoryFilter( filter ) );
585 for ( File file : files )
587 if ( isProject( file, filter ) )
589 projects.add( file.getName() );
593 Collections.sort( projects );
598 public Collection<String> listProjectVersions( String repoId, String namespace, String projectId,
599 Filter<String> filter )
600 throws RepositoryStorageRuntimeException
602 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace, projectId );
604 // all directories in a project directory can be considered a version
605 return getSortedFiles( dir, filter );
609 public Collection<ArtifactMetadata> readArtifactsMetadata( ReadMetadataRequest readMetadataRequest )
610 throws RepositoryStorageRuntimeException
612 File dir = pathTranslator.toFile( getRepositoryBasedir( readMetadataRequest.getRepositoryId() ),
613 readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(),
614 readMetadataRequest.getProjectVersion() );
616 // all files that are not metadata and not a checksum / signature are considered artifacts
617 File[] files = dir.listFiles( new ArtifactDirectoryFilter( readMetadataRequest.getFilter() ) );
619 List<ArtifactMetadata> artifacts = new ArrayList<>();
623 for ( File file : files )
626 ArtifactMetadata metadata =
627 getArtifactFromFile(readMetadataRequest.getRepositoryId(), readMetadataRequest.getNamespace(),
628 readMetadataRequest.getProjectId(), readMetadataRequest.getProjectVersion(),
630 artifacts.add(metadata);
631 } catch (Exception ex) {
632 LOGGER.error("Error while retrieving metadata of file {} (Project: {}, Repository: {}): {}",
633 file.getName(), readMetadataRequest.getProjectId(), readMetadataRequest.getRepositoryId(),
638 // We throw only an error, if the number of errors equals the number of files
639 if (errorCount>0 && errorCount==files.length) {
640 throw new RepositoryStorageRuntimeException(readMetadataRequest.getRepositoryId(), "Could not retrieve metadata of the files");
647 public ArtifactMetadata readArtifactMetadataFromPath( String repoId, String path )
648 throws RepositoryStorageRuntimeException
650 ArtifactMetadata metadata = pathTranslator.getArtifactForPath( repoId, path );
652 populateArtifactMetadataFromFile( metadata, new File( getRepositoryBasedir( repoId ), path ) );
657 private ArtifactMetadata getArtifactFromFile( String repoId, String namespace, String projectId,
658 String projectVersion, File file )
660 ArtifactMetadata metadata =
661 pathTranslator.getArtifactFromId( repoId, namespace, projectId, projectVersion, file.getName() );
663 populateArtifactMetadataFromFile( metadata, file );
669 public void applyServerSideRelocation( ManagedRepositoryContent managedRepository, ArtifactReference artifact )
670 throws ProxyDownloadException
672 if ( "pom".equals( artifact.getType() ) )
677 // Build the artifact POM reference
678 ArtifactReference pomReference = new ArtifactReference();
679 pomReference.setGroupId( artifact.getGroupId() );
680 pomReference.setArtifactId( artifact.getArtifactId() );
681 pomReference.setVersion( artifact.getVersion() );
682 pomReference.setType( "pom" );
684 RepositoryProxyConnectors connectors =
685 applicationContext.getBean( "repositoryProxyConnectors#default", RepositoryProxyConnectors.class );
687 // Get the artifact POM from proxied repositories if needed
688 connectors.fetchFromProxies( managedRepository, pomReference );
690 // Open and read the POM from the managed repo
691 Path pom = managedRepository.toFile( pomReference );
693 if ( !Files.exists(pom) )
700 // MavenXpp3Reader leaves the file open, so we need to close it ourselves.
703 try (Reader reader = Files.newBufferedReader( pom, Charset.defaultCharset() ))
705 model = MAVEN_XPP_3_READER.read( reader );
708 DistributionManagement dist = model.getDistributionManagement();
711 Relocation relocation = dist.getRelocation();
712 if ( relocation != null )
714 // artifact is relocated : update the repositoryPath
715 if ( relocation.getGroupId() != null )
717 artifact.setGroupId( relocation.getGroupId() );
719 if ( relocation.getArtifactId() != null )
721 artifact.setArtifactId( relocation.getArtifactId() );
723 if ( relocation.getVersion() != null )
725 artifact.setVersion( relocation.getVersion() );
730 catch ( IOException e )
732 // Unable to read POM : ignore.
734 catch ( XmlPullParserException e )
736 // Invalid POM : ignore
742 public String getFilePath( String requestPath, ManagedRepository managedRepository )
744 // managedRepository can be null
745 // extract artifact reference from url
746 // groupId:artifactId:version:packaging:classifier
747 //org/apache/archiva/archiva-checksum/1.4-M4-SNAPSHOT/archiva-checksum-1.4-M4-SNAPSHOT.jar
748 String logicalResource = null;
749 String requestPathInfo = StringUtils.defaultString( requestPath );
751 //remove prefix ie /repository/blah becomes /blah
752 requestPathInfo = removePrefix( requestPathInfo );
754 // Remove prefixing slash as the repository id doesn't contain it;
755 if ( requestPathInfo.startsWith( "/" ) )
757 requestPathInfo = requestPathInfo.substring( 1 );
760 int slash = requestPathInfo.indexOf( '/' );
763 logicalResource = requestPathInfo.substring( slash );
765 if ( logicalResource.endsWith( "/.." ) )
767 logicalResource += "/";
770 if ( logicalResource != null && logicalResource.startsWith( "//" ) )
772 logicalResource = logicalResource.substring( 1 );
775 if ( logicalResource == null )
777 logicalResource = "/";
782 logicalResource = "/";
784 return logicalResource;
789 public String getFilePathWithVersion( final String requestPath, ManagedRepositoryContent managedRepositoryContent )
790 throws XMLException, RelocationException
793 if ( StringUtils.endsWith( requestPath, METADATA_FILENAME ) )
795 return getFilePath( requestPath, managedRepositoryContent.getRepository() );
798 String filePath = getFilePath( requestPath, managedRepositoryContent.getRepository() );
800 ArtifactReference artifactReference = null;
803 artifactReference = pathParser.toArtifactReference( filePath );
805 catch ( LayoutException e )
810 if ( StringUtils.endsWith( artifactReference.getVersion(), VersionUtil.SNAPSHOT ) )
812 // read maven metadata to get last timestamp
813 File metadataDir = new File( managedRepositoryContent.getRepoRoot(), filePath ).getParentFile();
814 if ( !metadataDir.exists() )
818 File metadataFile = new File( metadataDir, METADATA_FILENAME );
819 if ( !metadataFile.exists() )
823 ArchivaRepositoryMetadata archivaRepositoryMetadata = MavenMetadataReader.read( metadataFile );
824 int buildNumber = archivaRepositoryMetadata.getSnapshotVersion().getBuildNumber();
825 String timestamp = archivaRepositoryMetadata.getSnapshotVersion().getTimestamp();
828 if ( buildNumber < 1 && timestamp == null )
833 // org/apache/archiva/archiva-checksum/1.4-M4-SNAPSHOT/archiva-checksum-1.4-M4-SNAPSHOT.jar
834 // -> archiva-checksum-1.4-M4-20130425.081822-1.jar
836 filePath = StringUtils.replace( filePath, //
837 artifactReference.getArtifactId() //
838 + "-" + artifactReference.getVersion(), //
839 artifactReference.getArtifactId() //
840 + "-" + StringUtils.remove( artifactReference.getVersion(),
841 "-" + VersionUtil.SNAPSHOT ) //
843 + "-" + buildNumber );
845 throw new RelocationException( "/repository/" + managedRepositoryContent.getRepository().getId() +
846 ( StringUtils.startsWith( filePath, "/" ) ? "" : "/" ) + filePath,
847 RelocationException.RelocationType.TEMPORARY );
854 //-----------------------------
856 //-----------------------------
864 private static String removePrefix( final String href )
866 String[] parts = StringUtils.split( href, '/' );
867 parts = (String[]) ArrayUtils.subarray( parts, 1, parts.length );
868 if ( parts == null || parts.length == 0 )
873 String joinedString = StringUtils.join( parts, '/' );
874 if ( href.endsWith( "/" ) )
876 joinedString = joinedString + "/";
882 private static void populateArtifactMetadataFromFile( ArtifactMetadata metadata, File file )
884 metadata.setWhenGathered( new Date() );
885 metadata.setFileLastModified( file.lastModified() );
886 ChecksummedFile checksummedFile = new ChecksummedFile( file );
889 metadata.setMd5( checksummedFile.calculateChecksum( ChecksumAlgorithm.MD5 ) );
891 catch ( IOException e )
893 LOGGER.error( "Unable to checksum file {}: {},MD5", file, e.getMessage() );
897 metadata.setSha1( checksummedFile.calculateChecksum( ChecksumAlgorithm.SHA1 ) );
899 catch ( IOException e )
901 LOGGER.error( "Unable to checksum file {}: {},SHA1", file, e.getMessage() );
903 metadata.setSize( file.length() );
906 private boolean isProject( File dir, Filter<String> filter )
908 // scan directories for a valid project version subdirectory, meaning this must be a project directory
909 File[] files = dir.listFiles( new DirectoryFilter( filter ) );
912 for ( File file : files )
914 if ( isProjectVersion( file ) )
921 // if a metadata file is present, check if this is the "artifactId" directory, marking it as a project
922 ArchivaRepositoryMetadata metadata = readMetadata( dir );
923 if ( metadata != null && dir.getName().equals( metadata.getArtifactId() ) )
931 private boolean isProjectVersion( File dir )
933 final String artifactId = dir.getParentFile().getName();
934 final String projectVersion = dir.getName();
936 // check if there is a POM artifact file to ensure it is a version directory
938 if ( VersionUtil.isSnapshot( projectVersion ) )
940 files = dir.listFiles( new PomFilenameFilter( artifactId, projectVersion ) );
944 final String pomFile = artifactId + "-" + projectVersion + ".pom";
945 files = dir.listFiles( new PomFileFilter( pomFile ) );
947 if ( files != null && files.length > 0 )
952 // if a metadata file is present, check if this is the "version" directory, marking it as a project version
953 ArchivaRepositoryMetadata metadata = readMetadata( dir );
954 if ( metadata != null && projectVersion.equals( metadata.getVersion() ) )
962 private ArchivaRepositoryMetadata readMetadata( File directory )
964 ArchivaRepositoryMetadata metadata = null;
965 File metadataFile = new File( directory, METADATA_FILENAME );
966 if ( metadataFile.exists() )
970 metadata = MavenMetadataReader.read( metadataFile );
972 catch ( XMLException e )
974 // ignore missing or invalid metadata
980 private static class DirectoryFilter
981 implements FilenameFilter
983 private final Filter<String> filter;
985 public DirectoryFilter( Filter<String> filter )
987 this.filter = filter;
991 public boolean accept( File dir, String name )
993 if ( !filter.accept( name ) )
997 else if ( name.startsWith( "." ) )
1001 else if ( !new File( dir, name ).isDirectory() )
1009 private static class ArtifactDirectoryFilter
1010 implements FilenameFilter
1012 private final Filter<String> filter;
1014 private ArtifactDirectoryFilter( Filter<String> filter )
1016 this.filter = filter;
1020 public boolean accept( File dir, String name )
1022 // TODO compare to logic in maven-repository-layer
1023 if ( !filter.accept( name ) )
1027 else if ( name.startsWith( "." ) )
1031 else if ( name.endsWith( ".md5" ) || name.endsWith( ".sha1" ) || name.endsWith( ".asc" ) )
1035 else if ( Arrays.binarySearch(IGNORED_FILES, name)>=0 )
1039 else if ( new File( dir, name ).isDirectory() )
1043 // some files from remote repositories can have name like maven-metadata-archiva-vm-all-public.xml
1044 else if ( StringUtils.startsWith( name, METADATA_FILENAME_START ) && StringUtils.endsWith( name, ".xml" ) )
1055 private static final class PomFilenameFilter
1056 implements FilenameFilter
1059 private final String artifactId, projectVersion;
1061 private PomFilenameFilter( String artifactId, String projectVersion )
1063 this.artifactId = artifactId;
1064 this.projectVersion = projectVersion;
1068 public boolean accept( File dir, String name )
1070 if ( name.startsWith( artifactId + "-" ) && name.endsWith( ".pom" ) )
1072 String v = name.substring( artifactId.length() + 1, name.length() - 4 );
1073 v = VersionUtil.getBaseVersion( v );
1074 if ( v.equals( projectVersion ) )
1083 private static class PomFileFilter
1084 implements FilenameFilter
1086 private final String pomFile;
1088 private PomFileFilter( String pomFile )
1090 this.pomFile = pomFile;
1094 public boolean accept( File dir, String name )
1096 return pomFile.equals( name );
1101 public PathParser getPathParser()
1106 public void setPathParser( PathParser pathParser )
1108 this.pathParser = pathParser;