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