]> source.dussan.org Git - archiva.git/blob
e0ec1fe273ef857139f58271a5012c60f66cac9e
[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 java.io.File;
23 import java.io.FilenameFilter;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.Date;
30 import java.util.List;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33
34 import org.apache.archiva.checksum.ChecksumAlgorithm;
35 import org.apache.archiva.checksum.ChecksummedFile;
36 import org.apache.archiva.metadata.model.ArtifactMetadata;
37 import org.apache.archiva.metadata.model.ProjectMetadata;
38 import org.apache.archiva.metadata.model.ProjectVersionMetadata;
39 import org.apache.archiva.metadata.model.ProjectVersionReference;
40 import org.apache.archiva.metadata.repository.MetadataRepository;
41 import org.apache.archiva.metadata.repository.filter.AllFilter;
42 import org.apache.archiva.metadata.repository.filter.Filter;
43 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
44 import org.apache.archiva.metadata.repository.storage.StorageMetadataResolver;
45 import org.apache.archiva.reports.RepositoryProblemFacet;
46 import org.apache.maven.archiva.common.utils.VersionUtil;
47 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
48 import org.apache.maven.archiva.configuration.ManagedRepositoryConfiguration;
49 import org.apache.maven.archiva.xml.XMLException;
50 import org.apache.maven.model.CiManagement;
51 import org.apache.maven.model.Dependency;
52 import org.apache.maven.model.IssueManagement;
53 import org.apache.maven.model.License;
54 import org.apache.maven.model.MailingList;
55 import org.apache.maven.model.Model;
56 import org.apache.maven.model.Organization;
57 import org.apache.maven.model.Scm;
58 import org.apache.maven.model.building.DefaultModelBuildingRequest;
59 import org.apache.maven.model.building.ModelBuilder;
60 import org.apache.maven.model.building.ModelBuildingException;
61 import org.apache.maven.model.building.ModelBuildingRequest;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64
65 /**
66  * @plexus.component role="org.apache.archiva.metadata.repository.storage.StorageMetadataResolver" role-hint="maven2"
67  */
68 public class Maven2RepositoryMetadataResolver
69     implements StorageMetadataResolver
70 {
71     /**
72      * @plexus.requirement
73      */
74     private ModelBuilder builder;
75
76     /**
77      * @plexus.requirement
78      */
79     private ArchivaConfiguration archivaConfiguration;
80
81     /**
82      * @plexus.requirement role-hint="maven2"
83      */
84     private RepositoryPathTranslator pathTranslator;
85
86     /**
87      * @plexus.requirement
88      */
89     private MetadataRepository metadataRepository;
90
91     private final static Logger log = LoggerFactory.getLogger( Maven2RepositoryMetadataResolver.class );
92
93     private static final String METADATA_FILENAME = "maven-metadata.xml";
94
95     private static final Filter<String> ALL = new AllFilter<String>();
96
97     public ProjectMetadata getProject( String repoId, String namespace, String projectId )
98     {
99         // TODO: could natively implement the "shared model" concept from the browse action to avoid needing it there?
100         return null;
101     }
102
103     public ProjectVersionMetadata getProjectVersion( String repoId, String namespace, String projectId,
104                                                      String projectVersion )
105     {
106         ManagedRepositoryConfiguration repositoryConfiguration =
107             archivaConfiguration.getConfiguration().findManagedRepositoryById( repoId );
108
109         String artifactVersion = projectVersion;
110
111         File basedir = new File( repositoryConfiguration.getLocation() );
112         if ( VersionUtil.isSnapshot( projectVersion ) )
113         {
114             File metadataFile =
115                 pathTranslator.toFile( basedir, namespace, projectId, projectVersion, METADATA_FILENAME );
116             try
117             {
118                 MavenRepositoryMetadata metadata = MavenRepositoryMetadataReader.read( metadataFile );
119
120                 // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
121                 MavenRepositoryMetadata.Snapshot snapshotVersion = metadata.getSnapshotVersion();
122                 if ( snapshotVersion != null )
123                 {
124                     artifactVersion =
125                         artifactVersion.substring( 0, artifactVersion.length() - 8 ); // remove SNAPSHOT from end
126                     artifactVersion =
127                         artifactVersion + snapshotVersion.getTimestamp() + "-" + snapshotVersion.getBuildNumber();
128                 }
129             }
130             catch ( XMLException e )
131             {
132                 // unable to parse metadata - log it, and continue with the version as the original SNAPSHOT version
133                 log.warn( "Invalid metadata: " + metadataFile + " - " + e.getMessage() );
134             }
135         }
136
137         String id = projectId + "-" + artifactVersion + ".pom";
138         File file = pathTranslator.toFile( basedir, namespace, projectId, projectVersion, id );
139
140         if ( !file.exists() )
141         {
142             // TODO: an event mechanism would remove coupling to the problem reporting plugin
143             addProblemReport( repoId, namespace, projectId, projectVersion, "missing-pom",
144                               "The artifact's POM file '" + file + "' was missing" );
145
146             // metadata could not be resolved
147             return null;
148         }
149
150         ModelBuildingRequest req = new DefaultModelBuildingRequest();
151         req.setProcessPlugins( false );
152         req.setPomFile( file );
153         req.setModelResolver( new RepositoryModelResolver( basedir, pathTranslator ) );
154         req.setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL );
155
156         Model model;
157         try
158         {
159             model = builder.build( req ).getEffectiveModel();
160         }
161         catch ( ModelBuildingException e )
162         {
163             addProblemReport( repoId, namespace, projectId, projectVersion, "invalid-pom",
164                               "The artifact's POM file '" + file + "' was invalid: " + e.getMessage() );
165
166             // metadata could not be resolved
167             return null;
168         }
169
170         // Check if the POM is in the correct location
171         boolean correctGroupId = namespace.equals( model.getGroupId() );
172         boolean correctArtifactId = projectId.equals( model.getArtifactId() );
173         boolean correctVersion = projectVersion.equals( model.getVersion() );
174         if ( !correctGroupId || !correctArtifactId || !correctVersion )
175         {
176             StringBuilder message = new StringBuilder( "Incorrect POM coordinates in '" + file + "':" );
177             if ( !correctGroupId )
178             {
179                 message.append( "\nIncorrect group ID: " ).append( model.getGroupId() );
180             }
181             if ( !correctArtifactId )
182             {
183                 message.append( "\nIncorrect artifact ID: " ).append( model.getArtifactId() );
184             }
185             if ( !correctVersion )
186             {
187                 message.append( "\nIncorrect version: " ).append( model.getVersion() );
188             }
189
190             addProblemReport( repoId, namespace, projectId, projectVersion, "mislocated-pom", message.toString() );
191
192             // metadata could not be resolved
193             return null;
194         }
195
196         ProjectVersionMetadata metadata = new ProjectVersionMetadata();
197         metadata.setCiManagement( convertCiManagement( model.getCiManagement() ) );
198         metadata.setDescription( model.getDescription() );
199         metadata.setId( projectVersion );
200         metadata.setIssueManagement( convertIssueManagement( model.getIssueManagement() ) );
201         metadata.setLicenses( convertLicenses( model.getLicenses() ) );
202         metadata.setMailingLists( convertMailingLists( model.getMailingLists() ) );
203         metadata.setDependencies( convertDependencies( model.getDependencies() ) );
204         metadata.setName( model.getName() );
205         metadata.setOrganization( convertOrganization( model.getOrganization() ) );
206         metadata.setScm( convertScm( model.getScm() ) );
207         metadata.setUrl( model.getUrl() );
208
209         MavenProjectFacet facet = new MavenProjectFacet();
210         facet.setGroupId( model.getGroupId() != null ? model.getGroupId() : model.getParent().getGroupId() );
211         facet.setArtifactId( model.getArtifactId() );
212         facet.setPackaging( model.getPackaging() );
213         if ( model.getParent() != null )
214         {
215             MavenProjectParent parent = new MavenProjectParent();
216             parent.setGroupId( model.getParent().getGroupId() );
217             parent.setArtifactId( model.getParent().getArtifactId() );
218             parent.setVersion( model.getParent().getVersion() );
219             facet.setParent( parent );
220         }
221         metadata.addFacet( facet );
222
223         return metadata;
224     }
225
226     private void addProblemReport( String repoId, String namespace, String projectId, String projectVersion,
227                                    String problemId, String message )
228     {
229         // TODO: an event mechanism would remove coupling to the problem reporting plugin
230         RepositoryProblemFacet problem = new RepositoryProblemFacet();
231         problem.setProblem( problemId );
232         problem.setMessage( message );
233         problem.setProject( projectId );
234         problem.setNamespace( namespace );
235         problem.setRepositoryId( repoId );
236         problem.setVersion( projectVersion );
237
238         metadataRepository.addMetadataFacet( repoId, problem );
239     }
240
241     private List<org.apache.archiva.metadata.model.Dependency> convertDependencies( List<Dependency> dependencies )
242     {
243         List<org.apache.archiva.metadata.model.Dependency> l =
244             new ArrayList<org.apache.archiva.metadata.model.Dependency>();
245         for ( Dependency dependency : dependencies )
246         {
247             org.apache.archiva.metadata.model.Dependency newDependency =
248                 new org.apache.archiva.metadata.model.Dependency();
249             newDependency.setArtifactId( dependency.getArtifactId() );
250             newDependency.setClassifier( dependency.getClassifier() );
251             newDependency.setGroupId( dependency.getGroupId() );
252             newDependency.setOptional( dependency.isOptional() );
253             newDependency.setScope( dependency.getScope() );
254             newDependency.setSystemPath( dependency.getSystemPath() );
255             newDependency.setType( dependency.getType() );
256             newDependency.setVersion( dependency.getVersion() );
257             l.add( newDependency );
258         }
259         return l;
260     }
261
262     private org.apache.archiva.metadata.model.Scm convertScm( Scm scm )
263     {
264         org.apache.archiva.metadata.model.Scm newScm = null;
265         if ( scm != null )
266         {
267             newScm = new org.apache.archiva.metadata.model.Scm();
268             newScm.setConnection( scm.getConnection() );
269             newScm.setDeveloperConnection( scm.getDeveloperConnection() );
270             newScm.setUrl( scm.getUrl() );
271         }
272         return newScm;
273     }
274
275     private org.apache.archiva.metadata.model.Organization convertOrganization( Organization organization )
276     {
277         org.apache.archiva.metadata.model.Organization org = null;
278         if ( organization != null )
279         {
280             org = new org.apache.archiva.metadata.model.Organization();
281             org.setName( organization.getName() );
282             org.setUrl( organization.getUrl() );
283         }
284         return org;
285     }
286
287     private List<org.apache.archiva.metadata.model.License> convertLicenses( List<License> licenses )
288     {
289         List<org.apache.archiva.metadata.model.License> l = new ArrayList<org.apache.archiva.metadata.model.License>();
290         for ( License license : licenses )
291         {
292             org.apache.archiva.metadata.model.License newLicense = new org.apache.archiva.metadata.model.License();
293             newLicense.setName( license.getName() );
294             newLicense.setUrl( license.getUrl() );
295             l.add( newLicense );
296         }
297         return l;
298     }
299
300     private List<org.apache.archiva.metadata.model.MailingList> convertMailingLists( List<MailingList> mailingLists )
301     {
302         List<org.apache.archiva.metadata.model.MailingList> l =
303             new ArrayList<org.apache.archiva.metadata.model.MailingList>();
304         for ( MailingList mailingList : mailingLists )
305         {
306             org.apache.archiva.metadata.model.MailingList newMailingList =
307                 new org.apache.archiva.metadata.model.MailingList();
308             newMailingList.setName( mailingList.getName() );
309             newMailingList.setMainArchiveUrl( mailingList.getArchive() );
310             newMailingList.setPostAddress( mailingList.getPost() );
311             newMailingList.setSubscribeAddress( mailingList.getSubscribe() );
312             newMailingList.setUnsubscribeAddress( mailingList.getUnsubscribe() );
313             newMailingList.setOtherArchives( mailingList.getOtherArchives() );
314             l.add( newMailingList );
315         }
316         return l;
317     }
318
319     private org.apache.archiva.metadata.model.IssueManagement convertIssueManagement( IssueManagement issueManagement )
320     {
321         org.apache.archiva.metadata.model.IssueManagement im = null;
322         if ( issueManagement != null )
323         {
324             im = new org.apache.archiva.metadata.model.IssueManagement();
325             im.setSystem( issueManagement.getSystem() );
326             im.setUrl( issueManagement.getUrl() );
327         }
328         return im;
329     }
330
331     private org.apache.archiva.metadata.model.CiManagement convertCiManagement( CiManagement ciManagement )
332     {
333         org.apache.archiva.metadata.model.CiManagement ci = null;
334         if ( ciManagement != null )
335         {
336             ci = new org.apache.archiva.metadata.model.CiManagement();
337             ci.setSystem( ciManagement.getSystem() );
338             ci.setUrl( ciManagement.getUrl() );
339         }
340         return ci;
341     }
342
343     public Collection<String> getArtifactVersions( String repoId, String namespace, String projectId,
344                                                    String projectVersion )
345     {
346         // TODO: useful, but not implemented yet, not called from DefaultMetadataResolver
347         throw new UnsupportedOperationException();
348     }
349
350     public Collection<ProjectVersionReference> getProjectReferences( String repoId, String namespace, String projectId,
351                                                                      String projectVersion )
352     {
353         // Can't be determined on a Maven 2 repository
354         throw new UnsupportedOperationException();
355     }
356
357     public Collection<String> getRootNamespaces( String repoId )
358     {
359         return getRootNamespaces( repoId, ALL );
360     }
361
362     public Collection<String> getRootNamespaces( String repoId, Filter<String> filter )
363     {
364         File dir = getRepositoryBasedir( repoId );
365
366         String[] files = dir.list( new DirectoryFilter( filter ) );
367         return files != null ? Arrays.asList( files ) : Collections.<String>emptyList();
368     }
369
370     private File getRepositoryBasedir( String repoId )
371     {
372         ManagedRepositoryConfiguration repositoryConfiguration =
373             archivaConfiguration.getConfiguration().findManagedRepositoryById( repoId );
374
375         return new File( repositoryConfiguration.getLocation() );
376     }
377
378     public Collection<String> getNamespaces( String repoId, String namespace )
379     {
380         return getNamespaces( repoId, namespace, ALL );
381     }
382
383     public Collection<String> getNamespaces( String repoId, String namespace, Filter<String> filter )
384     {
385         File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace );
386
387         // scan all the directories which are potential namespaces. Any directories known to be projects are excluded
388         Collection<String> namespaces = new ArrayList<String>();
389         File[] files = dir.listFiles( new DirectoryFilter( filter ) );
390         if ( files != null )
391         {
392             for ( File file : files )
393             {
394                 if ( !isProject( file, filter ) )
395                 {
396                     namespaces.add( file.getName() );
397                 }
398             }
399         }
400         return namespaces;
401     }
402
403     public Collection<String> getProjects( String repoId, String namespace )
404     {
405         return getProjects( repoId, namespace, ALL );
406     }
407
408     public Collection<String> getProjects( String repoId, String namespace, Filter<String> filter )
409     {
410         File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace );
411
412         // scan all directories in the namespace, and only include those that are known to be projects
413         Collection<String> projects = new ArrayList<String>();
414         File[] files = dir.listFiles( new DirectoryFilter( filter ) );
415         if ( files != null )
416         {
417             for ( File file : files )
418             {
419                 if ( isProject( file, filter ) )
420                 {
421                     projects.add( file.getName() );
422                 }
423             }
424         }
425         return projects;
426     }
427
428     public Collection<String> getProjectVersions( String repoId, String namespace, String projectId )
429     {
430         return getProjectVersions( repoId, namespace, projectId, ALL );
431     }
432
433     public Collection<ArtifactMetadata> getArtifacts( String repoId, String namespace, String projectId,
434                                                       String projectVersion )
435     {
436         return getArtifacts( repoId, namespace, projectId, projectVersion, ALL );
437     }
438
439     public Collection<String> getProjectVersions( String repoId, String namespace, String projectId,
440                                                   Filter<String> filter )
441     {
442         File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace, projectId );
443
444         // all directories in a project directory can be considered a version
445         String[] files = dir.list( new DirectoryFilter( filter ) );
446         return files != null ? Arrays.asList( files ) : Collections.<String>emptyList();
447     }
448
449     public Collection<ArtifactMetadata> getArtifacts( String repoId, String namespace, String projectId,
450                                                       String projectVersion, Filter<String> filter )
451     {
452         File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace, projectId, projectVersion );
453
454         // all files that are not metadata and not a checksum / signature are considered artifacts
455         File[] files = dir.listFiles( new ArtifactDirectoryFilter( filter ) );
456
457         List<ArtifactMetadata> artifacts = new ArrayList<ArtifactMetadata>();
458         if ( files != null )
459         {
460             for ( File file : files )
461             {
462                 ArtifactMetadata metadata = new ArtifactMetadata();
463                 metadata.setId( file.getName() );
464                 metadata.setProject( projectId );
465                 metadata.setNamespace( namespace );
466                 metadata.setRepositoryId( repoId );
467                 metadata.setWhenGathered( new Date() );
468                 metadata.setFileLastModified( file.lastModified() );
469                 ChecksummedFile checksummedFile = new ChecksummedFile( file );
470                 try
471                 {
472                     metadata.setMd5( checksummedFile.calculateChecksum( ChecksumAlgorithm.MD5 ) );
473                 }
474                 catch ( IOException e )
475                 {
476                     log.error( "Unable to checksum file " + file + ": " + e.getMessage() );
477                 }
478                 try
479                 {
480                     metadata.setSha1( checksummedFile.calculateChecksum( ChecksumAlgorithm.SHA1 ) );
481                 }
482                 catch ( IOException e )
483                 {
484                     log.error( "Unable to checksum file " + file + ": " + e.getMessage() );
485                 }
486                 metadata.setSize( file.length() );
487
488                 // TODO: very crude, migrate the functionality from the repository-layer here
489                 if ( VersionUtil.isGenericSnapshot( projectVersion ) )
490                 {
491                     String mainVersion =
492                         projectVersion.substring( 0, projectVersion.length() - 8 ); // 8 is length of "SNAPSHOT"
493                     System.out.println( file.getName() + " " + mainVersion );
494                     Matcher m = Pattern.compile( projectId + "-" + mainVersion + "([0-9]{8}.[0-9]{6}-[0-9]+).*" ).matcher(
495                         file.getName() );
496                     m.matches();
497                     String version = mainVersion + m.group( 1 );
498
499                     metadata.setVersion( version );
500                 }
501                 else
502                 {
503                     metadata.setVersion( projectVersion );
504                 }
505                 artifacts.add( metadata );
506             }
507         }
508         return artifacts;
509     }
510
511     private boolean isProject( File dir, Filter<String> filter )
512     {
513         // scan directories for a valid project version subdirectory, meaning this must be a project directory
514         File[] files = dir.listFiles( new DirectoryFilter( filter ) );
515         if ( files != null )
516         {
517             for ( File file : files )
518             {
519                 if ( isProjectVersion( file ) )
520                 {
521                     return true;
522                 }
523             }
524         }
525
526         // if a metadata file is present, check if this is the "artifactId" directory, marking it as a project
527         MavenRepositoryMetadata metadata = readMetadata( dir );
528         if ( metadata != null && dir.getName().equals( metadata.getArtifactId() ) )
529         {
530             return true;
531         }
532
533         return false;
534     }
535
536     private boolean isProjectVersion( File dir )
537     {
538         final String artifactId = dir.getParentFile().getName();
539         final String projectVersion = dir.getName();
540
541         // check if there is a POM artifact file to ensure it is a version directory
542         File[] files;
543         if ( VersionUtil.isSnapshot( projectVersion ) )
544         {
545             files = dir.listFiles( new FilenameFilter()
546             {
547                 public boolean accept( File dir, String name )
548                 {
549                     if ( name.startsWith( artifactId + "-" ) && name.endsWith( ".pom" ) )
550                     {
551                         String v = name.substring( artifactId.length() + 1, name.length() - 4 );
552                         v = VersionUtil.getBaseVersion( v );
553                         if ( v.equals( projectVersion ) )
554                         {
555                             return true;
556                         }
557                     }
558                     return false;
559                 }
560             } );
561         }
562         else
563         {
564             final String pomFile = artifactId + "-" + projectVersion + ".pom";
565             files = dir.listFiles( new FilenameFilter()
566             {
567                 public boolean accept( File dir, String name )
568                 {
569                     return pomFile.equals( name );
570                 }
571             } );
572         }
573         if ( files != null && files.length > 0 )
574         {
575             return true;
576         }
577
578         // if a metadata file is present, check if this is the "version" directory, marking it as a project version
579         MavenRepositoryMetadata metadata = readMetadata( dir );
580         if ( metadata != null && projectVersion.equals( metadata.getVersion() ) )
581         {
582             return true;
583         }
584
585         return false;
586     }
587
588     private MavenRepositoryMetadata readMetadata( File directory )
589     {
590         MavenRepositoryMetadata metadata = null;
591         File metadataFile = new File( directory, METADATA_FILENAME );
592         if ( metadataFile.exists() )
593         {
594             try
595             {
596                 metadata = MavenRepositoryMetadataReader.read( metadataFile );
597             }
598             catch ( XMLException e )
599             {
600                 // ignore missing or invalid metadata
601             }
602         }
603         return metadata;
604     }
605
606     public void setConfiguration( ArchivaConfiguration configuration )
607     {
608         this.archivaConfiguration = configuration;
609     }
610
611     private static class DirectoryFilter
612         implements FilenameFilter
613     {
614         private final Filter<String> filter;
615
616         public DirectoryFilter( Filter<String> filter )
617         {
618             this.filter = filter;
619         }
620
621         public boolean accept( File dir, String name )
622         {
623             if ( !filter.accept( name ) )
624             {
625                 return false;
626             }
627             else if ( name.startsWith( "." ) )
628             {
629                 return false;
630             }
631             else if ( !new File( dir, name ).isDirectory() )
632             {
633                 return false;
634             }
635             return true;
636         }
637     }
638
639     private class ArtifactDirectoryFilter
640         implements FilenameFilter
641     {
642         private final Filter<String> filter;
643
644         public ArtifactDirectoryFilter( Filter<String> filter )
645         {
646             this.filter = filter;
647         }
648
649         public boolean accept( File dir, String name )
650         {
651             // TODO compare to logic in maven-repository-layer
652             if ( !filter.accept( name ) )
653             {
654                 return false;
655             }
656             else if ( name.startsWith( "." ) )
657             {
658                 return false;
659             }
660             else if ( name.endsWith( ".md5" ) || name.endsWith( ".sha1" ) || name.endsWith( ".asc" ) )
661             {
662                 return false;
663             }
664             else if ( name.equals( METADATA_FILENAME ) )
665             {
666                 return false;
667             }
668             else if ( new File( dir, name ).isDirectory() )
669             {
670                 return false;
671             }
672             return true;
673         }
674     }
675 }