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