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