]> source.dussan.org Git - archiva.git/blob
5eaa390f8c204683d801abce9b0e387ae5d6b6df
[archiva.git] /
1 package org.apache.archiva.metadata.repository.storage.maven2;
2
3 /*
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
11  *
12  *   http://www.apache.org/licenses/LICENSE-2.0
13  *
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
19  * under the License.
20  */
21
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;
52
53 import javax.annotation.PostConstruct;
54 import javax.inject.Inject;
55 import javax.inject.Named;
56 import java.io.File;
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;
65
66 /**
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
71  * <p/>
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.
74  * <p/>
75  * plexus.component role="org.apache.archiva.metadata.repository.storage.RepositoryStorage" role-hint="maven2"
76  */
77 @Service( "repositoryStorage#maven2" )
78 public class Maven2RepositoryStorage
79     implements RepositoryStorage
80 {
81     /**
82      * plexus.requirement
83      */
84     private ModelBuilder builder;
85
86     /**
87      * plexus.requirement
88      */
89     @Inject
90     @Named( value = "archivaConfiguration#default" )
91     private ArchivaConfiguration archivaConfiguration;
92
93     /**
94      * plexus.requirement role-hint="maven2"
95      */
96     @Inject
97     @Named( value = "repositoryPathTranslator#maven2" )
98     private RepositoryPathTranslator pathTranslator;
99
100     private final static Logger log = LoggerFactory.getLogger( Maven2RepositoryStorage.class );
101
102     private static final String METADATA_FILENAME = "maven-metadata.xml";
103
104
105     @PostConstruct
106     public void initialize()
107     {
108         DefaultModelBuilderFactory defaultModelBuilderFactory = new DefaultModelBuilderFactory();
109         builder = defaultModelBuilderFactory.newInstance();
110     }
111
112     public ProjectMetadata readProjectMetadata( String repoId, String namespace, String projectId )
113     {
114         // TODO: could natively implement the "shared model" concept from the browse action to avoid needing it there?
115         return null;
116     }
117
118     public ProjectVersionMetadata readProjectVersionMetadata( String repoId, String namespace, String projectId,
119                                                               String projectVersion )
120         throws RepositoryStorageMetadataNotFoundException, RepositoryStorageMetadataInvalidException
121     {
122         ManagedRepositoryConfiguration repositoryConfiguration =
123             archivaConfiguration.getConfiguration().findManagedRepositoryById( repoId );
124
125         String artifactVersion = projectVersion;
126
127         File basedir = new File( repositoryConfiguration.getLocation() );
128         if ( VersionUtil.isSnapshot( projectVersion ) )
129         {
130             File metadataFile =
131                 pathTranslator.toFile( basedir, namespace, projectId, projectVersion, METADATA_FILENAME );
132             try
133             {
134                 MavenRepositoryMetadata metadata = MavenRepositoryMetadataReader.read( metadataFile );
135
136                 // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
137                 MavenRepositoryMetadata.Snapshot snapshotVersion = metadata.getSnapshotVersion();
138                 if ( snapshotVersion != null )
139                 {
140                     artifactVersion =
141                         artifactVersion.substring( 0, artifactVersion.length() - 8 ); // remove SNAPSHOT from end
142                     artifactVersion =
143                         artifactVersion + snapshotVersion.getTimestamp() + "-" + snapshotVersion.getBuildNumber();
144                 }
145             }
146             catch ( XMLException e )
147             {
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() );
150             }
151         }
152
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 );
156
157         if ( !file.exists() )
158         {
159             // metadata could not be resolved
160             throw new RepositoryStorageMetadataNotFoundException(
161                 "The artifact's POM file '" + file.getAbsolutePath() + "' was missing" );
162         }
163
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 );
169
170         Model model;
171         try
172         {
173             model = builder.build( req ).getEffectiveModel();
174         }
175         catch ( ModelBuildingException e )
176         {
177             String msg = "The artifact's POM file '" + file + "' was invalid: " + e.getMessage();
178
179             throw new RepositoryStorageMetadataInvalidException( "invalid-pom", msg, e );
180         }
181
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 )
187         {
188             StringBuilder message = new StringBuilder( "Incorrect POM coordinates in '" + file + "':" );
189             if ( !correctGroupId )
190             {
191                 message.append( "\nIncorrect group ID: " ).append( model.getGroupId() );
192             }
193             if ( !correctArtifactId )
194             {
195                 message.append( "\nIncorrect artifact ID: " ).append( model.getArtifactId() );
196             }
197             if ( !correctVersion )
198             {
199                 message.append( "\nIncorrect version: " ).append( model.getVersion() );
200             }
201
202             throw new RepositoryStorageMetadataInvalidException( "mislocated-pom", message.toString() );
203         }
204
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() );
217
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 )
223         {
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 );
229         }
230         metadata.addFacet( facet );
231
232         return metadata;
233     }
234
235     private List<org.apache.archiva.metadata.model.Dependency> convertDependencies( List<Dependency> dependencies )
236     {
237         List<org.apache.archiva.metadata.model.Dependency> l =
238             new ArrayList<org.apache.archiva.metadata.model.Dependency>();
239         for ( Dependency dependency : dependencies )
240         {
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 );
252         }
253         return l;
254     }
255
256     private org.apache.archiva.metadata.model.Scm convertScm( Scm scm )
257     {
258         org.apache.archiva.metadata.model.Scm newScm = null;
259         if ( scm != null )
260         {
261             newScm = new org.apache.archiva.metadata.model.Scm();
262             newScm.setConnection( scm.getConnection() );
263             newScm.setDeveloperConnection( scm.getDeveloperConnection() );
264             newScm.setUrl( scm.getUrl() );
265         }
266         return newScm;
267     }
268
269     private org.apache.archiva.metadata.model.Organization convertOrganization( Organization organization )
270     {
271         org.apache.archiva.metadata.model.Organization org = null;
272         if ( organization != null )
273         {
274             org = new org.apache.archiva.metadata.model.Organization();
275             org.setName( organization.getName() );
276             org.setUrl( organization.getUrl() );
277         }
278         return org;
279     }
280
281     private List<org.apache.archiva.metadata.model.License> convertLicenses( List<License> licenses )
282     {
283         List<org.apache.archiva.metadata.model.License> l = new ArrayList<org.apache.archiva.metadata.model.License>();
284         for ( License license : licenses )
285         {
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() );
289             l.add( newLicense );
290         }
291         return l;
292     }
293
294     private List<org.apache.archiva.metadata.model.MailingList> convertMailingLists( List<MailingList> mailingLists )
295     {
296         List<org.apache.archiva.metadata.model.MailingList> l =
297             new ArrayList<org.apache.archiva.metadata.model.MailingList>();
298         for ( MailingList mailingList : mailingLists )
299         {
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 );
309         }
310         return l;
311     }
312
313     private org.apache.archiva.metadata.model.IssueManagement convertIssueManagement( IssueManagement issueManagement )
314     {
315         org.apache.archiva.metadata.model.IssueManagement im = null;
316         if ( issueManagement != null )
317         {
318             im = new org.apache.archiva.metadata.model.IssueManagement();
319             im.setSystem( issueManagement.getSystem() );
320             im.setUrl( issueManagement.getUrl() );
321         }
322         return im;
323     }
324
325     private org.apache.archiva.metadata.model.CiManagement convertCiManagement( CiManagement ciManagement )
326     {
327         org.apache.archiva.metadata.model.CiManagement ci = null;
328         if ( ciManagement != null )
329         {
330             ci = new org.apache.archiva.metadata.model.CiManagement();
331             ci.setSystem( ciManagement.getSystem() );
332             ci.setUrl( ciManagement.getUrl() );
333         }
334         return ci;
335     }
336
337     public Collection<String> listRootNamespaces( String repoId, Filter<String> filter )
338     {
339         File dir = getRepositoryBasedir( repoId );
340
341         return getSortedFiles( dir, filter );
342     }
343
344     private static Collection<String> getSortedFiles( File dir, Filter<String> filter )
345     {
346         List<String> fileNames;
347         String[] files = dir.list( new DirectoryFilter( filter ) );
348         if ( files != null )
349         {
350             fileNames = new ArrayList<String>( Arrays.asList( files ) );
351             Collections.sort( fileNames );
352         }
353         else
354         {
355             fileNames = Collections.emptyList();
356         }
357         return fileNames;
358     }
359
360     private File getRepositoryBasedir( String repoId )
361     {
362         ManagedRepositoryConfiguration repositoryConfiguration =
363             archivaConfiguration.getConfiguration().findManagedRepositoryById( repoId );
364
365         return new File( repositoryConfiguration.getLocation() );
366     }
367
368     public Collection<String> listNamespaces( String repoId, String namespace, Filter<String> filter )
369     {
370         File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace );
371
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 ) );
375         if ( files != null )
376         {
377             for ( File file : files )
378             {
379                 if ( !isProject( file, filter ) )
380                 {
381                     namespaces.add( file.getName() );
382                 }
383             }
384         }
385         Collections.sort( namespaces );
386         return namespaces;
387     }
388
389     public Collection<String> listProjects( String repoId, String namespace, Filter<String> filter )
390     {
391         File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace );
392
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 ) );
396         if ( files != null )
397         {
398             for ( File file : files )
399             {
400                 if ( isProject( file, filter ) )
401                 {
402                     projects.add( file.getName() );
403                 }
404             }
405         }
406         Collections.sort( projects );
407         return projects;
408     }
409
410     public Collection<String> listProjectVersions( String repoId, String namespace, String projectId,
411                                                    Filter<String> filter )
412     {
413         File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace, projectId );
414
415         // all directories in a project directory can be considered a version
416         return getSortedFiles( dir, filter );
417     }
418
419     public Collection<ArtifactMetadata> readArtifactsMetadata( String repoId, String namespace, String projectId,
420                                                                String projectVersion, Filter<String> filter )
421     {
422         File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace, projectId, projectVersion );
423
424         // all files that are not metadata and not a checksum / signature are considered artifacts
425         File[] files = dir.listFiles( new ArtifactDirectoryFilter( filter ) );
426
427         List<ArtifactMetadata> artifacts = new ArrayList<ArtifactMetadata>();
428         if ( files != null )
429         {
430             for ( File file : files )
431             {
432                 ArtifactMetadata metadata = getArtifactFromFile( repoId, namespace, projectId, projectVersion, file );
433                 artifacts.add( metadata );
434             }
435         }
436         return artifacts;
437     }
438
439     public ArtifactMetadata readArtifactMetadataFromPath( String repoId, String path )
440     {
441         ArtifactMetadata metadata = pathTranslator.getArtifactForPath( repoId, path );
442
443         populateArtifactMetadataFromFile( metadata, new File( getRepositoryBasedir( repoId ), path ) );
444
445         return metadata;
446     }
447
448     private ArtifactMetadata getArtifactFromFile( String repoId, String namespace, String projectId,
449                                                   String projectVersion, File file )
450     {
451         ArtifactMetadata metadata =
452             pathTranslator.getArtifactFromId( repoId, namespace, projectId, projectVersion, file.getName() );
453
454         populateArtifactMetadataFromFile( metadata, file );
455
456         return metadata;
457     }
458
459     private static void populateArtifactMetadataFromFile( ArtifactMetadata metadata, File file )
460     {
461         metadata.setWhenGathered( new Date() );
462         metadata.setFileLastModified( file.lastModified() );
463         ChecksummedFile checksummedFile = new ChecksummedFile( file );
464         try
465         {
466             metadata.setMd5( checksummedFile.calculateChecksum( ChecksumAlgorithm.MD5 ) );
467         }
468         catch ( IOException e )
469         {
470             log.error( "Unable to checksum file " + file + ": " + e.getMessage() );
471         }
472         try
473         {
474             metadata.setSha1( checksummedFile.calculateChecksum( ChecksumAlgorithm.SHA1 ) );
475         }
476         catch ( IOException e )
477         {
478             log.error( "Unable to checksum file " + file + ": " + e.getMessage() );
479         }
480         metadata.setSize( file.length() );
481     }
482
483     private boolean isProject( File dir, Filter<String> filter )
484     {
485         // scan directories for a valid project version subdirectory, meaning this must be a project directory
486         File[] files = dir.listFiles( new DirectoryFilter( filter ) );
487         if ( files != null )
488         {
489             for ( File file : files )
490             {
491                 if ( isProjectVersion( file ) )
492                 {
493                     return true;
494                 }
495             }
496         }
497
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() ) )
501         {
502             return true;
503         }
504
505         return false;
506     }
507
508     private boolean isProjectVersion( File dir )
509     {
510         final String artifactId = dir.getParentFile().getName();
511         final String projectVersion = dir.getName();
512
513         // check if there is a POM artifact file to ensure it is a version directory
514         File[] files;
515         if ( VersionUtil.isSnapshot( projectVersion ) )
516         {
517             files = dir.listFiles( new FilenameFilter()
518             {
519                 public boolean accept( File dir, String name )
520                 {
521                     if ( name.startsWith( artifactId + "-" ) && name.endsWith( ".pom" ) )
522                     {
523                         String v = name.substring( artifactId.length() + 1, name.length() - 4 );
524                         v = VersionUtil.getBaseVersion( v );
525                         if ( v.equals( projectVersion ) )
526                         {
527                             return true;
528                         }
529                     }
530                     return false;
531                 }
532             } );
533         }
534         else
535         {
536             final String pomFile = artifactId + "-" + projectVersion + ".pom";
537             files = dir.listFiles( new FilenameFilter()
538             {
539                 public boolean accept( File dir, String name )
540                 {
541                     return pomFile.equals( name );
542                 }
543             } );
544         }
545         if ( files != null && files.length > 0 )
546         {
547             return true;
548         }
549
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() ) )
553         {
554             return true;
555         }
556
557         return false;
558     }
559
560     private MavenRepositoryMetadata readMetadata( File directory )
561     {
562         MavenRepositoryMetadata metadata = null;
563         File metadataFile = new File( directory, METADATA_FILENAME );
564         if ( metadataFile.exists() )
565         {
566             try
567             {
568                 metadata = MavenRepositoryMetadataReader.read( metadataFile );
569             }
570             catch ( XMLException e )
571             {
572                 // ignore missing or invalid metadata
573             }
574         }
575         return metadata;
576     }
577
578     private static class DirectoryFilter
579         implements FilenameFilter
580     {
581         private final Filter<String> filter;
582
583         public DirectoryFilter( Filter<String> filter )
584         {
585             this.filter = filter;
586         }
587
588         public boolean accept( File dir, String name )
589         {
590             if ( !filter.accept( name ) )
591             {
592                 return false;
593             }
594             else if ( name.startsWith( "." ) )
595             {
596                 return false;
597             }
598             else if ( !new File( dir, name ).isDirectory() )
599             {
600                 return false;
601             }
602             return true;
603         }
604     }
605
606     private class ArtifactDirectoryFilter
607         implements FilenameFilter
608     {
609         private final Filter<String> filter;
610
611         public ArtifactDirectoryFilter( Filter<String> filter )
612         {
613             this.filter = filter;
614         }
615
616         public boolean accept( File dir, String name )
617         {
618             // TODO compare to logic in maven-repository-layer
619             if ( !filter.accept( name ) )
620             {
621                 return false;
622             }
623             else if ( name.startsWith( "." ) )
624             {
625                 return false;
626             }
627             else if ( name.endsWith( ".md5" ) || name.endsWith( ".sha1" ) || name.endsWith( ".asc" ) )
628             {
629                 return false;
630             }
631             else if ( name.equals( METADATA_FILENAME ) )
632             {
633                 return false;
634             }
635             else if ( new File( dir, name ).isDirectory() )
636             {
637                 return false;
638             }
639             return true;
640         }
641     }
642 }