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