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