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.maven.archiva.common.utils.VersionUtil;
33 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
34 import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
35 import org.apache.maven.archiva.xml.XMLException;
36 import org.apache.maven.model.CiManagement;
37 import org.apache.maven.model.Dependency;
38 import org.apache.maven.model.IssueManagement;
39 import org.apache.maven.model.License;
40 import org.apache.maven.model.MailingList;
41 import org.apache.maven.model.Model;
42 import org.apache.maven.model.Organization;
43 import org.apache.maven.model.Scm;
44 import org.apache.maven.model.building.DefaultModelBuilderFactory;
45 import org.apache.maven.model.building.DefaultModelBuildingRequest;
46 import org.apache.maven.model.building.ModelBuilder;
47 import org.apache.maven.model.building.ModelBuildingException;
48 import org.apache.maven.model.building.ModelBuildingRequest;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51 import org.springframework.stereotype.Service;
53 import javax.annotation.PostConstruct;
54 import javax.inject.Inject;
55 import javax.inject.Named;
57 import java.io.FilenameFilter;
58 import java.io.IOException;
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.Collection;
62 import java.util.Collections;
63 import java.util.Date;
64 import java.util.List;
67 * Maven 2 repository format storage implementation. This class currently takes parameters to indicate the repository to
68 * deal with rather than being instantiated per-repository.
69 * FIXME: instantiate one per repository and allocate permanently from a factory (which can be obtained within the session).
70 * TODO: finish Maven 1 implementation to prove this API
72 * The session is passed in as an argument to obtain any necessary resources, rather than the class being instantiated
73 * within the session in the context of a single managed repository's resolution needs.
75 * plexus.component role="org.apache.archiva.metadata.repository.storage.RepositoryStorage" role-hint="maven2"
77 @Service( "repositoryStorage#maven2" )
78 public class Maven2RepositoryStorage
79 implements RepositoryStorage
84 private ModelBuilder builder;
90 @Named( value = "archivaConfiguration#default" )
91 private ArchivaConfiguration archivaConfiguration;
94 * plexus.requirement role-hint="maven2"
97 @Named( value = "repositoryPathTranslator#maven2" )
98 private RepositoryPathTranslator pathTranslator;
100 private final static Logger log = LoggerFactory.getLogger( Maven2RepositoryStorage.class );
102 private static final String METADATA_FILENAME = "maven-metadata.xml";
106 public void initialize()
108 DefaultModelBuilderFactory defaultModelBuilderFactory = new DefaultModelBuilderFactory();
109 builder = defaultModelBuilderFactory.newInstance();
112 public ProjectMetadata readProjectMetadata( String repoId, String namespace, String projectId )
114 // TODO: could natively implement the "shared model" concept from the browse action to avoid needing it there?
118 public ProjectVersionMetadata readProjectVersionMetadata( String repoId, String namespace, String projectId,
119 String projectVersion )
120 throws RepositoryStorageMetadataNotFoundException, RepositoryStorageMetadataInvalidException
122 ManagedRepositoryConfiguration repositoryConfiguration =
123 archivaConfiguration.getConfiguration().findManagedRepositoryById( repoId );
125 String artifactVersion = projectVersion;
127 File basedir = new File( repositoryConfiguration.getLocation() );
128 if ( VersionUtil.isSnapshot( projectVersion ) )
131 pathTranslator.toFile( basedir, namespace, projectId, projectVersion, METADATA_FILENAME );
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 )
141 artifactVersion.substring( 0, artifactVersion.length() - 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 // TODO: won't work well with some other layouts, might need to convert artifact parts to ID by path translator
154 String id = projectId + "-" + artifactVersion + ".pom";
155 File file = pathTranslator.toFile( basedir, namespace, projectId, projectVersion, id );
157 if ( !file.exists() )
159 // metadata could not be resolved
160 throw new RepositoryStorageMetadataNotFoundException(
161 "The artifact's POM file '" + file.getAbsolutePath() + "' was missing" );
164 ModelBuildingRequest req = new DefaultModelBuildingRequest();
165 req.setProcessPlugins( false );
166 req.setPomFile( file );
167 req.setModelResolver( new RepositoryModelResolver( basedir, pathTranslator ) );
168 req.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL );
173 model = builder.build( req ).getEffectiveModel();
175 catch ( ModelBuildingException e )
177 String msg = "The artifact's POM file '" + file + "' was invalid: " + e.getMessage();
179 throw new RepositoryStorageMetadataInvalidException( "invalid-pom", msg, e );
182 // Check if the POM is in the correct location
183 boolean correctGroupId = namespace.equals( model.getGroupId() );
184 boolean correctArtifactId = projectId.equals( model.getArtifactId() );
185 boolean correctVersion = projectVersion.equals( model.getVersion() );
186 if ( !correctGroupId || !correctArtifactId || !correctVersion )
188 StringBuilder message = new StringBuilder( "Incorrect POM coordinates in '" + file + "':" );
189 if ( !correctGroupId )
191 message.append( "\nIncorrect group ID: " ).append( model.getGroupId() );
193 if ( !correctArtifactId )
195 message.append( "\nIncorrect artifact ID: " ).append( model.getArtifactId() );
197 if ( !correctVersion )
199 message.append( "\nIncorrect version: " ).append( model.getVersion() );
202 throw new RepositoryStorageMetadataInvalidException( "mislocated-pom", message.toString() );
205 ProjectVersionMetadata metadata = new ProjectVersionMetadata();
206 metadata.setCiManagement( convertCiManagement( model.getCiManagement() ) );
207 metadata.setDescription( model.getDescription() );
208 metadata.setId( projectVersion );
209 metadata.setIssueManagement( convertIssueManagement( model.getIssueManagement() ) );
210 metadata.setLicenses( convertLicenses( model.getLicenses() ) );
211 metadata.setMailingLists( convertMailingLists( model.getMailingLists() ) );
212 metadata.setDependencies( convertDependencies( model.getDependencies() ) );
213 metadata.setName( model.getName() );
214 metadata.setOrganization( convertOrganization( model.getOrganization() ) );
215 metadata.setScm( convertScm( model.getScm() ) );
216 metadata.setUrl( model.getUrl() );
218 MavenProjectFacet facet = new MavenProjectFacet();
219 facet.setGroupId( model.getGroupId() != null ? model.getGroupId() : model.getParent().getGroupId() );
220 facet.setArtifactId( model.getArtifactId() );
221 facet.setPackaging( model.getPackaging() );
222 if ( model.getParent() != null )
224 MavenProjectParent parent = new MavenProjectParent();
225 parent.setGroupId( model.getParent().getGroupId() );
226 parent.setArtifactId( model.getParent().getArtifactId() );
227 parent.setVersion( model.getParent().getVersion() );
228 facet.setParent( parent );
230 metadata.addFacet( facet );
235 private List<org.apache.archiva.metadata.model.Dependency> convertDependencies( List<Dependency> dependencies )
237 List<org.apache.archiva.metadata.model.Dependency> l =
238 new ArrayList<org.apache.archiva.metadata.model.Dependency>();
239 for ( Dependency dependency : dependencies )
241 org.apache.archiva.metadata.model.Dependency newDependency =
242 new org.apache.archiva.metadata.model.Dependency();
243 newDependency.setArtifactId( dependency.getArtifactId() );
244 newDependency.setClassifier( dependency.getClassifier() );
245 newDependency.setGroupId( dependency.getGroupId() );
246 newDependency.setOptional( dependency.isOptional() );
247 newDependency.setScope( dependency.getScope() );
248 newDependency.setSystemPath( dependency.getSystemPath() );
249 newDependency.setType( dependency.getType() );
250 newDependency.setVersion( dependency.getVersion() );
251 l.add( newDependency );
256 private org.apache.archiva.metadata.model.Scm convertScm( Scm scm )
258 org.apache.archiva.metadata.model.Scm newScm = null;
261 newScm = new org.apache.archiva.metadata.model.Scm();
262 newScm.setConnection( scm.getConnection() );
263 newScm.setDeveloperConnection( scm.getDeveloperConnection() );
264 newScm.setUrl( scm.getUrl() );
269 private org.apache.archiva.metadata.model.Organization convertOrganization( Organization organization )
271 org.apache.archiva.metadata.model.Organization org = null;
272 if ( organization != null )
274 org = new org.apache.archiva.metadata.model.Organization();
275 org.setName( organization.getName() );
276 org.setUrl( organization.getUrl() );
281 private List<org.apache.archiva.metadata.model.License> convertLicenses( List<License> licenses )
283 List<org.apache.archiva.metadata.model.License> l = new ArrayList<org.apache.archiva.metadata.model.License>();
284 for ( License license : licenses )
286 org.apache.archiva.metadata.model.License newLicense = new org.apache.archiva.metadata.model.License();
287 newLicense.setName( license.getName() );
288 newLicense.setUrl( license.getUrl() );
294 private List<org.apache.archiva.metadata.model.MailingList> convertMailingLists( List<MailingList> mailingLists )
296 List<org.apache.archiva.metadata.model.MailingList> l =
297 new ArrayList<org.apache.archiva.metadata.model.MailingList>();
298 for ( MailingList mailingList : mailingLists )
300 org.apache.archiva.metadata.model.MailingList newMailingList =
301 new org.apache.archiva.metadata.model.MailingList();
302 newMailingList.setName( mailingList.getName() );
303 newMailingList.setMainArchiveUrl( mailingList.getArchive() );
304 newMailingList.setPostAddress( mailingList.getPost() );
305 newMailingList.setSubscribeAddress( mailingList.getSubscribe() );
306 newMailingList.setUnsubscribeAddress( mailingList.getUnsubscribe() );
307 newMailingList.setOtherArchives( mailingList.getOtherArchives() );
308 l.add( newMailingList );
313 private org.apache.archiva.metadata.model.IssueManagement convertIssueManagement( IssueManagement issueManagement )
315 org.apache.archiva.metadata.model.IssueManagement im = null;
316 if ( issueManagement != null )
318 im = new org.apache.archiva.metadata.model.IssueManagement();
319 im.setSystem( issueManagement.getSystem() );
320 im.setUrl( issueManagement.getUrl() );
325 private org.apache.archiva.metadata.model.CiManagement convertCiManagement( CiManagement ciManagement )
327 org.apache.archiva.metadata.model.CiManagement ci = null;
328 if ( ciManagement != null )
330 ci = new org.apache.archiva.metadata.model.CiManagement();
331 ci.setSystem( ciManagement.getSystem() );
332 ci.setUrl( ciManagement.getUrl() );
337 public Collection<String> listRootNamespaces( String repoId, Filter<String> filter )
339 File dir = getRepositoryBasedir( repoId );
341 return getSortedFiles( dir, filter );
344 private static Collection<String> getSortedFiles( File dir, Filter<String> filter )
346 List<String> fileNames;
347 String[] files = dir.list( new DirectoryFilter( filter ) );
350 fileNames = new ArrayList<String>( Arrays.asList( files ) );
351 Collections.sort( fileNames );
355 fileNames = Collections.emptyList();
360 private File getRepositoryBasedir( String repoId )
362 ManagedRepositoryConfiguration repositoryConfiguration =
363 archivaConfiguration.getConfiguration().findManagedRepositoryById( repoId );
365 return new File( repositoryConfiguration.getLocation() );
368 public Collection<String> listNamespaces( String repoId, String namespace, Filter<String> filter )
370 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace );
372 // scan all the directories which are potential namespaces. Any directories known to be projects are excluded
373 List<String> namespaces = new ArrayList<String>();
374 File[] files = dir.listFiles( new DirectoryFilter( filter ) );
377 for ( File file : files )
379 if ( !isProject( file, filter ) )
381 namespaces.add( file.getName() );
385 Collections.sort( namespaces );
389 public Collection<String> listProjects( String repoId, String namespace, Filter<String> filter )
391 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace );
393 // scan all directories in the namespace, and only include those that are known to be projects
394 List<String> projects = new ArrayList<String>();
395 File[] files = dir.listFiles( new DirectoryFilter( filter ) );
398 for ( File file : files )
400 if ( isProject( file, filter ) )
402 projects.add( file.getName() );
406 Collections.sort( projects );
410 public Collection<String> listProjectVersions( String repoId, String namespace, String projectId,
411 Filter<String> filter )
413 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace, projectId );
415 // all directories in a project directory can be considered a version
416 return getSortedFiles( dir, filter );
419 public Collection<ArtifactMetadata> readArtifactsMetadata( String repoId, String namespace, String projectId,
420 String projectVersion, Filter<String> filter )
422 File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace, projectId, projectVersion );
424 // all files that are not metadata and not a checksum / signature are considered artifacts
425 File[] files = dir.listFiles( new ArtifactDirectoryFilter( filter ) );
427 List<ArtifactMetadata> artifacts = new ArrayList<ArtifactMetadata>();
430 for ( File file : files )
432 ArtifactMetadata metadata = getArtifactFromFile( repoId, namespace, projectId, projectVersion, file );
433 artifacts.add( metadata );
439 public ArtifactMetadata readArtifactMetadataFromPath( String repoId, String path )
441 ArtifactMetadata metadata = pathTranslator.getArtifactForPath( repoId, path );
443 populateArtifactMetadataFromFile( metadata, new File( getRepositoryBasedir( repoId ), path ) );
448 private ArtifactMetadata getArtifactFromFile( String repoId, String namespace, String projectId,
449 String projectVersion, File file )
451 ArtifactMetadata metadata =
452 pathTranslator.getArtifactFromId( repoId, namespace, projectId, projectVersion, file.getName() );
454 populateArtifactMetadataFromFile( metadata, file );
459 private static void populateArtifactMetadataFromFile( ArtifactMetadata metadata, File file )
461 metadata.setWhenGathered( new Date() );
462 metadata.setFileLastModified( file.lastModified() );
463 ChecksummedFile checksummedFile = new ChecksummedFile( file );
466 metadata.setMd5( checksummedFile.calculateChecksum( ChecksumAlgorithm.MD5 ) );
468 catch ( IOException e )
470 log.error( "Unable to checksum file " + file + ": " + e.getMessage() );
474 metadata.setSha1( checksummedFile.calculateChecksum( ChecksumAlgorithm.SHA1 ) );
476 catch ( IOException e )
478 log.error( "Unable to checksum file " + file + ": " + e.getMessage() );
480 metadata.setSize( file.length() );
483 private boolean isProject( File dir, Filter<String> filter )
485 // scan directories for a valid project version subdirectory, meaning this must be a project directory
486 File[] files = dir.listFiles( new DirectoryFilter( filter ) );
489 for ( File file : files )
491 if ( isProjectVersion( file ) )
498 // if a metadata file is present, check if this is the "artifactId" directory, marking it as a project
499 MavenRepositoryMetadata metadata = readMetadata( dir );
500 if ( metadata != null && dir.getName().equals( metadata.getArtifactId() ) )
508 private boolean isProjectVersion( File dir )
510 final String artifactId = dir.getParentFile().getName();
511 final String projectVersion = dir.getName();
513 // check if there is a POM artifact file to ensure it is a version directory
515 if ( VersionUtil.isSnapshot( projectVersion ) )
517 files = dir.listFiles( new FilenameFilter()
519 public boolean accept( File dir, String name )
521 if ( name.startsWith( artifactId + "-" ) && name.endsWith( ".pom" ) )
523 String v = name.substring( artifactId.length() + 1, name.length() - 4 );
524 v = VersionUtil.getBaseVersion( v );
525 if ( v.equals( projectVersion ) )
536 final String pomFile = artifactId + "-" + projectVersion + ".pom";
537 files = dir.listFiles( new FilenameFilter()
539 public boolean accept( File dir, String name )
541 return pomFile.equals( name );
545 if ( files != null && files.length > 0 )
550 // if a metadata file is present, check if this is the "version" directory, marking it as a project version
551 MavenRepositoryMetadata metadata = readMetadata( dir );
552 if ( metadata != null && projectVersion.equals( metadata.getVersion() ) )
560 private MavenRepositoryMetadata readMetadata( File directory )
562 MavenRepositoryMetadata metadata = null;
563 File metadataFile = new File( directory, METADATA_FILENAME );
564 if ( metadataFile.exists() )
568 metadata = MavenRepositoryMetadataReader.read( metadataFile );
570 catch ( XMLException e )
572 // ignore missing or invalid metadata
578 private static class DirectoryFilter
579 implements FilenameFilter
581 private final Filter<String> filter;
583 public DirectoryFilter( Filter<String> filter )
585 this.filter = filter;
588 public boolean accept( File dir, String name )
590 if ( !filter.accept( name ) )
594 else if ( name.startsWith( "." ) )
598 else if ( !new File( dir, name ).isDirectory() )
606 private class ArtifactDirectoryFilter
607 implements FilenameFilter
609 private final Filter<String> filter;
611 public ArtifactDirectoryFilter( Filter<String> filter )
613 this.filter = filter;
616 public boolean accept( File dir, String name )
618 // TODO compare to logic in maven-repository-layer
619 if ( !filter.accept( name ) )
623 else if ( name.startsWith( "." ) )
627 else if ( name.endsWith( ".md5" ) || name.endsWith( ".sha1" ) || name.endsWith( ".asc" ) )
631 else if ( name.equals( METADATA_FILENAME ) )
635 else if ( new File( dir, name ).isDirectory() )