]> source.dussan.org Git - archiva.git/blob
791c8806c2d878560fe0af415ed075ab9ca02938
[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.admin.model.RepositoryAdminException;
23 import org.apache.archiva.admin.model.beans.ManagedRepository;
24 import org.apache.archiva.admin.model.beans.NetworkProxy;
25 import org.apache.archiva.admin.model.beans.ProxyConnector;
26 import org.apache.archiva.admin.model.beans.RemoteRepository;
27 import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin;
28 import org.apache.archiva.admin.model.networkproxy.NetworkProxyAdmin;
29 import org.apache.archiva.admin.model.proxyconnector.ProxyConnectorAdmin;
30 import org.apache.archiva.admin.model.remote.RemoteRepositoryAdmin;
31 import org.apache.archiva.checksum.ChecksumAlgorithm;
32 import org.apache.archiva.checksum.ChecksummedFile;
33 import org.apache.archiva.common.utils.VersionUtil;
34 import org.apache.archiva.maven2.metadata.MavenMetadataReader;
35 import org.apache.archiva.metadata.model.ArtifactMetadata;
36 import org.apache.archiva.metadata.model.ProjectMetadata;
37 import org.apache.archiva.metadata.model.ProjectVersionMetadata;
38 import org.apache.archiva.metadata.repository.filter.Filter;
39 import org.apache.archiva.metadata.repository.storage.ReadMetadataRequest;
40 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
41 import org.apache.archiva.metadata.repository.storage.RepositoryStorage;
42 import org.apache.archiva.metadata.repository.storage.RepositoryStorageMetadataInvalidException;
43 import org.apache.archiva.metadata.repository.storage.RepositoryStorageMetadataNotFoundException;
44 import org.apache.archiva.metadata.repository.storage.RepositoryStorageRuntimeException;
45 import org.apache.archiva.model.ArchivaRepositoryMetadata;
46 import org.apache.archiva.model.ArtifactReference;
47 import org.apache.archiva.model.SnapshotVersion;
48 import org.apache.archiva.policies.ProxyDownloadException;
49 import org.apache.archiva.proxy.common.WagonFactory;
50 import org.apache.archiva.proxy.model.RepositoryProxyConnectors;
51 import org.apache.archiva.reports.RepositoryProblemFacet;
52 import org.apache.archiva.repository.ManagedRepositoryContent;
53 import org.apache.archiva.xml.XMLException;
54 import org.apache.commons.lang.StringUtils;
55 import org.apache.maven.model.CiManagement;
56 import org.apache.maven.model.Dependency;
57 import org.apache.maven.model.DistributionManagement;
58 import org.apache.maven.model.IssueManagement;
59 import org.apache.maven.model.License;
60 import org.apache.maven.model.MailingList;
61 import org.apache.maven.model.Model;
62 import org.apache.maven.model.Organization;
63 import org.apache.maven.model.Relocation;
64 import org.apache.maven.model.Scm;
65 import org.apache.maven.model.building.DefaultModelBuilderFactory;
66 import org.apache.maven.model.building.DefaultModelBuildingRequest;
67 import org.apache.maven.model.building.ModelBuilder;
68 import org.apache.maven.model.building.ModelBuildingException;
69 import org.apache.maven.model.building.ModelBuildingRequest;
70 import org.apache.maven.model.building.ModelProblem;
71 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
72 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
73 import org.slf4j.Logger;
74 import org.slf4j.LoggerFactory;
75 import org.springframework.stereotype.Service;
76
77 import javax.annotation.PostConstruct;
78 import javax.inject.Inject;
79 import javax.inject.Named;
80 import java.io.File;
81 import java.io.FileNotFoundException;
82 import java.io.FileReader;
83 import java.io.FilenameFilter;
84 import java.io.IOException;
85 import java.util.ArrayList;
86 import java.util.Arrays;
87 import java.util.Collection;
88 import java.util.Collections;
89 import java.util.Date;
90 import java.util.HashMap;
91 import java.util.List;
92 import java.util.Map;
93
94 /**
95  * Maven 2 repository format storage implementation. This class currently takes parameters to indicate the repository to
96  * deal with rather than being instantiated per-repository.
97  * FIXME: instantiate one per repository and allocate permanently from a factory (which can be obtained within the session).
98  * TODO: finish Maven 1 implementation to prove this API
99  * <p/>
100  * The session is passed in as an argument to obtain any necessary resources, rather than the class being instantiated
101  * within the session in the context of a single managed repository's resolution needs.
102  * <p/>
103  */
104 @Service ( "repositoryStorage#maven2" )
105 public class Maven2RepositoryStorage
106     implements RepositoryStorage
107 {
108     /**
109      *
110      */
111     private ModelBuilder builder;
112
113     /**
114      *
115      */
116     @Inject
117     private RemoteRepositoryAdmin remoteRepositoryAdmin;
118
119     @Inject
120     private ManagedRepositoryAdmin managedRepositoryAdmin;
121
122     @Inject
123     private ProxyConnectorAdmin proxyConnectorAdmin;
124
125     @Inject
126     private NetworkProxyAdmin networkProxyAdmin;
127
128     /**
129      *
130      */
131     @Inject
132     @Named ( value = "repositoryPathTranslator#maven2" )
133     private RepositoryPathTranslator pathTranslator;
134
135     @Inject
136     private WagonFactory wagonFactory;
137
138     @Inject
139     @Named ( value = "repositoryProxyConnectors#default" )
140     private RepositoryProxyConnectors connectors;
141
142     private static final Logger log = LoggerFactory.getLogger( Maven2RepositoryStorage.class );
143
144     private static final String METADATA_FILENAME_START = "maven-metadata";
145
146     private static final String METADATA_FILENAME = METADATA_FILENAME_START + ".xml";
147
148     private static final MavenXpp3Reader MAVEN_XPP_3_READER = new MavenXpp3Reader();
149
150
151     @PostConstruct
152     public void initialize()
153     {
154         DefaultModelBuilderFactory defaultModelBuilderFactory = new DefaultModelBuilderFactory();
155         builder = defaultModelBuilderFactory.newInstance();
156
157     }
158
159     public ProjectMetadata readProjectMetadata( String repoId, String namespace, String projectId )
160     {
161         // TODO: could natively implement the "shared model" concept from the browse action to avoid needing it there?
162         return null;
163     }
164
165     public ProjectVersionMetadata readProjectVersionMetadata( ReadMetadataRequest readMetadataRequest )
166         throws RepositoryStorageMetadataNotFoundException, RepositoryStorageMetadataInvalidException,
167         RepositoryStorageRuntimeException
168     {
169         try
170         {
171             ManagedRepository managedRepository =
172                 managedRepositoryAdmin.getManagedRepository( readMetadataRequest.getRepositoryId() );
173
174             String artifactVersion = readMetadataRequest.getProjectVersion();
175             if ( VersionUtil.isSnapshot(
176                 readMetadataRequest.getProjectVersion() ) ) // skygo trying to improve speed by honoring managed configuration MRM-1658
177             {
178                 if ( managedRepository.isReleases() && !managedRepository.isSnapshots() )
179                 {
180                     throw new RepositoryStorageRuntimeException( "lookforsnaponreleaseonly",
181                                                                  "managed repo is configured for release only" );
182                 }
183             }
184             else
185             {
186                 if ( !managedRepository.isReleases() && managedRepository.isSnapshots() )
187                 {
188                     throw new RepositoryStorageRuntimeException( "lookforsreleaseonsneponly",
189                                                                  "managed repo is configured for snapshot only" );
190                 }
191             }
192             File basedir = new File( managedRepository.getLocation() );
193             if ( VersionUtil.isSnapshot( readMetadataRequest.getProjectVersion() ) )
194             {
195                 File metadataFile = pathTranslator.toFile( basedir, readMetadataRequest.getNamespace(),
196                                                            readMetadataRequest.getProjectId(),
197                                                            readMetadataRequest.getProjectVersion(), METADATA_FILENAME );
198                 try
199                 {
200                     ArchivaRepositoryMetadata metadata = MavenMetadataReader.read( metadataFile );
201
202                     // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
203                     SnapshotVersion snapshotVersion = metadata.getSnapshotVersion();
204                     if ( snapshotVersion != null )
205                     {
206                         artifactVersion =
207                             artifactVersion.substring( 0, artifactVersion.length() - 8 ); // remove SNAPSHOT from end
208                         artifactVersion =
209                             artifactVersion + snapshotVersion.getTimestamp() + "-" + snapshotVersion.getBuildNumber();
210                     }
211                 }
212                 catch ( XMLException e )
213                 {
214                     // unable to parse metadata - log it, and continue with the version as the original SNAPSHOT version
215                     log.warn( "Invalid metadata: " + metadataFile + " - " + e.getMessage() );
216                 }
217             }
218
219             // TODO: won't work well with some other layouts, might need to convert artifact parts to ID by path translator
220             String id = readMetadataRequest.getProjectId() + "-" + artifactVersion + ".pom";
221             File file =
222                 pathTranslator.toFile( basedir, readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(),
223                                        readMetadataRequest.getProjectVersion(), id );
224
225             if ( !file.exists() )
226             {
227                 // metadata could not be resolved
228                 throw new RepositoryStorageMetadataNotFoundException(
229                     "The artifact's POM file '" + file.getAbsolutePath() + "' was missing" );
230             }
231
232             // TODO: this is a workaround until we can properly resolve using proxies as well - this doesn't cache
233             //       anything locally!
234             List<RemoteRepository> remoteRepositories = new ArrayList<RemoteRepository>();
235             Map<String, NetworkProxy> networkProxies = new HashMap<String, NetworkProxy>();
236
237             Map<String, List<ProxyConnector>> proxyConnectorsMap = proxyConnectorAdmin.getProxyConnectorAsMap();
238             List<ProxyConnector> proxyConnectors = proxyConnectorsMap.get( readMetadataRequest.getRepositoryId() );
239             if ( proxyConnectors != null )
240             {
241                 for ( ProxyConnector proxyConnector : proxyConnectors )
242                 {
243                     RemoteRepository remoteRepoConfig =
244                         remoteRepositoryAdmin.getRemoteRepository( proxyConnector.getTargetRepoId() );
245
246                     if ( remoteRepoConfig != null )
247                     {
248                         remoteRepositories.add( remoteRepoConfig );
249
250                         NetworkProxy networkProxyConfig =
251                             networkProxyAdmin.getNetworkProxy( proxyConnector.getProxyId() );
252
253                         if ( networkProxyConfig != null )
254                         {
255                             // key/value: remote repo ID/proxy info
256                             networkProxies.put( proxyConnector.getTargetRepoId(), networkProxyConfig );
257                         }
258                     }
259                 }
260             }
261
262             ModelBuildingRequest req =
263                 new DefaultModelBuildingRequest().setProcessPlugins( false ).setPomFile( file ).setTwoPhaseBuilding(
264                     false ).setValidationLevel( ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL );
265
266             //MRM-1607. olamy this will resolve jdk profiles on the current running archiva jvm
267             req.setSystemProperties( System.getProperties() );
268
269             // MRM-1411
270             req.setModelResolver(
271                 new RepositoryModelResolver( managedRepository, pathTranslator, wagonFactory, remoteRepositories,
272                                              networkProxies, managedRepository ) );
273
274             Model model;
275             try
276             {
277                 model = builder.build( req ).getEffectiveModel();
278             }
279             catch ( ModelBuildingException e )
280             {
281                 String msg = "The artifact's POM file '" + file + "' was invalid: " + e.getMessage();
282
283                 List<ModelProblem> modelProblems = e.getProblems();
284                 for ( ModelProblem problem : modelProblems )
285                 {
286                     // MRM-1411, related to MRM-1335
287                     // this means that the problem was that the parent wasn't resolved!
288                     // olamy really hackhish but fail with java profile so use error message
289                     // || ( StringUtils.startsWith( problem.getMessage(), "Failed to determine Java version for profile" ) )
290                     // but setTwoPhaseBuilding(true) fix that
291                     if ( ( problem.getException() instanceof FileNotFoundException && e.getModelId() != null &&
292                         !e.getModelId().equals( problem.getModelId() ) ) )
293                     {
294                         log.warn( "The artifact's parent POM file '" + file + "' cannot be resolved. " +
295                                       "Using defaults for project version metadata.." );
296
297                         ProjectVersionMetadata metadata = new ProjectVersionMetadata();
298                         metadata.setId( readMetadataRequest.getProjectVersion() );
299
300                         MavenProjectFacet facet = new MavenProjectFacet();
301                         facet.setGroupId( readMetadataRequest.getNamespace() );
302                         facet.setArtifactId( readMetadataRequest.getProjectId() );
303                         facet.setPackaging( "jar" );
304                         metadata.addFacet( facet );
305
306                         String errMsg =
307                             "Error in resolving artifact's parent POM file. " + ( problem.getException() == null
308                                 ? problem.getMessage()
309                                 : problem.getException().getMessage() );
310                         RepositoryProblemFacet repoProblemFacet = new RepositoryProblemFacet();
311                         repoProblemFacet.setRepositoryId( readMetadataRequest.getRepositoryId() );
312                         repoProblemFacet.setId( readMetadataRequest.getRepositoryId() );
313                         repoProblemFacet.setMessage( errMsg );
314                         repoProblemFacet.setProblem( errMsg );
315                         repoProblemFacet.setProject( readMetadataRequest.getProjectId() );
316                         repoProblemFacet.setVersion( readMetadataRequest.getProjectVersion() );
317                         repoProblemFacet.setNamespace( readMetadataRequest.getNamespace() );
318
319                         metadata.addFacet( repoProblemFacet );
320
321                         return metadata;
322                     }
323                 }
324
325                 throw new RepositoryStorageMetadataInvalidException( "invalid-pom", msg, e );
326             }
327
328             // Check if the POM is in the correct location
329             boolean correctGroupId = readMetadataRequest.getNamespace().equals( model.getGroupId() );
330             boolean correctArtifactId = readMetadataRequest.getProjectId().equals( model.getArtifactId() );
331             boolean correctVersion = readMetadataRequest.getProjectVersion().equals( model.getVersion() );
332             if ( !correctGroupId || !correctArtifactId || !correctVersion )
333             {
334                 StringBuilder message = new StringBuilder( "Incorrect POM coordinates in '" + file + "':" );
335                 if ( !correctGroupId )
336                 {
337                     message.append( "\nIncorrect group ID: " ).append( model.getGroupId() );
338                 }
339                 if ( !correctArtifactId )
340                 {
341                     message.append( "\nIncorrect artifact ID: " ).append( model.getArtifactId() );
342                 }
343                 if ( !correctVersion )
344                 {
345                     message.append( "\nIncorrect version: " ).append( model.getVersion() );
346                 }
347
348                 throw new RepositoryStorageMetadataInvalidException( "mislocated-pom", message.toString() );
349             }
350
351             ProjectVersionMetadata metadata = new ProjectVersionMetadata();
352             metadata.setCiManagement( convertCiManagement( model.getCiManagement() ) );
353             metadata.setDescription( model.getDescription() );
354             metadata.setId( readMetadataRequest.getProjectVersion() );
355             metadata.setIssueManagement( convertIssueManagement( model.getIssueManagement() ) );
356             metadata.setLicenses( convertLicenses( model.getLicenses() ) );
357             metadata.setMailingLists( convertMailingLists( model.getMailingLists() ) );
358             metadata.setDependencies( convertDependencies( model.getDependencies() ) );
359             metadata.setName( model.getName() );
360             metadata.setOrganization( convertOrganization( model.getOrganization() ) );
361             metadata.setScm( convertScm( model.getScm() ) );
362             metadata.setUrl( model.getUrl() );
363
364             MavenProjectFacet facet = new MavenProjectFacet();
365             facet.setGroupId( model.getGroupId() != null ? model.getGroupId() : model.getParent().getGroupId() );
366             facet.setArtifactId( model.getArtifactId() );
367             facet.setPackaging( model.getPackaging() );
368             if ( model.getParent() != null )
369             {
370                 MavenProjectParent parent = new MavenProjectParent();
371                 parent.setGroupId( model.getParent().getGroupId() );
372                 parent.setArtifactId( model.getParent().getArtifactId() );
373                 parent.setVersion( model.getParent().getVersion() );
374                 facet.setParent( parent );
375             }
376             metadata.addFacet( facet );
377
378             return metadata;
379         }
380         catch ( RepositoryAdminException e )
381         {
382             throw new RepositoryStorageRuntimeException( "repo-admin", e.getMessage() );
383         }
384     }
385
386     public void setWagonFactory( WagonFactory wagonFactory )
387     {
388         this.wagonFactory = wagonFactory;
389     }
390
391     private List<org.apache.archiva.metadata.model.Dependency> convertDependencies( List<Dependency> dependencies )
392     {
393         List<org.apache.archiva.metadata.model.Dependency> l =
394             new ArrayList<org.apache.archiva.metadata.model.Dependency>();
395         for ( Dependency dependency : dependencies )
396         {
397             org.apache.archiva.metadata.model.Dependency newDependency =
398                 new org.apache.archiva.metadata.model.Dependency();
399             newDependency.setArtifactId( dependency.getArtifactId() );
400             newDependency.setClassifier( dependency.getClassifier() );
401             newDependency.setGroupId( dependency.getGroupId() );
402             newDependency.setOptional( dependency.isOptional() );
403             newDependency.setScope( dependency.getScope() );
404             newDependency.setSystemPath( dependency.getSystemPath() );
405             newDependency.setType( dependency.getType() );
406             newDependency.setVersion( dependency.getVersion() );
407             l.add( newDependency );
408         }
409         return l;
410     }
411
412     private org.apache.archiva.metadata.model.Scm convertScm( Scm scm )
413     {
414         org.apache.archiva.metadata.model.Scm newScm = null;
415         if ( scm != null )
416         {
417             newScm = new org.apache.archiva.metadata.model.Scm();
418             newScm.setConnection( scm.getConnection() );
419             newScm.setDeveloperConnection( scm.getDeveloperConnection() );
420             newScm.setUrl( scm.getUrl() );
421         }
422         return newScm;
423     }
424
425     private org.apache.archiva.metadata.model.Organization convertOrganization( Organization organization )
426     {
427         org.apache.archiva.metadata.model.Organization org = null;
428         if ( organization != null )
429         {
430             org = new org.apache.archiva.metadata.model.Organization();
431             org.setName( organization.getName() );
432             org.setUrl( organization.getUrl() );
433         }
434         return org;
435     }
436
437     private List<org.apache.archiva.metadata.model.License> convertLicenses( List<License> licenses )
438     {
439         List<org.apache.archiva.metadata.model.License> l = new ArrayList<org.apache.archiva.metadata.model.License>();
440         for ( License license : licenses )
441         {
442             org.apache.archiva.metadata.model.License newLicense = new org.apache.archiva.metadata.model.License();
443             newLicense.setName( license.getName() );
444             newLicense.setUrl( license.getUrl() );
445             l.add( newLicense );
446         }
447         return l;
448     }
449
450     private List<org.apache.archiva.metadata.model.MailingList> convertMailingLists( List<MailingList> mailingLists )
451     {
452         List<org.apache.archiva.metadata.model.MailingList> l =
453             new ArrayList<org.apache.archiva.metadata.model.MailingList>();
454         for ( MailingList mailingList : mailingLists )
455         {
456             org.apache.archiva.metadata.model.MailingList newMailingList =
457                 new org.apache.archiva.metadata.model.MailingList();
458             newMailingList.setName( mailingList.getName() );
459             newMailingList.setMainArchiveUrl( mailingList.getArchive() );
460             newMailingList.setPostAddress( mailingList.getPost() );
461             newMailingList.setSubscribeAddress( mailingList.getSubscribe() );
462             newMailingList.setUnsubscribeAddress( mailingList.getUnsubscribe() );
463             newMailingList.setOtherArchives( mailingList.getOtherArchives() );
464             l.add( newMailingList );
465         }
466         return l;
467     }
468
469     private org.apache.archiva.metadata.model.IssueManagement convertIssueManagement( IssueManagement issueManagement )
470     {
471         org.apache.archiva.metadata.model.IssueManagement im = null;
472         if ( issueManagement != null )
473         {
474             im = new org.apache.archiva.metadata.model.IssueManagement();
475             im.setSystem( issueManagement.getSystem() );
476             im.setUrl( issueManagement.getUrl() );
477         }
478         return im;
479     }
480
481     private org.apache.archiva.metadata.model.CiManagement convertCiManagement( CiManagement ciManagement )
482     {
483         org.apache.archiva.metadata.model.CiManagement ci = null;
484         if ( ciManagement != null )
485         {
486             ci = new org.apache.archiva.metadata.model.CiManagement();
487             ci.setSystem( ciManagement.getSystem() );
488             ci.setUrl( ciManagement.getUrl() );
489         }
490         return ci;
491     }
492
493     public Collection<String> listRootNamespaces( String repoId, Filter<String> filter )
494         throws RepositoryStorageRuntimeException
495     {
496         File dir = getRepositoryBasedir( repoId );
497
498         return getSortedFiles( dir, filter );
499     }
500
501     private static Collection<String> getSortedFiles( File dir, Filter<String> filter )
502     {
503         List<String> fileNames;
504         String[] files = dir.list( new DirectoryFilter( filter ) );
505         if ( files != null )
506         {
507             fileNames = new ArrayList<String>( Arrays.asList( files ) );
508             Collections.sort( fileNames );
509         }
510         else
511         {
512             fileNames = Collections.emptyList();
513         }
514         return fileNames;
515     }
516
517     private File getRepositoryBasedir( String repoId )
518         throws RepositoryStorageRuntimeException
519     {
520         try
521         {
522             ManagedRepository repositoryConfiguration = managedRepositoryAdmin.getManagedRepository( repoId );
523
524             return new File( repositoryConfiguration.getLocation() );
525         }
526         catch ( RepositoryAdminException e )
527         {
528             throw new RepositoryStorageRuntimeException( "repo-admin", e.getMessage() );
529         }
530     }
531
532     public Collection<String> listNamespaces( String repoId, String namespace, Filter<String> filter )
533         throws RepositoryStorageRuntimeException
534     {
535         File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace );
536
537         // scan all the directories which are potential namespaces. Any directories known to be projects are excluded
538         List<String> namespaces = new ArrayList<String>();
539         File[] files = dir.listFiles( new DirectoryFilter( filter ) );
540         if ( files != null )
541         {
542             for ( File file : files )
543             {
544                 if ( !isProject( file, filter ) )
545                 {
546                     namespaces.add( file.getName() );
547                 }
548             }
549         }
550         Collections.sort( namespaces );
551         return namespaces;
552     }
553
554     public Collection<String> listProjects( String repoId, String namespace, Filter<String> filter )
555         throws RepositoryStorageRuntimeException
556     {
557         File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace );
558
559         // scan all directories in the namespace, and only include those that are known to be projects
560         List<String> projects = new ArrayList<String>();
561         File[] files = dir.listFiles( new DirectoryFilter( filter ) );
562         if ( files != null )
563         {
564             for ( File file : files )
565             {
566                 if ( isProject( file, filter ) )
567                 {
568                     projects.add( file.getName() );
569                 }
570             }
571         }
572         Collections.sort( projects );
573         return projects;
574     }
575
576     public Collection<String> listProjectVersions( String repoId, String namespace, String projectId,
577                                                    Filter<String> filter )
578         throws RepositoryStorageRuntimeException
579     {
580         File dir = pathTranslator.toFile( getRepositoryBasedir( repoId ), namespace, projectId );
581
582         // all directories in a project directory can be considered a version
583         return getSortedFiles( dir, filter );
584     }
585
586     public Collection<ArtifactMetadata> readArtifactsMetadata( ReadMetadataRequest readMetadataRequest )
587         throws RepositoryStorageRuntimeException
588     {
589         File dir = pathTranslator.toFile( getRepositoryBasedir( readMetadataRequest.getRepositoryId() ),
590                                           readMetadataRequest.getNamespace(), readMetadataRequest.getProjectId(),
591                                           readMetadataRequest.getProjectVersion() );
592
593         // all files that are not metadata and not a checksum / signature are considered artifacts
594         File[] files = dir.listFiles( new ArtifactDirectoryFilter( readMetadataRequest.getFilter() ) );
595
596         List<ArtifactMetadata> artifacts = new ArrayList<ArtifactMetadata>();
597         if ( files != null )
598         {
599             for ( File file : files )
600             {
601                 ArtifactMetadata metadata =
602                     getArtifactFromFile( readMetadataRequest.getRepositoryId(), readMetadataRequest.getNamespace(),
603                                          readMetadataRequest.getProjectId(), readMetadataRequest.getProjectVersion(),
604                                          file );
605                 artifacts.add( metadata );
606             }
607         }
608         return artifacts;
609     }
610
611     public ArtifactMetadata readArtifactMetadataFromPath( String repoId, String path )
612         throws RepositoryStorageRuntimeException
613     {
614         ArtifactMetadata metadata = pathTranslator.getArtifactForPath( repoId, path );
615
616         populateArtifactMetadataFromFile( metadata, new File( getRepositoryBasedir( repoId ), path ) );
617
618         return metadata;
619     }
620
621     private ArtifactMetadata getArtifactFromFile( String repoId, String namespace, String projectId,
622                                                   String projectVersion, File file )
623     {
624         ArtifactMetadata metadata =
625             pathTranslator.getArtifactFromId( repoId, namespace, projectId, projectVersion, file.getName() );
626
627         populateArtifactMetadataFromFile( metadata, file );
628
629         return metadata;
630     }
631
632     public void applyServerSideRelocation( ManagedRepositoryContent managedRepository, ArtifactReference artifact )
633         throws ProxyDownloadException
634     {
635         if ( "pom".equals( artifact.getType() ) )
636         {
637             return;
638         }
639
640         // Build the artifact POM reference
641         ArtifactReference pomReference = new ArtifactReference();
642         pomReference.setGroupId( artifact.getGroupId() );
643         pomReference.setArtifactId( artifact.getArtifactId() );
644         pomReference.setVersion( artifact.getVersion() );
645         pomReference.setType( "pom" );
646
647         // Get the artifact POM from proxied repositories if needed
648         connectors.fetchFromProxies( managedRepository, pomReference );
649
650         // Open and read the POM from the managed repo
651         File pom = managedRepository.toFile( pomReference );
652
653         if ( !pom.exists() )
654         {
655             return;
656         }
657
658         try
659         {
660             // MavenXpp3Reader leaves the file open, so we need to close it ourselves.
661             FileReader reader = new FileReader( pom );
662             Model model = null;
663             try
664             {
665                 model = MAVEN_XPP_3_READER.read( reader );
666             }
667             finally
668             {
669                 if ( reader != null )
670                 {
671                     reader.close();
672                 }
673             }
674
675             DistributionManagement dist = model.getDistributionManagement();
676             if ( dist != null )
677             {
678                 Relocation relocation = dist.getRelocation();
679                 if ( relocation != null )
680                 {
681                     // artifact is relocated : update the repositoryPath
682                     if ( relocation.getGroupId() != null )
683                     {
684                         artifact.setGroupId( relocation.getGroupId() );
685                     }
686                     if ( relocation.getArtifactId() != null )
687                     {
688                         artifact.setArtifactId( relocation.getArtifactId() );
689                     }
690                     if ( relocation.getVersion() != null )
691                     {
692                         artifact.setVersion( relocation.getVersion() );
693                     }
694                 }
695             }
696         }
697         catch ( FileNotFoundException e )
698         {
699             // Artifact has no POM in repo : ignore
700         }
701         catch ( IOException e )
702         {
703             // Unable to read POM : ignore.
704         }
705         catch ( XmlPullParserException e )
706         {
707             // Invalid POM : ignore
708         }
709     }
710
711     //-----------------------------
712     // internal
713     //-----------------------------
714     private static void populateArtifactMetadataFromFile( ArtifactMetadata metadata, File file )
715     {
716         metadata.setWhenGathered( new Date() );
717         metadata.setFileLastModified( file.lastModified() );
718         ChecksummedFile checksummedFile = new ChecksummedFile( file );
719         try
720         {
721             metadata.setMd5( checksummedFile.calculateChecksum( ChecksumAlgorithm.MD5 ) );
722         }
723         catch ( IOException e )
724         {
725             log.error( "Unable to checksum file {}: {},MD5", file, e.getMessage() );
726         }
727         try
728         {
729             metadata.setSha1( checksummedFile.calculateChecksum( ChecksumAlgorithm.SHA1 ) );
730         }
731         catch ( IOException e )
732         {
733             log.error( "Unable to checksum file {}: {},SHA1", file, e.getMessage() );
734         }
735         metadata.setSize( file.length() );
736     }
737
738     private boolean isProject( File dir, Filter<String> filter )
739     {
740         // scan directories for a valid project version subdirectory, meaning this must be a project directory
741         File[] files = dir.listFiles( new DirectoryFilter( filter ) );
742         if ( files != null )
743         {
744             for ( File file : files )
745             {
746                 if ( isProjectVersion( file ) )
747                 {
748                     return true;
749                 }
750             }
751         }
752
753         // if a metadata file is present, check if this is the "artifactId" directory, marking it as a project
754         ArchivaRepositoryMetadata metadata = readMetadata( dir );
755         if ( metadata != null && dir.getName().equals( metadata.getArtifactId() ) )
756         {
757             return true;
758         }
759
760         return false;
761     }
762
763     private boolean isProjectVersion( File dir )
764     {
765         final String artifactId = dir.getParentFile().getName();
766         final String projectVersion = dir.getName();
767
768         // check if there is a POM artifact file to ensure it is a version directory
769         File[] files;
770         if ( VersionUtil.isSnapshot( projectVersion ) )
771         {
772             files = dir.listFiles( new PomFilenameFilter( artifactId, projectVersion ) );
773         }
774         else
775         {
776             final String pomFile = artifactId + "-" + projectVersion + ".pom";
777             files = dir.listFiles( new PomFileFilter( pomFile ) );
778         }
779         if ( files != null && files.length > 0 )
780         {
781             return true;
782         }
783
784         // if a metadata file is present, check if this is the "version" directory, marking it as a project version
785         ArchivaRepositoryMetadata metadata = readMetadata( dir );
786         if ( metadata != null && projectVersion.equals( metadata.getVersion() ) )
787         {
788             return true;
789         }
790
791         return false;
792     }
793
794     private ArchivaRepositoryMetadata readMetadata( File directory )
795     {
796         ArchivaRepositoryMetadata metadata = null;
797         File metadataFile = new File( directory, METADATA_FILENAME );
798         if ( metadataFile.exists() )
799         {
800             try
801             {
802                 metadata = MavenMetadataReader.read( metadataFile );
803             }
804             catch ( XMLException e )
805             {
806                 // ignore missing or invalid metadata
807             }
808         }
809         return metadata;
810     }
811
812     private static class DirectoryFilter
813         implements FilenameFilter
814     {
815         private final Filter<String> filter;
816
817         public DirectoryFilter( Filter<String> filter )
818         {
819             this.filter = filter;
820         }
821
822         public boolean accept( File dir, String name )
823         {
824             if ( !filter.accept( name ) )
825             {
826                 return false;
827             }
828             else if ( name.startsWith( "." ) )
829             {
830                 return false;
831             }
832             else if ( !new File( dir, name ).isDirectory() )
833             {
834                 return false;
835             }
836             return true;
837         }
838     }
839
840     private class ArtifactDirectoryFilter
841         implements FilenameFilter
842     {
843         private final Filter<String> filter;
844
845         public ArtifactDirectoryFilter( Filter<String> filter )
846         {
847             this.filter = filter;
848         }
849
850         public boolean accept( File dir, String name )
851         {
852             // TODO compare to logic in maven-repository-layer
853             if ( !filter.accept( name ) )
854             {
855                 return false;
856             }
857             else if ( name.startsWith( "." ) )
858             {
859                 return false;
860             }
861             else if ( name.endsWith( ".md5" ) || name.endsWith( ".sha1" ) || name.endsWith( ".asc" ) )
862             {
863                 return false;
864             }
865             else if ( name.equals( METADATA_FILENAME ) )
866             {
867                 return false;
868             }
869             else if ( new File( dir, name ).isDirectory() )
870             {
871                 return false;
872             }
873             // some files from remote repositories can have name like maven-metadata-archiva-vm-all-public.xml
874             else if ( StringUtils.startsWith( name, METADATA_FILENAME_START ) && StringUtils.endsWith( name, ".xml" ) )
875             {
876                 return false;
877             }
878
879             return true;
880
881         }
882     }
883
884     private static class PomFilenameFilter
885         implements FilenameFilter
886     {
887
888         private final String artifactId, projectVersion;
889
890         private PomFilenameFilter( String artifactId, String projectVersion )
891         {
892             this.artifactId = artifactId;
893             this.projectVersion = projectVersion;
894         }
895
896         public boolean accept( File dir, String name )
897         {
898             if ( name.startsWith( artifactId + "-" ) && name.endsWith( ".pom" ) )
899             {
900                 String v = name.substring( artifactId.length() + 1, name.length() - 4 );
901                 v = VersionUtil.getBaseVersion( v );
902                 if ( v.equals( projectVersion ) )
903                 {
904                     return true;
905                 }
906             }
907             return false;
908         }
909     }
910
911     private static class PomFileFilter
912         implements FilenameFilter
913     {
914         private final String pomFile;
915
916         private PomFileFilter( String pomFile )
917         {
918             this.pomFile = pomFile;
919         }
920
921         public boolean accept( File dir, String name )
922         {
923             return pomFile.equals( name );
924         }
925     }
926 }