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.model.ProjectVersionReference;
28 import org.apache.archiva.metadata.repository.MetadataRepository;
29 import org.apache.archiva.metadata.repository.MetadataResolutionException;
30 import org.apache.archiva.metadata.repository.filter.AllFilter;
31 import org.apache.archiva.metadata.repository.filter.Filter;
32 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
33 import org.apache.archiva.metadata.repository.storage.StorageMetadataResolver;
34 import org.apache.archiva.reports.RepositoryProblemFacet;
35 import org.apache.maven.archiva.common.utils.VersionUtil;
36 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
37 import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
38 import org.apache.maven.archiva.xml.XMLException;
39 import org.apache.maven.model.CiManagement;
40 import org.apache.maven.model.Dependency;
41 import org.apache.maven.model.IssueManagement;
42 import org.apache.maven.model.License;
43 import org.apache.maven.model.MailingList;
44 import org.apache.maven.model.Model;
45 import org.apache.maven.model.Organization;
46 import org.apache.maven.model.Scm;
47 import org.apache.maven.model.building.DefaultModelBuildingRequest;
48 import org.apache.maven.model.building.ModelBuilder;
49 import org.apache.maven.model.building.ModelBuildingException;
50 import org.apache.maven.model.building.ModelBuildingRequest;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
55 import java.io.FilenameFilter;
56 import java.io.IOException;
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.Collection;
60 import java.util.Collections;
61 import java.util.Date;
62 import java.util.List;
63 import java.util.regex.Matcher;
64 import java.util.regex.Pattern;
67 * @plexus.component role="org.apache.archiva.metadata.repository.storage.StorageMetadataResolver" role-hint="maven2"
69 public class Maven2RepositoryMetadataResolver
70 implements StorageMetadataResolver
75 private ModelBuilder builder;
80 private ArchivaConfiguration archivaConfiguration;
83 * @plexus.requirement role-hint="maven2"
85 private RepositoryPathTranslator pathTranslator;
90 private MetadataRepository metadataRepository;
92 private final static Logger log = LoggerFactory.getLogger( Maven2RepositoryMetadataResolver.class );
94 private static final String METADATA_FILENAME = "maven-metadata.xml";
96 private static final Filter<String> ALL = new AllFilter<String>();
98 private static final String PROBLEM_MISSING_POM = "missing-pom";
100 private static final String PROBLEM_INVALID_POM = "invalid-pom";
102 private static final String PROBLEM_MISLOCATED_POM = "mislocated-pom";
104 private static final List<String> POTENTIAL_PROBLEMS = Arrays.asList( PROBLEM_INVALID_POM, PROBLEM_MISSING_POM,
105 PROBLEM_MISLOCATED_POM );
107 public ProjectMetadata getProject( String repoId, String namespace, String projectId )
109 // TODO: could natively implement the "shared model" concept from the browse action to avoid needing it there?
113 public ProjectVersionMetadata getProjectVersion( String repoId, String namespace, String projectId,
114 String projectVersion )
115 throws MetadataResolutionException
117 // TODO: an event mechanism would remove coupling to the problem reporting plugin
118 // TODO: this removes all problems - do we need something that just removes the problems created by this resolver?
119 String name = RepositoryProblemFacet.createName( namespace, projectId, projectVersion, null );
120 metadataRepository.removeMetadataFacet( repoId, RepositoryProblemFacet.FACET_ID, name );
122 ManagedRepositoryConfiguration repositoryConfiguration =
123 archivaConfiguration.getConfiguration().findManagedRepositoryById( repoId );
125 String artifactVersion = projectVersion;
127 File basedir = new File( repositoryConfiguration.getLocation() );
128 if ( VersionUtil.isSnapshot( projectVersion ) )
130 File metadataFile = pathTranslator.toFile( basedir, namespace, projectId, projectVersion,
134 MavenRepositoryMetadata metadata = MavenRepositoryMetadataReader.read( metadataFile );
136 // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
137 MavenRepositoryMetadata.Snapshot snapshotVersion = metadata.getSnapshotVersion();
138 if ( snapshotVersion != null )
140 artifactVersion = artifactVersion.substring( 0, artifactVersion.length() -
141 8 ); // remove SNAPSHOT from end
143 artifactVersion + snapshotVersion.getTimestamp() + "-" + snapshotVersion.getBuildNumber();
146 catch ( XMLException e )
148 // unable to parse metadata - log it, and continue with the version as the original SNAPSHOT version
149 log.warn( "Invalid metadata: " + metadataFile + " - " + e.getMessage() );
153 String id = projectId + "-" + artifactVersion + ".pom";
154 File file = pathTranslator.toFile( basedir, namespace, projectId, projectVersion, id );
156 if ( !file.exists() )
158 // TODO: an event mechanism would remove coupling to the problem reporting plugin
159 addProblemReport( repoId, namespace, projectId, projectVersion, PROBLEM_MISSING_POM,
160 "The artifact's POM file '" + file + "' was missing" );
162 // metadata could not be resolved
166 ModelBuildingRequest req = new DefaultModelBuildingRequest();
167 req.setProcessPlugins( false );
168 req.setPomFile( file );
169 req.setModelResolver( new RepositoryModelResolver( basedir, pathTranslator ) );
170 req.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL );
175 model = builder.build( req ).getEffectiveModel();
177 catch ( ModelBuildingException e )
179 addProblemReport( repoId, namespace, projectId, projectVersion, PROBLEM_INVALID_POM,
180 "The artifact's POM file '" + file + "' was invalid: " + e.getMessage() );
182 throw new MetadataResolutionException( e.getMessage() );
185 // Check if the POM is in the correct location
186 boolean correctGroupId = namespace.equals( model.getGroupId() );
187 boolean correctArtifactId = projectId.equals( model.getArtifactId() );
188 boolean correctVersion = projectVersion.equals( model.getVersion() );
189 if ( !correctGroupId || !correctArtifactId || !correctVersion )
191 StringBuilder message = new StringBuilder( "Incorrect POM coordinates in '" + file + "':" );
192 if ( !correctGroupId )
194 message.append( "\nIncorrect group ID: " ).append( model.getGroupId() );
196 if ( !correctArtifactId )
198 message.append( "\nIncorrect artifact ID: " ).append( model.getArtifactId() );
200 if ( !correctVersion )
202 message.append( "\nIncorrect version: " ).append( model.getVersion() );
205 String msg = message.toString();
206 addProblemReport( repoId, namespace, projectId, projectVersion, PROBLEM_MISLOCATED_POM, msg );
208 throw new MetadataResolutionException( msg );
211 ProjectVersionMetadata metadata = new ProjectVersionMetadata();
212 metadata.setCiManagement( convertCiManagement( model.getCiManagement() ) );
213 metadata.setDescription( model.getDescription() );
214 metadata.setId( projectVersion );
215 metadata.setIssueManagement( convertIssueManagement( model.getIssueManagement() ) );
216 metadata.setLicenses( convertLicenses( model.getLicenses() ) );
217 metadata.setMailingLists( convertMailingLists( model.getMailingLists() ) );
218 metadata.setDependencies( convertDependencies( model.getDependencies() ) );
219 metadata.setName( model.getName() );
220 metadata.setOrganization( convertOrganization( model.getOrganization() ) );
221 metadata.setScm( convertScm( model.getScm() ) );
222 metadata.setUrl( model.getUrl() );
224 MavenProjectFacet facet = new MavenProjectFacet();
225 facet.setGroupId( model.getGroupId() != null ? model.getGroupId() : model.getParent().getGroupId() );
226 facet.setArtifactId( model.getArtifactId() );
227 facet.setPackaging( model.getPackaging() );
228 if ( model.getParent() != null )
230 MavenProjectParent parent = new MavenProjectParent();
231 parent.setGroupId( model.getParent().getGroupId() );
232 parent.setArtifactId( model.getParent().getArtifactId() );
233 parent.setVersion( model.getParent().getVersion() );
234 facet.setParent( parent );
236 metadata.addFacet( facet );
241 private void addProblemReport( String repoId, String namespace, String projectId, String projectVersion,
242 String problemId, String message )
244 // TODO: an event mechanism would remove coupling to the problem reporting plugin
245 RepositoryProblemFacet problem = new RepositoryProblemFacet();
246 problem.setProblem( problemId );
247 problem.setMessage( message );
248 problem.setProject( projectId );
249 problem.setNamespace( namespace );
250 problem.setRepositoryId( repoId );
251 problem.setVersion( projectVersion );
253 metadataRepository.addMetadataFacet( repoId, problem );
256 private List<org.apache.archiva.metadata.model.Dependency> convertDependencies( List<Dependency> dependencies )
258 List<org.apache.archiva.metadata.model.Dependency> l =
259 new ArrayList<org.apache.archiva.metadata.model.Dependency>();
260 for ( Dependency dependency : dependencies )
262 org.apache.archiva.metadata.model.Dependency newDependency =
263 new org.apache.archiva.metadata.model.Dependency();
264 newDependency.setArtifactId( dependency.getArtifactId() );
265 newDependency.setClassifier( dependency.getClassifier() );
266 newDependency.setGroupId( dependency.getGroupId() );
267 newDependency.setOptional( dependency.isOptional() );
268 newDependency.setScope( dependency.getScope() );
269 newDependency.setSystemPath( dependency.getSystemPath() );
270 newDependency.setType( dependency.getType() );
271 newDependency.setVersion( dependency.getVersion() );
272 l.add( newDependency );
277 private org.apache.archiva.metadata.model.Scm convertScm( Scm scm )
279 org.apache.archiva.metadata.model.Scm newScm = null;
282 newScm = new org.apache.archiva.metadata.model.Scm();
283 newScm.setConnection( scm.getConnection() );
284 newScm.setDeveloperConnection( scm.getDeveloperConnection() );
285 newScm.setUrl( scm.getUrl() );
290 private org.apache.archiva.metadata.model.Organization convertOrganization( Organization organization )
292 org.apache.archiva.metadata.model.Organization org = null;
293 if ( organization != null )
295 org = new org.apache.archiva.metadata.model.Organization();
296 org.setName( organization.getName() );
297 org.setUrl( organization.getUrl() );
302 private List<org.apache.archiva.metadata.model.License> convertLicenses( List<License> licenses )
304 List<org.apache.archiva.metadata.model.License> l = new ArrayList<org.apache.archiva.metadata.model.License>();
305 for ( License license : licenses )
307 org.apache.archiva.metadata.model.License newLicense = new org.apache.archiva.metadata.model.License();
308 newLicense.setName( license.getName() );
309 newLicense.setUrl( license.getUrl() );
315 private List<org.apache.archiva.metadata.model.MailingList> convertMailingLists( List<MailingList> mailingLists )
317 List<org.apache.archiva.metadata.model.MailingList> l =
318 new ArrayList<org.apache.archiva.metadata.model.MailingList>();
319 for ( MailingList mailingList : mailingLists )
321 org.apache.archiva.metadata.model.MailingList newMailingList =
322 new org.apache.archiva.metadata.model.MailingList();
323 newMailingList.setName( mailingList.getName() );
324 newMailingList.setMainArchiveUrl( mailingList.getArchive() );
325 newMailingList.setPostAddress( mailingList.getPost() );
326 newMailingList.setSubscribeAddress( mailingList.getSubscribe() );
327 newMailingList.setUnsubscribeAddress( mailingList.getUnsubscribe() );
328 newMailingList.setOtherArchives( mailingList.getOtherArchives() );
329 l.add( newMailingList );
334 private org.apache.archiva.metadata.model.IssueManagement convertIssueManagement( IssueManagement issueManagement )
336 org.apache.archiva.metadata.model.IssueManagement im = null;
337 if ( issueManagement != null )
339 im = new org.apache.archiva.metadata.model.IssueManagement();
340 im.setSystem( issueManagement.getSystem() );
341 im.setUrl( issueManagement.getUrl() );
346 private org.apache.archiva.metadata.model.CiManagement convertCiManagement( CiManagement ciManagement )
348 org.apache.archiva.metadata.model.CiManagement ci = null;
349 if ( ciManagement != null )
351 ci = new org.apache.archiva.metadata.model.CiManagement();
352 ci.setSystem( ciManagement.getSystem() );
353 ci.setUrl( ciManagement.getUrl() );
358 public Collection<String> getArtifactVersions( String repoId, String namespace, String projectId,
359 String projectVersion )
361 // TODO: useful, but not implemented yet, not called from DefaultMetadataResolver
362 throw new UnsupportedOperationException();
365 public Collection<ProjectVersionReference> getProjectReferences( String repoId, String namespace, String projectId,
366 String projectVersion )
368 // Can't be determined on a Maven 2 repository
369 throw new UnsupportedOperationException();
372 public Collection<String> getRootNamespaces( String repoId )
374 return getRootNamespaces( repoId, ALL );
377 public Collection<String> getRootNamespaces( String repoId, Filter<String> filter )
379 File dir = getRepositoryBasedir( repoId );
381 List<String> rootNamespaces;
382 String[] files = dir.list( new DirectoryFilter( filter ) );
385 rootNamespaces = new ArrayList<String>( Arrays.asList( files ) );
386 Collections.sort( rootNamespaces );
390 rootNamespaces = Collections.emptyList();
392 return rootNamespaces;
395 private File getRepositoryBasedir( String repoId )
397 ManagedRepositoryConfiguration repositoryConfiguration =
398 archivaConfiguration.getConfiguration().findManagedRepositoryById( repoId );
400 return new File( repositoryConfiguration.getLocation() );
403 public Collection<String> getNamespaces( String repoId, String namespace )
405 return getNamespaces( repoId, namespace, ALL );
408 public Collection<String> getNamespaces( String repoId, String namespace, Filter<String> filter )
410 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace );
412 // scan all the directories which are potential namespaces. Any directories known to be projects are excluded
413 List<String> namespaces = new ArrayList<String>();
414 File[] files = dir.listFiles( new DirectoryFilter( filter ) );
417 for ( File file : files )
419 if ( !isProject( file, filter ) )
421 namespaces.add( file.getName() );
425 Collections.sort( namespaces );
429 public Collection<String> getProjects( String repoId, String namespace )
431 return getProjects( repoId, namespace, ALL );
434 public Collection<String> getProjects( String repoId, String namespace, Filter<String> filter )
436 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace );
438 // scan all directories in the namespace, and only include those that are known to be projects
439 List<String> projects = new ArrayList<String>();
440 File[] files = dir.listFiles( new DirectoryFilter( filter ) );
443 for ( File file : files )
445 if ( isProject( file, filter ) )
447 projects.add( file.getName() );
451 Collections.sort( projects );
455 public Collection<String> getProjectVersions( String repoId, String namespace, String projectId )
457 return getProjectVersions( repoId, namespace, projectId, ALL );
460 public Collection<ArtifactMetadata> getArtifacts( String repoId, String namespace, String projectId,
461 String projectVersion )
463 return getArtifacts( repoId, namespace, projectId, projectVersion, ALL );
466 public Collection<String> getProjectVersions( String repoId, String namespace, String projectId,
467 Filter<String> filter )
469 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace, projectId );
471 // all directories in a project directory can be considered a version
472 String[] files = dir.list( new DirectoryFilter( filter ) );
473 return files != null ? Arrays.asList( files ) : Collections.<String>emptyList();
476 public Collection<ArtifactMetadata> getArtifacts( String repoId, String namespace, String projectId,
477 String projectVersion, Filter<String> filter )
479 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace, projectId, projectVersion );
481 // all files that are not metadata and not a checksum / signature are considered artifacts
482 File[] files = dir.listFiles( new ArtifactDirectoryFilter( filter ) );
484 List<ArtifactMetadata> artifacts = new ArrayList<ArtifactMetadata>();
487 for ( File file : files )
489 ArtifactMetadata metadata = new ArtifactMetadata();
490 metadata.setId( file.getName() );
491 metadata.setProject( projectId );
492 metadata.setNamespace( namespace );
493 metadata.setRepositoryId( repoId );
494 metadata.setWhenGathered( new Date() );
495 metadata.setFileLastModified( file.lastModified() );
496 ChecksummedFile checksummedFile = new ChecksummedFile( file );
499 metadata.setMd5( checksummedFile.calculateChecksum( ChecksumAlgorithm.MD5 ) );
501 catch ( IOException e )
503 log.error( "Unable to checksum file " + file + ": " + e.getMessage() );
507 metadata.setSha1( checksummedFile.calculateChecksum( ChecksumAlgorithm.SHA1 ) );
509 catch ( IOException e )
511 log.error( "Unable to checksum file " + file + ": " + e.getMessage() );
513 metadata.setSize( file.length() );
515 // TODO: very crude, migrate the functionality from the repository-layer here
516 if ( VersionUtil.isGenericSnapshot( projectVersion ) )
518 String mainVersion = projectVersion.substring( 0, projectVersion.length() -
519 8 ); // 8 is length of "SNAPSHOT"
520 Matcher m = Pattern.compile(
521 projectId + "-" + mainVersion + "([0-9]{8}.[0-9]{6}-[0-9]+).*" ).matcher( file.getName() );
523 String version = mainVersion + m.group( 1 );
525 metadata.setVersion( version );
529 metadata.setVersion( projectVersion );
531 artifacts.add( metadata );
537 private boolean isProject( File dir, Filter<String> filter )
539 // scan directories for a valid project version subdirectory, meaning this must be a project directory
540 File[] files = dir.listFiles( new DirectoryFilter( filter ) );
543 for ( File file : files )
545 if ( isProjectVersion( file ) )
552 // if a metadata file is present, check if this is the "artifactId" directory, marking it as a project
553 MavenRepositoryMetadata metadata = readMetadata( dir );
554 if ( metadata != null && dir.getName().equals( metadata.getArtifactId() ) )
562 private boolean isProjectVersion( File dir )
564 final String artifactId = dir.getParentFile().getName();
565 final String projectVersion = dir.getName();
567 // check if there is a POM artifact file to ensure it is a version directory
569 if ( VersionUtil.isSnapshot( projectVersion ) )
571 files = dir.listFiles( new FilenameFilter()
573 public boolean accept( File dir, String name )
575 if ( name.startsWith( artifactId + "-" ) && name.endsWith( ".pom" ) )
577 String v = name.substring( artifactId.length() + 1, name.length() - 4 );
578 v = VersionUtil.getBaseVersion( v );
579 if ( v.equals( projectVersion ) )
590 final String pomFile = artifactId + "-" + projectVersion + ".pom";
591 files = dir.listFiles( new FilenameFilter()
593 public boolean accept( File dir, String name )
595 return pomFile.equals( name );
599 if ( files != null && files.length > 0 )
604 // if a metadata file is present, check if this is the "version" directory, marking it as a project version
605 MavenRepositoryMetadata metadata = readMetadata( dir );
606 if ( metadata != null && projectVersion.equals( metadata.getVersion() ) )
614 private MavenRepositoryMetadata readMetadata( File directory )
616 MavenRepositoryMetadata metadata = null;
617 File metadataFile = new File( directory, METADATA_FILENAME );
618 if ( metadataFile.exists() )
622 metadata = MavenRepositoryMetadataReader.read( metadataFile );
624 catch ( XMLException e )
626 // ignore missing or invalid metadata
632 public void setConfiguration( ArchivaConfiguration configuration )
634 this.archivaConfiguration = configuration;
637 private static class DirectoryFilter
638 implements FilenameFilter
640 private final Filter<String> filter;
642 public DirectoryFilter( Filter<String> filter )
644 this.filter = filter;
647 public boolean accept( File dir, String name )
649 if ( !filter.accept( name ) )
653 else if ( name.startsWith( "." ) )
657 else if ( !new File( dir, name ).isDirectory() )
665 private class ArtifactDirectoryFilter
666 implements FilenameFilter
668 private final Filter<String> filter;
670 public ArtifactDirectoryFilter( Filter<String> filter )
672 this.filter = filter;
675 public boolean accept( File dir, String name )
677 // TODO compare to logic in maven-repository-layer
678 if ( !filter.accept( name ) )
682 else if ( name.startsWith( "." ) )
686 else if ( name.endsWith( ".md5" ) || name.endsWith( ".sha1" ) || name.endsWith( ".asc" ) )
690 else if ( name.equals( METADATA_FILENAME ) )
694 else if ( new File( dir, name ).isDirectory() )