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.checksum.ChecksumAlgorithm;
23 import org.apache.archiva.checksum.ChecksummedFile;
24 import org.apache.archiva.metadata.model.ArtifactMetadata;
25 import org.apache.archiva.metadata.model.ProjectMetadata;
26 import org.apache.archiva.metadata.model.ProjectVersionMetadata;
27 import org.apache.archiva.metadata.repository.filter.Filter;
28 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
29 import org.apache.archiva.metadata.repository.storage.RepositoryStorage;
30 import org.apache.archiva.metadata.repository.storage.RepositoryStorageMetadataInvalidException;
31 import org.apache.archiva.metadata.repository.storage.RepositoryStorageMetadataNotFoundException;
32 import org.apache.archiva.proxy.common.WagonFactory;
33 import org.apache.archiva.reports.RepositoryProblemFacet;
34 import org.apache.maven.archiva.common.utils.VersionUtil;
35 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
36 import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
37 import org.apache.maven.archiva.configuration.NetworkProxyConfiguration;
38 import org.apache.maven.archiva.configuration.ProxyConnectorConfiguration;
39 import org.apache.maven.archiva.configuration.RemoteRepositoryConfiguration;
40 import org.apache.archiva.xml.XMLException;
41 import org.apache.maven.model.CiManagement;
42 import org.apache.maven.model.Dependency;
43 import org.apache.maven.model.IssueManagement;
44 import org.apache.maven.model.License;
45 import org.apache.maven.model.MailingList;
46 import org.apache.maven.model.Model;
47 import org.apache.maven.model.Organization;
48 import org.apache.maven.model.Scm;
49 import org.apache.maven.model.building.DefaultModelBuilderFactory;
50 import org.apache.maven.model.building.DefaultModelBuildingRequest;
51 import org.apache.maven.model.building.ModelBuilder;
52 import org.apache.maven.model.building.ModelBuildingException;
53 import org.apache.maven.model.building.ModelBuildingRequest;
54 import org.apache.maven.model.building.ModelProblem;
55 import org.apache.maven.wagon.proxy.ProxyInfo;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58 import org.springframework.stereotype.Service;
61 import java.io.FileNotFoundException;
62 import java.io.FilenameFilter;
63 import java.io.IOException;
64 import java.util.ArrayList;
65 import java.util.Arrays;
66 import java.util.Collection;
67 import java.util.Collections;
68 import java.util.Date;
69 import java.util.HashMap;
70 import java.util.List;
72 import javax.annotation.PostConstruct;
73 import javax.inject.Inject;
74 import javax.inject.Named;
77 * Maven 2 repository format storage implementation. This class currently takes parameters to indicate the repository to
78 * deal with rather than being instantiated per-repository.
79 * FIXME: instantiate one per repository and allocate permanently from a factory (which can be obtained within the session).
80 * TODO: finish Maven 1 implementation to prove this API
82 * The session is passed in as an argument to obtain any necessary resources, rather than the class being instantiated
83 * within the session in the context of a single managed repository's resolution needs.
85 * plexus.component role="org.apache.archiva.metadata.repository.storage.RepositoryStorage" role-hint="maven2"
87 @Service( "repositoryStorage#maven2" )
88 public class Maven2RepositoryStorage
89 implements RepositoryStorage
94 private ModelBuilder builder;
100 @Named( value = "archivaConfiguration#default" )
101 private ArchivaConfiguration archivaConfiguration;
104 * plexus.requirement role-hint="maven2"
107 @Named( value = "repositoryPathTranslator#maven2" )
108 private RepositoryPathTranslator pathTranslator;
111 private WagonFactory wagonFactory;
113 private final static Logger log = LoggerFactory.getLogger( Maven2RepositoryStorage.class );
115 private static final String METADATA_FILENAME = "maven-metadata.xml";
119 public void initialize()
121 DefaultModelBuilderFactory defaultModelBuilderFactory = new DefaultModelBuilderFactory();
122 builder = defaultModelBuilderFactory.newInstance();
125 public ProjectMetadata readProjectMetadata( String repoId, String namespace, String projectId )
127 // TODO: could natively implement the "shared model" concept from the browse action to avoid needing it there?
131 public ProjectVersionMetadata readProjectVersionMetadata( String repoId, String namespace, String projectId,
132 String projectVersion )
133 throws RepositoryStorageMetadataNotFoundException, RepositoryStorageMetadataInvalidException
135 ManagedRepositoryConfiguration repositoryConfiguration =
136 archivaConfiguration.getConfiguration().findManagedRepositoryById( repoId );
138 String artifactVersion = projectVersion;
140 File basedir = new File( repositoryConfiguration.getLocation() );
141 if ( VersionUtil.isSnapshot( projectVersion ) )
144 pathTranslator.toFile( basedir, namespace, projectId, projectVersion, METADATA_FILENAME );
147 MavenRepositoryMetadata metadata = MavenRepositoryMetadataReader.read( metadataFile );
149 // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
150 MavenRepositoryMetadata.Snapshot snapshotVersion = metadata.getSnapshotVersion();
151 if ( snapshotVersion != null )
154 artifactVersion.substring( 0, artifactVersion.length() - 8 ); // remove SNAPSHOT from end
156 artifactVersion + snapshotVersion.getTimestamp() + "-" + snapshotVersion.getBuildNumber();
159 catch ( XMLException e )
161 // unable to parse metadata - log it, and continue with the version as the original SNAPSHOT version
162 log.warn( "Invalid metadata: " + metadataFile + " - " + e.getMessage() );
166 // TODO: won't work well with some other layouts, might need to convert artifact parts to ID by path translator
167 String id = projectId + "-" + artifactVersion + ".pom";
168 File file = pathTranslator.toFile( basedir, namespace, projectId, projectVersion, id );
170 if ( !file.exists() )
172 // metadata could not be resolved
173 throw new RepositoryStorageMetadataNotFoundException(
174 "The artifact's POM file '" + file.getAbsolutePath() + "' was missing" );
177 // TODO: this is a workaround until we can properly resolve using proxies as well - this doesn't cache
179 List<RemoteRepositoryConfiguration> remoteRepositories = new ArrayList<RemoteRepositoryConfiguration>();
180 Map<String, ProxyInfo> networkProxies = new HashMap<String, ProxyInfo>();
182 Map<String, List<ProxyConnectorConfiguration>> proxyConnectorsMap = archivaConfiguration.getConfiguration().getProxyConnectorAsMap();
183 List<ProxyConnectorConfiguration> proxyConnectors = proxyConnectorsMap.get( repoId );
184 if( proxyConnectors != null )
186 for( ProxyConnectorConfiguration proxyConnector : proxyConnectors )
188 RemoteRepositoryConfiguration remoteRepoConfig = archivaConfiguration.getConfiguration().findRemoteRepositoryById(
189 proxyConnector.getTargetRepoId() );
191 if( remoteRepoConfig != null )
193 remoteRepositories.add( remoteRepoConfig );
195 NetworkProxyConfiguration networkProxyConfig = archivaConfiguration.getConfiguration().getNetworkProxiesAsMap().get(
196 proxyConnector.getProxyId() );
198 if( networkProxyConfig != null )
200 ProxyInfo proxy = new ProxyInfo();
201 proxy.setType( networkProxyConfig.getProtocol() );
202 proxy.setHost( networkProxyConfig.getHost() );
203 proxy.setPort( networkProxyConfig.getPort() );
204 proxy.setUserName( networkProxyConfig.getUsername() );
205 proxy.setPassword( networkProxyConfig.getPassword() );
207 // key/value: remote repo ID/proxy info
208 networkProxies.put( proxyConnector.getTargetRepoId(), proxy );
214 ModelBuildingRequest req = new DefaultModelBuildingRequest();
215 req.setProcessPlugins( false );
216 req.setPomFile( file );
219 req.setModelResolver( new RepositoryModelResolver( basedir, pathTranslator, wagonFactory, remoteRepositories,
220 networkProxies, repositoryConfiguration ) );
221 req.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL );
226 model = builder.build( req ).getEffectiveModel();
228 catch ( ModelBuildingException e )
230 String msg = "The artifact's POM file '" + file + "' was invalid: " + e.getMessage();
232 List<ModelProblem> modelProblems = e.getProblems();
233 for( ModelProblem problem : modelProblems )
235 // MRM-1411, related to MRM-1335
236 // this means that the problem was that the parent wasn't resolved!
237 if( problem.getException() instanceof FileNotFoundException && e.getModelId() != null &&
238 !e.getModelId().equals( problem.getModelId() ) )
240 log.warn( "The artifact's parent POM file '" + file + "' cannot be resolved. " +
241 "Using defaults for project version metadata.." );
243 ProjectVersionMetadata metadata = new ProjectVersionMetadata();
244 metadata.setId( projectVersion );
246 MavenProjectFacet facet = new MavenProjectFacet();
247 facet.setGroupId( namespace );
248 facet.setArtifactId( projectId );
249 facet.setPackaging( "jar" );
250 metadata.addFacet( facet );
252 String errMsg = "Error in resolving artifact's parent POM file. " + problem.getException().getMessage();
253 RepositoryProblemFacet repoProblemFacet = new RepositoryProblemFacet();
254 repoProblemFacet.setRepositoryId( repoId );
255 repoProblemFacet.setId( repoId );
256 repoProblemFacet.setMessage( errMsg );
257 repoProblemFacet.setProblem( errMsg );
258 repoProblemFacet.setProject( projectId );
259 repoProblemFacet.setVersion( projectVersion );
260 repoProblemFacet.setNamespace( namespace );
262 metadata.addFacet( repoProblemFacet );
268 throw new RepositoryStorageMetadataInvalidException( "invalid-pom", msg, e );
271 // Check if the POM is in the correct location
272 boolean correctGroupId = namespace.equals( model.getGroupId() );
273 boolean correctArtifactId = projectId.equals( model.getArtifactId() );
274 boolean correctVersion = projectVersion.equals( model.getVersion() );
275 if ( !correctGroupId || !correctArtifactId || !correctVersion )
277 StringBuilder message = new StringBuilder( "Incorrect POM coordinates in '" + file + "':" );
278 if ( !correctGroupId )
280 message.append( "\nIncorrect group ID: " ).append( model.getGroupId() );
282 if ( !correctArtifactId )
284 message.append( "\nIncorrect artifact ID: " ).append( model.getArtifactId() );
286 if ( !correctVersion )
288 message.append( "\nIncorrect version: " ).append( model.getVersion() );
291 throw new RepositoryStorageMetadataInvalidException( "mislocated-pom", message.toString() );
294 ProjectVersionMetadata metadata = new ProjectVersionMetadata();
295 metadata.setCiManagement( convertCiManagement( model.getCiManagement() ) );
296 metadata.setDescription( model.getDescription() );
297 metadata.setId( projectVersion );
298 metadata.setIssueManagement( convertIssueManagement( model.getIssueManagement() ) );
299 metadata.setLicenses( convertLicenses( model.getLicenses() ) );
300 metadata.setMailingLists( convertMailingLists( model.getMailingLists() ) );
301 metadata.setDependencies( convertDependencies( model.getDependencies() ) );
302 metadata.setName( model.getName() );
303 metadata.setOrganization( convertOrganization( model.getOrganization() ) );
304 metadata.setScm( convertScm( model.getScm() ) );
305 metadata.setUrl( model.getUrl() );
307 MavenProjectFacet facet = new MavenProjectFacet();
308 facet.setGroupId( model.getGroupId() != null ? model.getGroupId() : model.getParent().getGroupId() );
309 facet.setArtifactId( model.getArtifactId() );
310 facet.setPackaging( model.getPackaging() );
311 if ( model.getParent() != null )
313 MavenProjectParent parent = new MavenProjectParent();
314 parent.setGroupId( model.getParent().getGroupId() );
315 parent.setArtifactId( model.getParent().getArtifactId() );
316 parent.setVersion( model.getParent().getVersion() );
317 facet.setParent( parent );
319 metadata.addFacet( facet );
324 public void setWagonFactory( WagonFactory wagonFactory )
326 this.wagonFactory = wagonFactory;
329 private List<org.apache.archiva.metadata.model.Dependency> convertDependencies( List<Dependency> dependencies )
331 List<org.apache.archiva.metadata.model.Dependency> l =
332 new ArrayList<org.apache.archiva.metadata.model.Dependency>();
333 for ( Dependency dependency : dependencies )
335 org.apache.archiva.metadata.model.Dependency newDependency =
336 new org.apache.archiva.metadata.model.Dependency();
337 newDependency.setArtifactId( dependency.getArtifactId() );
338 newDependency.setClassifier( dependency.getClassifier() );
339 newDependency.setGroupId( dependency.getGroupId() );
340 newDependency.setOptional( dependency.isOptional() );
341 newDependency.setScope( dependency.getScope() );
342 newDependency.setSystemPath( dependency.getSystemPath() );
343 newDependency.setType( dependency.getType() );
344 newDependency.setVersion( dependency.getVersion() );
345 l.add( newDependency );
350 private org.apache.archiva.metadata.model.Scm convertScm( Scm scm )
352 org.apache.archiva.metadata.model.Scm newScm = null;
355 newScm = new org.apache.archiva.metadata.model.Scm();
356 newScm.setConnection( scm.getConnection() );
357 newScm.setDeveloperConnection( scm.getDeveloperConnection() );
358 newScm.setUrl( scm.getUrl() );
363 private org.apache.archiva.metadata.model.Organization convertOrganization( Organization organization )
365 org.apache.archiva.metadata.model.Organization org = null;
366 if ( organization != null )
368 org = new org.apache.archiva.metadata.model.Organization();
369 org.setName( organization.getName() );
370 org.setUrl( organization.getUrl() );
375 private List<org.apache.archiva.metadata.model.License> convertLicenses( List<License> licenses )
377 List<org.apache.archiva.metadata.model.License> l = new ArrayList<org.apache.archiva.metadata.model.License>();
378 for ( License license : licenses )
380 org.apache.archiva.metadata.model.License newLicense = new org.apache.archiva.metadata.model.License();
381 newLicense.setName( license.getName() );
382 newLicense.setUrl( license.getUrl() );
388 private List<org.apache.archiva.metadata.model.MailingList> convertMailingLists( List<MailingList> mailingLists )
390 List<org.apache.archiva.metadata.model.MailingList> l =
391 new ArrayList<org.apache.archiva.metadata.model.MailingList>();
392 for ( MailingList mailingList : mailingLists )
394 org.apache.archiva.metadata.model.MailingList newMailingList =
395 new org.apache.archiva.metadata.model.MailingList();
396 newMailingList.setName( mailingList.getName() );
397 newMailingList.setMainArchiveUrl( mailingList.getArchive() );
398 newMailingList.setPostAddress( mailingList.getPost() );
399 newMailingList.setSubscribeAddress( mailingList.getSubscribe() );
400 newMailingList.setUnsubscribeAddress( mailingList.getUnsubscribe() );
401 newMailingList.setOtherArchives( mailingList.getOtherArchives() );
402 l.add( newMailingList );
407 private org.apache.archiva.metadata.model.IssueManagement convertIssueManagement( IssueManagement issueManagement )
409 org.apache.archiva.metadata.model.IssueManagement im = null;
410 if ( issueManagement != null )
412 im = new org.apache.archiva.metadata.model.IssueManagement();
413 im.setSystem( issueManagement.getSystem() );
414 im.setUrl( issueManagement.getUrl() );
419 private org.apache.archiva.metadata.model.CiManagement convertCiManagement( CiManagement ciManagement )
421 org.apache.archiva.metadata.model.CiManagement ci = null;
422 if ( ciManagement != null )
424 ci = new org.apache.archiva.metadata.model.CiManagement();
425 ci.setSystem( ciManagement.getSystem() );
426 ci.setUrl( ciManagement.getUrl() );
431 public Collection<String> listRootNamespaces( String repoId, Filter<String> filter )
433 File dir = getRepositoryBasedir( repoId );
435 return getSortedFiles( dir, filter );
438 private static Collection<String> getSortedFiles( File dir, Filter<String> filter )
440 List<String> fileNames;
441 String[] files = dir.list( new DirectoryFilter( filter ) );
444 fileNames = new ArrayList<String>( Arrays.asList( files ) );
445 Collections.sort( fileNames );
449 fileNames = Collections.emptyList();
454 private File getRepositoryBasedir( String repoId )
456 ManagedRepositoryConfiguration repositoryConfiguration =
457 archivaConfiguration.getConfiguration().findManagedRepositoryById( repoId );
459 return new File( repositoryConfiguration.getLocation() );
462 public Collection<String> listNamespaces( String repoId, String namespace, Filter<String> filter )
464 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace );
466 // scan all the directories which are potential namespaces. Any directories known to be projects are excluded
467 List<String> namespaces = new ArrayList<String>();
468 File[] files = dir.listFiles( new DirectoryFilter( filter ) );
471 for ( File file : files )
473 if ( !isProject( file, filter ) )
475 namespaces.add( file.getName() );
479 Collections.sort( namespaces );
483 public Collection<String> listProjects( String repoId, String namespace, Filter<String> filter )
485 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace );
487 // scan all directories in the namespace, and only include those that are known to be projects
488 List<String> projects = new ArrayList<String>();
489 File[] files = dir.listFiles( new DirectoryFilter( filter ) );
492 for ( File file : files )
494 if ( isProject( file, filter ) )
496 projects.add( file.getName() );
500 Collections.sort( projects );
504 public Collection<String> listProjectVersions( String repoId, String namespace, String projectId,
505 Filter<String> filter )
507 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace, projectId );
509 // all directories in a project directory can be considered a version
510 return getSortedFiles( dir, filter );
513 public Collection<ArtifactMetadata> readArtifactsMetadata( String repoId, String namespace, String projectId,
514 String projectVersion, Filter<String> filter )
516 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace, projectId, projectVersion );
518 // all files that are not metadata and not a checksum / signature are considered artifacts
519 File[] files = dir.listFiles( new ArtifactDirectoryFilter( filter ) );
521 List<ArtifactMetadata> artifacts = new ArrayList<ArtifactMetadata>();
524 for ( File file : files )
526 ArtifactMetadata metadata = getArtifactFromFile( repoId, namespace, projectId, projectVersion, file );
527 artifacts.add( metadata );
533 public ArtifactMetadata readArtifactMetadataFromPath( String repoId, String path )
535 ArtifactMetadata metadata = pathTranslator.getArtifactForPath( repoId, path );
537 populateArtifactMetadataFromFile( metadata, new File( getRepositoryBasedir( repoId ), path ) );
542 private ArtifactMetadata getArtifactFromFile( String repoId, String namespace, String projectId,
543 String projectVersion, File file )
545 ArtifactMetadata metadata =
546 pathTranslator.getArtifactFromId( repoId, namespace, projectId, projectVersion, file.getName() );
548 populateArtifactMetadataFromFile( metadata, file );
553 private static void populateArtifactMetadataFromFile( ArtifactMetadata metadata, File file )
555 metadata.setWhenGathered( new Date() );
556 metadata.setFileLastModified( file.lastModified() );
557 ChecksummedFile checksummedFile = new ChecksummedFile( file );
560 metadata.setMd5( checksummedFile.calculateChecksum( ChecksumAlgorithm.MD5 ) );
562 catch ( IOException e )
564 log.error( "Unable to checksum file " + file + ": " + e.getMessage() );
568 metadata.setSha1( checksummedFile.calculateChecksum( ChecksumAlgorithm.SHA1 ) );
570 catch ( IOException e )
572 log.error( "Unable to checksum file " + file + ": " + e.getMessage() );
574 metadata.setSize( file.length() );
577 private boolean isProject( File dir, Filter<String> filter )
579 // scan directories for a valid project version subdirectory, meaning this must be a project directory
580 File[] files = dir.listFiles( new DirectoryFilter( filter ) );
583 for ( File file : files )
585 if ( isProjectVersion( file ) )
592 // if a metadata file is present, check if this is the "artifactId" directory, marking it as a project
593 MavenRepositoryMetadata metadata = readMetadata( dir );
594 if ( metadata != null && dir.getName().equals( metadata.getArtifactId() ) )
602 private boolean isProjectVersion( File dir )
604 final String artifactId = dir.getParentFile().getName();
605 final String projectVersion = dir.getName();
607 // check if there is a POM artifact file to ensure it is a version directory
609 if ( VersionUtil.isSnapshot( projectVersion ) )
611 files = dir.listFiles( new FilenameFilter()
613 public boolean accept( File dir, String name )
615 if ( name.startsWith( artifactId + "-" ) && name.endsWith( ".pom" ) )
617 String v = name.substring( artifactId.length() + 1, name.length() - 4 );
618 v = VersionUtil.getBaseVersion( v );
619 if ( v.equals( projectVersion ) )
630 final String pomFile = artifactId + "-" + projectVersion + ".pom";
631 files = dir.listFiles( new FilenameFilter()
633 public boolean accept( File dir, String name )
635 return pomFile.equals( name );
639 if ( files != null && files.length > 0 )
644 // if a metadata file is present, check if this is the "version" directory, marking it as a project version
645 MavenRepositoryMetadata metadata = readMetadata( dir );
646 if ( metadata != null && projectVersion.equals( metadata.getVersion() ) )
654 private MavenRepositoryMetadata readMetadata( File directory )
656 MavenRepositoryMetadata metadata = null;
657 File metadataFile = new File( directory, METADATA_FILENAME );
658 if ( metadataFile.exists() )
662 metadata = MavenRepositoryMetadataReader.read( metadataFile );
664 catch ( XMLException e )
666 // ignore missing or invalid metadata
672 private static class DirectoryFilter
673 implements FilenameFilter
675 private final Filter<String> filter;
677 public DirectoryFilter( Filter<String> filter )
679 this.filter = filter;
682 public boolean accept( File dir, String name )
684 if ( !filter.accept( name ) )
688 else if ( name.startsWith( "." ) )
692 else if ( !new File( dir, name ).isDirectory() )
700 private class ArtifactDirectoryFilter
701 implements FilenameFilter
703 private final Filter<String> filter;
705 public ArtifactDirectoryFilter( Filter<String> filter )
707 this.filter = filter;
710 public boolean accept( File dir, String name )
712 // TODO compare to logic in maven-repository-layer
713 if ( !filter.accept( name ) )
717 else if ( name.startsWith( "." ) )
721 else if ( name.endsWith( ".md5" ) || name.endsWith( ".sha1" ) || name.endsWith( ".asc" ) )
725 else if ( name.equals( METADATA_FILENAME ) )
729 else if ( new File( dir, name ).isDirectory() )