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