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