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.MetadataRepository;
28 import org.apache.archiva.metadata.repository.MetadataRepositoryException;
29 import org.apache.archiva.metadata.repository.MetadataResolutionException;
30 import org.apache.archiva.metadata.repository.filter.Filter;
31 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
32 import org.apache.archiva.metadata.repository.storage.RepositoryStorage;
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.xml.XMLException;
38 import org.apache.maven.model.CiManagement;
39 import org.apache.maven.model.Dependency;
40 import org.apache.maven.model.IssueManagement;
41 import org.apache.maven.model.License;
42 import org.apache.maven.model.MailingList;
43 import org.apache.maven.model.Model;
44 import org.apache.maven.model.Organization;
45 import org.apache.maven.model.Scm;
46 import org.apache.maven.model.building.DefaultModelBuildingRequest;
47 import org.apache.maven.model.building.ModelBuilder;
48 import org.apache.maven.model.building.ModelBuildingException;
49 import org.apache.maven.model.building.ModelBuildingRequest;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
54 import java.io.FilenameFilter;
55 import java.io.IOException;
56 import java.util.ArrayList;
57 import java.util.Arrays;
58 import java.util.Collection;
59 import java.util.Collections;
60 import java.util.Date;
61 import java.util.List;
64 * @plexus.component role="org.apache.archiva.metadata.repository.storage.RepositoryStorage" role-hint="maven2"
66 public class Maven2RepositoryStorage
67 implements RepositoryStorage
72 private ModelBuilder builder;
77 private ArchivaConfiguration archivaConfiguration;
80 * @plexus.requirement role-hint="maven2"
82 private RepositoryPathTranslator pathTranslator;
87 private MetadataRepository metadataRepository;
89 private final static Logger log = LoggerFactory.getLogger( Maven2RepositoryStorage.class );
91 private static final String METADATA_FILENAME = "maven-metadata.xml";
93 private static final String PROBLEM_MISSING_POM = "missing-pom";
95 private static final String PROBLEM_INVALID_POM = "invalid-pom";
97 private static final String PROBLEM_MISLOCATED_POM = "mislocated-pom";
99 private static final List<String> POTENTIAL_PROBLEMS = Arrays.asList( PROBLEM_INVALID_POM, PROBLEM_MISSING_POM,
100 PROBLEM_MISLOCATED_POM );
102 public ProjectMetadata readProjectMetadata( String repoId, String namespace, String projectId )
104 // TODO: could natively implement the "shared model" concept from the browse action to avoid needing it there?
108 public ProjectVersionMetadata readProjectVersionMetadata( String repoId, String namespace, String projectId,
109 String projectVersion )
110 throws MetadataResolutionException
112 // Remove problems associated with this version, since we'll be re-adding any that still exist
113 // TODO: an event mechanism would remove coupling to the problem reporting plugin
114 // TODO: this removes all problems - do we need something that just removes the problems created by this resolver?
115 String name = RepositoryProblemFacet.createName( namespace, projectId, projectVersion, null );
118 metadataRepository.removeMetadataFacet( repoId, RepositoryProblemFacet.FACET_ID, name );
120 catch ( MetadataRepositoryException e )
122 log.warn( "Unable to remove repository problem facets for the version being removed: " + e.getMessage(),
126 ManagedRepositoryConfiguration repositoryConfiguration =
127 archivaConfiguration.getConfiguration().findManagedRepositoryById( repoId );
129 String artifactVersion = projectVersion;
131 File basedir = new File( repositoryConfiguration.getLocation() );
132 if ( VersionUtil.isSnapshot( projectVersion ) )
134 File metadataFile = pathTranslator.toFile( basedir, namespace, projectId, projectVersion,
138 MavenRepositoryMetadata metadata = MavenRepositoryMetadataReader.read( metadataFile );
140 // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
141 MavenRepositoryMetadata.Snapshot snapshotVersion = metadata.getSnapshotVersion();
142 if ( snapshotVersion != null )
144 artifactVersion = artifactVersion.substring( 0, artifactVersion.length() -
145 8 ); // remove SNAPSHOT from end
147 artifactVersion + snapshotVersion.getTimestamp() + "-" + snapshotVersion.getBuildNumber();
150 catch ( XMLException e )
152 // unable to parse metadata - log it, and continue with the version as the original SNAPSHOT version
153 log.warn( "Invalid metadata: " + metadataFile + " - " + e.getMessage() );
157 // TODO: won't work well with some other layouts, might need to convert artifact parts to ID by path translator
158 String id = projectId + "-" + artifactVersion + ".pom";
159 File file = pathTranslator.toFile( basedir, namespace, projectId, projectVersion, id );
161 if ( !file.exists() )
163 // TODO: an event mechanism would remove coupling to the problem reporting plugin
164 addProblemReport( repoId, namespace, projectId, projectVersion, PROBLEM_MISSING_POM,
165 "The artifact's POM file '" + file + "' was missing" );
167 // metadata could not be resolved
171 ModelBuildingRequest req = new DefaultModelBuildingRequest();
172 req.setProcessPlugins( false );
173 req.setPomFile( file );
174 req.setModelResolver( new RepositoryModelResolver( basedir, pathTranslator ) );
175 req.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL );
180 model = builder.build( req ).getEffectiveModel();
182 catch ( ModelBuildingException e )
184 addProblemReport( repoId, namespace, projectId, projectVersion, PROBLEM_INVALID_POM,
185 "The artifact's POM file '" + file + "' was invalid: " + e.getMessage() );
187 throw new MetadataResolutionException( e.getMessage() );
190 // Check if the POM is in the correct location
191 boolean correctGroupId = namespace.equals( model.getGroupId() );
192 boolean correctArtifactId = projectId.equals( model.getArtifactId() );
193 boolean correctVersion = projectVersion.equals( model.getVersion() );
194 if ( !correctGroupId || !correctArtifactId || !correctVersion )
196 StringBuilder message = new StringBuilder( "Incorrect POM coordinates in '" + file + "':" );
197 if ( !correctGroupId )
199 message.append( "\nIncorrect group ID: " ).append( model.getGroupId() );
201 if ( !correctArtifactId )
203 message.append( "\nIncorrect artifact ID: " ).append( model.getArtifactId() );
205 if ( !correctVersion )
207 message.append( "\nIncorrect version: " ).append( model.getVersion() );
210 String msg = message.toString();
211 addProblemReport( repoId, namespace, projectId, projectVersion, PROBLEM_MISLOCATED_POM, msg );
213 throw new MetadataResolutionException( msg );
216 ProjectVersionMetadata metadata = new ProjectVersionMetadata();
217 metadata.setCiManagement( convertCiManagement( model.getCiManagement() ) );
218 metadata.setDescription( model.getDescription() );
219 metadata.setId( projectVersion );
220 metadata.setIssueManagement( convertIssueManagement( model.getIssueManagement() ) );
221 metadata.setLicenses( convertLicenses( model.getLicenses() ) );
222 metadata.setMailingLists( convertMailingLists( model.getMailingLists() ) );
223 metadata.setDependencies( convertDependencies( model.getDependencies() ) );
224 metadata.setName( model.getName() );
225 metadata.setOrganization( convertOrganization( model.getOrganization() ) );
226 metadata.setScm( convertScm( model.getScm() ) );
227 metadata.setUrl( model.getUrl() );
229 MavenProjectFacet facet = new MavenProjectFacet();
230 facet.setGroupId( model.getGroupId() != null ? model.getGroupId() : model.getParent().getGroupId() );
231 facet.setArtifactId( model.getArtifactId() );
232 facet.setPackaging( model.getPackaging() );
233 if ( model.getParent() != null )
235 MavenProjectParent parent = new MavenProjectParent();
236 parent.setGroupId( model.getParent().getGroupId() );
237 parent.setArtifactId( model.getParent().getArtifactId() );
238 parent.setVersion( model.getParent().getVersion() );
239 facet.setParent( parent );
241 metadata.addFacet( facet );
246 private void addProblemReport( String repoId, String namespace, String projectId, String projectVersion,
247 String problemId, String message )
249 // TODO: an event mechanism would remove coupling to the problem reporting plugin and allow other plugins to
250 // generate metadata on the fly if appropriately checked for missing facets in the resolver
251 RepositoryProblemFacet problem = new RepositoryProblemFacet();
252 problem.setProblem( problemId );
253 problem.setMessage( message );
254 problem.setProject( projectId );
255 problem.setNamespace( namespace );
256 problem.setRepositoryId( repoId );
257 problem.setVersion( projectVersion );
261 metadataRepository.addMetadataFacet( repoId, problem );
263 catch ( MetadataRepositoryException e )
265 log.warn( "Unable to add repository problem facets for the version being removed: " + e.getMessage(), e );
269 private List<org.apache.archiva.metadata.model.Dependency> convertDependencies( List<Dependency> dependencies )
271 List<org.apache.archiva.metadata.model.Dependency> l =
272 new ArrayList<org.apache.archiva.metadata.model.Dependency>();
273 for ( Dependency dependency : dependencies )
275 org.apache.archiva.metadata.model.Dependency newDependency =
276 new org.apache.archiva.metadata.model.Dependency();
277 newDependency.setArtifactId( dependency.getArtifactId() );
278 newDependency.setClassifier( dependency.getClassifier() );
279 newDependency.setGroupId( dependency.getGroupId() );
280 newDependency.setOptional( dependency.isOptional() );
281 newDependency.setScope( dependency.getScope() );
282 newDependency.setSystemPath( dependency.getSystemPath() );
283 newDependency.setType( dependency.getType() );
284 newDependency.setVersion( dependency.getVersion() );
285 l.add( newDependency );
290 private org.apache.archiva.metadata.model.Scm convertScm( Scm scm )
292 org.apache.archiva.metadata.model.Scm newScm = null;
295 newScm = new org.apache.archiva.metadata.model.Scm();
296 newScm.setConnection( scm.getConnection() );
297 newScm.setDeveloperConnection( scm.getDeveloperConnection() );
298 newScm.setUrl( scm.getUrl() );
303 private org.apache.archiva.metadata.model.Organization convertOrganization( Organization organization )
305 org.apache.archiva.metadata.model.Organization org = null;
306 if ( organization != null )
308 org = new org.apache.archiva.metadata.model.Organization();
309 org.setName( organization.getName() );
310 org.setUrl( organization.getUrl() );
315 private List<org.apache.archiva.metadata.model.License> convertLicenses( List<License> licenses )
317 List<org.apache.archiva.metadata.model.License> l = new ArrayList<org.apache.archiva.metadata.model.License>();
318 for ( License license : licenses )
320 org.apache.archiva.metadata.model.License newLicense = new org.apache.archiva.metadata.model.License();
321 newLicense.setName( license.getName() );
322 newLicense.setUrl( license.getUrl() );
328 private List<org.apache.archiva.metadata.model.MailingList> convertMailingLists( List<MailingList> mailingLists )
330 List<org.apache.archiva.metadata.model.MailingList> l =
331 new ArrayList<org.apache.archiva.metadata.model.MailingList>();
332 for ( MailingList mailingList : mailingLists )
334 org.apache.archiva.metadata.model.MailingList newMailingList =
335 new org.apache.archiva.metadata.model.MailingList();
336 newMailingList.setName( mailingList.getName() );
337 newMailingList.setMainArchiveUrl( mailingList.getArchive() );
338 newMailingList.setPostAddress( mailingList.getPost() );
339 newMailingList.setSubscribeAddress( mailingList.getSubscribe() );
340 newMailingList.setUnsubscribeAddress( mailingList.getUnsubscribe() );
341 newMailingList.setOtherArchives( mailingList.getOtherArchives() );
342 l.add( newMailingList );
347 private org.apache.archiva.metadata.model.IssueManagement convertIssueManagement( IssueManagement issueManagement )
349 org.apache.archiva.metadata.model.IssueManagement im = null;
350 if ( issueManagement != null )
352 im = new org.apache.archiva.metadata.model.IssueManagement();
353 im.setSystem( issueManagement.getSystem() );
354 im.setUrl( issueManagement.getUrl() );
359 private org.apache.archiva.metadata.model.CiManagement convertCiManagement( CiManagement ciManagement )
361 org.apache.archiva.metadata.model.CiManagement ci = null;
362 if ( ciManagement != null )
364 ci = new org.apache.archiva.metadata.model.CiManagement();
365 ci.setSystem( ciManagement.getSystem() );
366 ci.setUrl( ciManagement.getUrl() );
371 public Collection<String> listRootNamespaces( String repoId, Filter<String> filter )
373 File dir = getRepositoryBasedir( repoId );
375 return getSortedFiles( dir, filter );
378 private static Collection<String> getSortedFiles( File dir, Filter<String> filter )
380 List<String> fileNames;
381 String[] files = dir.list( new DirectoryFilter( filter ) );
384 fileNames = new ArrayList<String>( Arrays.asList( files ) );
385 Collections.sort( fileNames );
389 fileNames = Collections.emptyList();
394 private File getRepositoryBasedir( String repoId )
396 ManagedRepositoryConfiguration repositoryConfiguration =
397 archivaConfiguration.getConfiguration().findManagedRepositoryById( repoId );
399 return new File( repositoryConfiguration.getLocation() );
402 public Collection<String> listNamespaces( String repoId, String namespace, Filter<String> filter )
404 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace );
406 // scan all the directories which are potential namespaces. Any directories known to be projects are excluded
407 List<String> namespaces = new ArrayList<String>();
408 File[] files = dir.listFiles( new DirectoryFilter( filter ) );
411 for ( File file : files )
413 if ( !isProject( file, filter ) )
415 namespaces.add( file.getName() );
419 Collections.sort( namespaces );
423 public Collection<String> listProjects( String repoId, String namespace, Filter<String> filter )
425 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace );
427 // scan all directories in the namespace, and only include those that are known to be projects
428 List<String> projects = new ArrayList<String>();
429 File[] files = dir.listFiles( new DirectoryFilter( filter ) );
432 for ( File file : files )
434 if ( isProject( file, filter ) )
436 projects.add( file.getName() );
440 Collections.sort( projects );
444 public Collection<String> listProjectVersions( String repoId, String namespace, String projectId,
445 Filter<String> filter )
447 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace, projectId );
449 // all directories in a project directory can be considered a version
450 return getSortedFiles( dir, filter );
453 public Collection<ArtifactMetadata> readArtifactsMetadata( String repoId, String namespace, String projectId,
454 String projectVersion, Filter<String> filter )
456 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace, projectId, projectVersion );
458 // all files that are not metadata and not a checksum / signature are considered artifacts
459 File[] files = dir.listFiles( new ArtifactDirectoryFilter( filter ) );
461 List<ArtifactMetadata> artifacts = new ArrayList<ArtifactMetadata>();
464 for ( File file : files )
466 ArtifactMetadata metadata = getArtifactFromFile( repoId, namespace, projectId, projectVersion, file );
467 artifacts.add( metadata );
473 public ArtifactMetadata readArtifactMetadataFromPath( String repoId, String path )
475 ArtifactMetadata metadata = pathTranslator.getArtifactForPath( repoId, path );
477 populateArtifactMetadataFromFile( metadata, new File( getRepositoryBasedir( repoId ), path ) );
482 private ArtifactMetadata getArtifactFromFile( String repoId, String namespace, String projectId,
483 String projectVersion, File file )
485 ArtifactMetadata metadata = pathTranslator.getArtifactFromId( repoId, namespace, projectId, projectVersion,
488 populateArtifactMetadataFromFile( metadata, file );
493 private static void populateArtifactMetadataFromFile( ArtifactMetadata metadata, File file )
495 metadata.setWhenGathered( new Date() );
496 metadata.setFileLastModified( file.lastModified() );
497 ChecksummedFile checksummedFile = new ChecksummedFile( file );
500 metadata.setMd5( checksummedFile.calculateChecksum( ChecksumAlgorithm.MD5 ) );
502 catch ( IOException e )
504 log.error( "Unable to checksum file " + file + ": " + e.getMessage() );
508 metadata.setSha1( checksummedFile.calculateChecksum( ChecksumAlgorithm.SHA1 ) );
510 catch ( IOException e )
512 log.error( "Unable to checksum file " + file + ": " + e.getMessage() );
514 metadata.setSize( file.length() );
517 private boolean isProject( File dir, Filter<String> filter )
519 // scan directories for a valid project version subdirectory, meaning this must be a project directory
520 File[] files = dir.listFiles( new DirectoryFilter( filter ) );
523 for ( File file : files )
525 if ( isProjectVersion( file ) )
532 // if a metadata file is present, check if this is the "artifactId" directory, marking it as a project
533 MavenRepositoryMetadata metadata = readMetadata( dir );
534 if ( metadata != null && dir.getName().equals( metadata.getArtifactId() ) )
542 private boolean isProjectVersion( File dir )
544 final String artifactId = dir.getParentFile().getName();
545 final String projectVersion = dir.getName();
547 // check if there is a POM artifact file to ensure it is a version directory
549 if ( VersionUtil.isSnapshot( projectVersion ) )
551 files = dir.listFiles( new FilenameFilter()
553 public boolean accept( File dir, String name )
555 if ( name.startsWith( artifactId + "-" ) && name.endsWith( ".pom" ) )
557 String v = name.substring( artifactId.length() + 1, name.length() - 4 );
558 v = VersionUtil.getBaseVersion( v );
559 if ( v.equals( projectVersion ) )
570 final String pomFile = artifactId + "-" + projectVersion + ".pom";
571 files = dir.listFiles( new FilenameFilter()
573 public boolean accept( File dir, String name )
575 return pomFile.equals( name );
579 if ( files != null && files.length > 0 )
584 // if a metadata file is present, check if this is the "version" directory, marking it as a project version
585 MavenRepositoryMetadata metadata = readMetadata( dir );
586 if ( metadata != null && projectVersion.equals( metadata.getVersion() ) )
594 private MavenRepositoryMetadata readMetadata( File directory )
596 MavenRepositoryMetadata metadata = null;
597 File metadataFile = new File( directory, METADATA_FILENAME );
598 if ( metadataFile.exists() )
602 metadata = MavenRepositoryMetadataReader.read( metadataFile );
604 catch ( XMLException e )
606 // ignore missing or invalid metadata
612 private static class DirectoryFilter
613 implements FilenameFilter
615 private final Filter<String> filter;
617 public DirectoryFilter( Filter<String> filter )
619 this.filter = filter;
622 public boolean accept( File dir, String name )
624 if ( !filter.accept( name ) )
628 else if ( name.startsWith( "." ) )
632 else if ( !new File( dir, name ).isDirectory() )
640 private class ArtifactDirectoryFilter
641 implements FilenameFilter
643 private final Filter<String> filter;
645 public ArtifactDirectoryFilter( Filter<String> filter )
647 this.filter = filter;
650 public boolean accept( File dir, String name )
652 // TODO compare to logic in maven-repository-layer
653 if ( !filter.accept( name ) )
657 else if ( name.startsWith( "." ) )
661 else if ( name.endsWith( ".md5" ) || name.endsWith( ".sha1" ) || name.endsWith( ".asc" ) )
665 else if ( name.equals( METADATA_FILENAME ) )
669 else if ( new File( dir, name ).isDirectory() )