This project has retired. For details please refer to its Attic page.
Source code
001package 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
022import org.apache.archiva.common.utils.VersionUtil;
023import org.apache.archiva.maven2.metadata.MavenMetadataReader;
024import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
025import org.apache.archiva.model.ArchivaRepositoryMetadata;
026import org.apache.archiva.model.SnapshotVersion;
027import org.apache.archiva.proxy.maven.WagonFactory;
028import org.apache.archiva.proxy.maven.WagonFactoryException;
029import org.apache.archiva.proxy.maven.WagonFactoryRequest;
030import org.apache.archiva.proxy.model.NetworkProxy;
031import org.apache.archiva.repository.ManagedRepository;
032import org.apache.archiva.repository.RemoteRepository;
033import org.apache.archiva.repository.RepositoryCredentials;
034import org.apache.archiva.repository.maven2.MavenSystemManager;
035import org.apache.archiva.repository.storage.StorageAsset;
036import org.apache.archiva.xml.XMLException;
037import org.apache.commons.lang3.StringUtils;
038import org.apache.http.auth.UsernamePasswordCredentials;
039import org.apache.maven.model.Dependency;
040import org.apache.maven.model.Parent;
041import org.apache.maven.model.Repository;
042import org.apache.maven.model.building.FileModelSource;
043import org.apache.maven.model.building.ModelSource;
044import org.apache.maven.model.resolution.InvalidRepositoryException;
045import org.apache.maven.model.resolution.ModelResolver;
046import org.apache.maven.model.resolution.UnresolvableModelException;
047import org.apache.maven.wagon.ConnectionException;
048import org.apache.maven.wagon.ResourceDoesNotExistException;
049import org.apache.maven.wagon.TransferFailedException;
050import org.apache.maven.wagon.Wagon;
051import org.apache.maven.wagon.authentication.AuthenticationException;
052import org.apache.maven.wagon.authentication.AuthenticationInfo;
053import org.apache.maven.wagon.authorization.AuthorizationException;
054import org.apache.maven.wagon.proxy.ProxyInfo;
055import org.eclipse.aether.RepositorySystemSession;
056import org.eclipse.aether.artifact.Artifact;
057import org.eclipse.aether.artifact.DefaultArtifact;
058import org.eclipse.aether.impl.VersionRangeResolver;
059import org.eclipse.aether.resolution.VersionRangeRequest;
060import org.eclipse.aether.resolution.VersionRangeResolutionException;
061import org.eclipse.aether.resolution.VersionRangeResult;
062import org.slf4j.Logger;
063import org.slf4j.LoggerFactory;
064
065import java.io.IOException;
066import java.nio.file.Files;
067import java.nio.file.Path;
068import java.nio.file.Paths;
069import java.util.HashMap;
070import java.util.List;
071import java.util.Map;
072
073public class RepositoryModelResolver
074    implements ModelResolver
075{
076
077    private final Map<String, NetworkProxy> networkProxyMap = new HashMap<>();
078
079    private RepositorySystemSession session;
080    private VersionRangeResolver versionRangeResolver;
081
082    private StorageAsset basedir;
083
084    private RepositoryPathTranslator pathTranslator;
085
086    private WagonFactory wagonFactory;
087
088    private List<RemoteRepository> remoteRepositories;
089
090    private ManagedRepository targetRepository;
091
092    private static final Logger log = LoggerFactory.getLogger( RepositoryModelResolver.class );
093
094    private static final String METADATA_FILENAME = "maven-metadata.xml";
095
096    private MavenSystemManager mavenSystemManager;
097
098
099
100    private ManagedRepository managedRepository;
101
102    public RepositoryModelResolver(StorageAsset basedir, RepositoryPathTranslator pathTranslator )
103    {
104        this.basedir = basedir;
105
106        this.pathTranslator = pathTranslator;
107    }
108
109    public RepositoryModelResolver(ManagedRepository managedRepository, RepositoryPathTranslator pathTranslator,
110                                   WagonFactory wagonFactory, List<RemoteRepository> remoteRepositories,
111                                   Map<String, NetworkProxy> networkProxiesMap, ManagedRepository targetRepository,
112                                   MavenSystemManager mavenSystemManager)
113    {
114        this( managedRepository.getAsset(""), pathTranslator );
115
116        this.managedRepository = managedRepository;
117
118        this.wagonFactory = wagonFactory;
119
120        this.remoteRepositories = remoteRepositories;
121
122        this.networkProxyMap.clear();
123        this.networkProxyMap.putAll(networkProxiesMap);
124
125        this.targetRepository = targetRepository;
126
127        this.session = MavenSystemManager.newRepositorySystemSession( managedRepository.getAsset("").getFilePath().toString() );
128
129        this.versionRangeResolver = mavenSystemManager.getLocator().getService(VersionRangeResolver.class);
130
131        this.mavenSystemManager = mavenSystemManager;
132    }
133
134
135    @Override
136    public ModelSource resolveModel( String groupId, String artifactId, String version )
137        throws UnresolvableModelException
138    {
139        String filename = artifactId + "-" + version + ".pom";
140        // TODO: we need to convert 1.0-20091120.112233-1 type paths to baseVersion for the below call - add a test
141
142        StorageAsset model = pathTranslator.toFile( basedir, groupId, artifactId, version, filename );
143
144        if ( !model.exists() )
145        {
146            /**
147             *
148             */
149            // is a SNAPSHOT ? so we can try to find locally before asking remote repositories.
150            if ( StringUtils.contains( version, VersionUtil.SNAPSHOT ) )
151            {
152                Path localSnapshotModel = findTimeStampedSnapshotPom( groupId, artifactId, version, model.getParent().getFilePath() );
153                if ( localSnapshotModel != null )
154                {
155                    return new FileModelSource( localSnapshotModel.toFile() );
156                }
157
158            }
159
160            for ( RemoteRepository remoteRepository : remoteRepositories )
161            {
162                try
163                {
164                    boolean success = getModelFromProxy( remoteRepository, groupId, artifactId, version, filename );
165                    if ( success && model.exists() )
166                    {
167                        log.info( "Model '{}' successfully retrieved from remote repository '{}'",
168                                  model.getPath(), remoteRepository.getId() );
169                        break;
170                    }
171                }
172                catch ( ResourceDoesNotExistException e )
173                {
174                    log.info(
175                        "An exception was caught while attempting to retrieve model '{}' from remote repository '{}'.Reason:{}",
176                        model.getPath(), remoteRepository.getId(), e.getMessage() );
177                }
178                catch ( Exception e )
179                {
180                    log.warn(
181                        "An exception was caught while attempting to retrieve model '{}' from remote repository '{}'.Reason:{}",
182                        model.getPath(), remoteRepository.getId(), e.getMessage() );
183
184                    continue;
185                }
186            }
187        }
188
189        return new FileModelSource( model.getFilePath().toFile() );
190    }
191
192    public ModelSource resolveModel(Parent parent) throws UnresolvableModelException {
193        try {
194            Artifact artifact = new DefaultArtifact(parent.getGroupId(), parent.getArtifactId(), "", "pom", parent.getVersion());
195            VersionRangeRequest versionRangeRequest;
196            versionRangeRequest = new VersionRangeRequest(artifact, null, null);
197            VersionRangeResult versionRangeResult = this.versionRangeResolver.resolveVersionRange(this.session, versionRangeRequest);
198            if (versionRangeResult.getHighestVersion() == null) {
199                throw new UnresolvableModelException(String.format("No versions matched the requested parent version range '%s'", parent.getVersion()), parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
200            } else if (versionRangeResult.getVersionConstraint() != null && versionRangeResult.getVersionConstraint().getRange() != null && versionRangeResult.getVersionConstraint().getRange().getUpperBound() == null) {
201                throw new UnresolvableModelException(String.format("The requested parent version range '%s' does not specify an upper bound", parent.getVersion()), parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
202            } else {
203                parent.setVersion(versionRangeResult.getHighestVersion().toString());
204                return this.resolveModel(parent.getGroupId(), parent.getArtifactId(), parent.getVersion());
205            }
206        } catch ( VersionRangeResolutionException var5) {
207            throw new UnresolvableModelException(var5.getMessage(), parent.getGroupId(), parent.getArtifactId(), parent.getVersion(), var5);
208        }
209    }
210
211    public ModelSource resolveModel(Dependency dependency) throws UnresolvableModelException {
212        try {
213            Artifact artifact = new DefaultArtifact(dependency.getGroupId(), dependency.getArtifactId(), "", "pom", dependency.getVersion());
214            VersionRangeRequest versionRangeRequest = new VersionRangeRequest(artifact, null, null);
215            VersionRangeResult versionRangeResult = this.versionRangeResolver.resolveVersionRange(this.session, versionRangeRequest);
216            if (versionRangeResult.getHighestVersion() == null) {
217                throw new UnresolvableModelException(String.format("No versions matched the requested dependency version range '%s'", dependency.getVersion()), dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion());
218            } else if (versionRangeResult.getVersionConstraint() != null && versionRangeResult.getVersionConstraint().getRange() != null && versionRangeResult.getVersionConstraint().getRange().getUpperBound() == null) {
219                throw new UnresolvableModelException(String.format("The requested dependency version range '%s' does not specify an upper bound", dependency.getVersion()), dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion());
220            } else {
221                dependency.setVersion(versionRangeResult.getHighestVersion().toString());
222                return this.resolveModel(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion());
223            }
224        } catch (VersionRangeResolutionException var5) {
225            throw new UnresolvableModelException(var5.getMessage(), dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), var5);
226        }
227    }
228
229    protected Path findTimeStampedSnapshotPom( String groupId, String artifactId, String version,
230                                               Path parentDirectory )
231    {
232
233        // reading metadata if there
234        Path mavenMetadata = parentDirectory.resolve( METADATA_FILENAME );
235        if ( Files.exists(mavenMetadata) )
236        {
237            try
238            {
239                ArchivaRepositoryMetadata archivaRepositoryMetadata = MavenMetadataReader.read( mavenMetadata);
240                SnapshotVersion snapshotVersion = archivaRepositoryMetadata.getSnapshotVersion();
241                if ( snapshotVersion != null )
242                {
243                    String lastVersion = snapshotVersion.getTimestamp();
244                    int buildNumber = snapshotVersion.getBuildNumber();
245                    String snapshotPath =
246                        StringUtils.replaceChars( groupId, '.', '/' ) + '/' + artifactId + '/' + version + '/'
247                            + artifactId + '-' + StringUtils.remove( version, "-" + VersionUtil.SNAPSHOT ) + '-'
248                            + lastVersion + '-' + buildNumber + ".pom";
249
250                    log.debug( "use snapshot path {} for maven coordinate {}:{}:{}", snapshotPath, groupId, artifactId,
251                               version );
252
253                    StorageAsset model = basedir.resolve( snapshotPath );
254                    //model = pathTranslator.toFile( basedir, groupId, artifactId, lastVersion, filename );
255                    if ( model.exists() )
256                    {
257                        return model.getFilePath();
258                    }
259                }
260            }
261            catch (XMLException e )
262            {
263                log.warn( "fail to read {}, {}", mavenMetadata.toAbsolutePath(), e.getCause() );
264            }
265        }
266
267        return null;
268    }
269
270    @Override
271    public void addRepository( Repository repository )
272        throws InvalidRepositoryException
273    {
274        // we just ignore repositories outside of the current one for now
275        // TODO: it'd be nice to look them up from Archiva's set, but we want to do that by URL / mapping, not just the
276        //       ID since they will rarely match
277    }
278
279    @Override
280    public void addRepository( Repository repository, boolean b ) throws InvalidRepositoryException
281    {
282
283    }
284
285    @Override
286    public ModelResolver newCopy()
287    {
288        return new RepositoryModelResolver( managedRepository,  pathTranslator, wagonFactory, remoteRepositories, 
289                                            networkProxyMap, targetRepository, mavenSystemManager);
290    }
291
292    // FIXME: we need to do some refactoring, we cannot re-use the proxy components of archiva-proxy in maven2-repository
293    // because it's causing a cyclic dependency
294    private boolean getModelFromProxy( RemoteRepository remoteRepository, String groupId, String artifactId,
295                                       String version, String filename )
296        throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException, WagonFactoryException,
297        XMLException, IOException
298    {
299        boolean success = false;
300        Path tmpMd5 = null;
301        Path tmpSha1 = null;
302        Path tmpResource = null;
303        String artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename );
304        Path resource = targetRepository.getAsset("").getFilePath().resolve( artifactPath );
305
306        Path workingDirectory = createWorkingDirectory( targetRepository.getLocation().toString() );
307        try
308        {
309            Wagon wagon = null;
310            try
311            {
312                String protocol = getProtocol( remoteRepository.getLocation().toString() );
313                final NetworkProxy networkProxy = this.networkProxyMap.get( remoteRepository.getId() );
314
315                wagon = wagonFactory.getWagon(
316                    new WagonFactoryRequest( "wagon#" + protocol, remoteRepository.getExtraHeaders() ).networkProxy(
317                        networkProxy )
318                );
319
320                if ( wagon == null )
321                {
322                    throw new RuntimeException( "Unsupported remote repository protocol: " + protocol );
323                }
324
325                boolean connected = connectToRepository( wagon, remoteRepository );
326                if ( connected )
327                {
328                    tmpResource = workingDirectory.resolve( filename );
329
330                    if ( VersionUtil.isSnapshot( version ) )
331                    {
332                        // get the metadata first!
333                        Path tmpMetadataResource = workingDirectory.resolve( METADATA_FILENAME );
334
335                        String metadataPath =
336                            StringUtils.substringBeforeLast( artifactPath, "/" ) + "/" + METADATA_FILENAME;
337
338                        wagon.get( addParameters( metadataPath, remoteRepository ), tmpMetadataResource.toFile() );
339
340                        log.debug( "Successfully downloaded metadata." );
341
342                        ArchivaRepositoryMetadata metadata = MavenMetadataReader.read( tmpMetadataResource );
343
344                        // re-adjust to timestamp if present, otherwise retain the original -SNAPSHOT filename
345                        SnapshotVersion snapshotVersion = metadata.getSnapshotVersion();
346                        String timestampVersion = version;
347                        if ( snapshotVersion != null )
348                        {
349                            timestampVersion = timestampVersion.substring( 0, timestampVersion.length()
350                                - 8 ); // remove SNAPSHOT from end
351                            timestampVersion = timestampVersion + snapshotVersion.getTimestamp() + "-"
352                                + snapshotVersion.getBuildNumber();
353
354                            filename = artifactId + "-" + timestampVersion + ".pom";
355
356                            artifactPath = pathTranslator.toPath( groupId, artifactId, version, filename );
357
358                            log.debug( "New artifactPath :{}", artifactPath );
359                        }
360                    }
361
362                    log.info( "Retrieving {} from {}", artifactPath, remoteRepository.getName() );
363
364                    wagon.get( addParameters( artifactPath, remoteRepository ), tmpResource.toFile() );
365
366                    log.debug( "Downloaded successfully." );
367
368                    tmpSha1 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory,
369                                                ".sha1" );
370                    tmpMd5 = transferChecksum( wagon, remoteRepository, artifactPath, tmpResource, workingDirectory,
371                                               ".md5" );
372                }
373            }
374            finally
375            {
376                if ( wagon != null )
377                {
378                    try
379                    {
380                        wagon.disconnect();
381                    }
382                    catch ( ConnectionException e )
383                    {
384                        log.warn( "Unable to disconnect wagon.", e );
385                    }
386                }
387            }
388
389            if ( resource != null )
390            {
391                synchronized ( resource.toAbsolutePath().toString().intern() )
392                {
393                    Path directory = resource.getParent();
394                    moveFileIfExists( tmpMd5, directory );
395                    moveFileIfExists( tmpSha1, directory );
396                    moveFileIfExists( tmpResource, directory );
397                    success = true;
398                }
399            }
400        }
401        finally
402        {
403            org.apache.archiva.common.utils.FileUtils.deleteQuietly( workingDirectory );
404        }
405
406        // do we still need to execute the consumers?
407
408        return success;
409    }
410
411    /**
412     * Using wagon, connect to the remote repository.
413     *
414     * @param wagon the wagon instance to establish the connection on.
415     * @return true if the connection was successful. false if not connected.
416     */
417    private boolean connectToRepository( Wagon wagon, RemoteRepository remoteRepository )
418    {
419        boolean connected;
420
421        final NetworkProxy proxyConnector = this.networkProxyMap.get( remoteRepository.getId() );
422        ProxyInfo networkProxy = null;
423        if ( proxyConnector != null )
424        {
425            networkProxy = new ProxyInfo();
426            networkProxy.setType( proxyConnector.getProtocol() );
427            networkProxy.setHost( proxyConnector.getHost() );
428            networkProxy.setPort( proxyConnector.getPort() );
429            networkProxy.setUserName( proxyConnector.getUsername() );
430            networkProxy.setPassword( new String(proxyConnector.getPassword()) );
431
432            String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
433                + " to connect to remote repository " + remoteRepository.getLocation();
434            if ( networkProxy.getNonProxyHosts() != null )
435            {
436                msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
437            }
438
439            if ( StringUtils.isNotBlank( networkProxy.getUserName() ) )
440            {
441                msg += "; as user: " + networkProxy.getUserName();
442            }
443
444            log.debug( msg );
445        }
446
447        AuthenticationInfo authInfo = null;
448        RepositoryCredentials creds = remoteRepository.getLoginCredentials();
449        String username = "";
450        String password = "";
451        if (creds instanceof UsernamePasswordCredentials) {
452            UsernamePasswordCredentials c = (UsernamePasswordCredentials) creds;
453            username = c.getUserName();
454            password = c.getPassword();
455        }
456
457        if ( StringUtils.isNotBlank( username ) && StringUtils.isNotBlank( password ) )
458        {
459            log.debug( "Using username {} to connect to remote repository {}", username, remoteRepository.getLocation() );
460            authInfo = new AuthenticationInfo();
461            authInfo.setUserName( username );
462            authInfo.setPassword( password );
463        }
464
465        int timeoutInMilliseconds = ((int)remoteRepository.getTimeout().getSeconds())*1000;
466        // FIXME olamy having 2 config values
467        // Set timeout
468        wagon.setReadTimeout( timeoutInMilliseconds );
469        wagon.setTimeout( timeoutInMilliseconds );
470
471        try
472        {
473            org.apache.maven.wagon.repository.Repository wagonRepository =
474                new org.apache.maven.wagon.repository.Repository( remoteRepository.getId(), remoteRepository.getLocation().toString() );
475            if ( networkProxy != null )
476            {
477                wagon.connect( wagonRepository, authInfo, networkProxy );
478            }
479            else
480            {
481                wagon.connect( wagonRepository, authInfo );
482            }
483            connected = true;
484        }
485        catch ( ConnectionException | AuthenticationException e )
486        {
487            log.error( "Could not connect to {}:{} ", remoteRepository.getName(), e.getMessage() );
488            connected = false;
489        }
490
491        return connected;
492    }
493
494    /**
495     *
496     * @param wagon The wagon instance that should be connected.
497     * @param remoteRepository The repository from where the checksum file should be retrieved
498     * @param remotePath The remote path of the artifact (without extension)
499     * @param resource The local artifact (without extension)
500     * @param workingDir The working directory where the downloaded file should be placed to
501     * @param ext The extension of th checksum file
502     * @return The file where the data has been downloaded to.
503     * @throws AuthorizationException
504     * @throws TransferFailedException
505     * @throws ResourceDoesNotExistException
506     */
507    private Path transferChecksum( final Wagon wagon, final RemoteRepository remoteRepository,
508                                   final String remotePath, final Path resource,
509                                   final Path workingDir, final String ext )
510        throws AuthorizationException, TransferFailedException, ResourceDoesNotExistException
511    {
512        Path destFile = workingDir.resolve( resource.getFileName() + ext );
513        String remoteChecksumPath = remotePath + ext;
514
515        log.info( "Retrieving {} from {}", remoteChecksumPath, remoteRepository.getName() );
516
517        wagon.get( addParameters( remoteChecksumPath, remoteRepository ), destFile.toFile() );
518
519        log.debug( "Downloaded successfully." );
520
521        return destFile;
522    }
523
524    private String getProtocol( String url )
525    {
526        String protocol = StringUtils.substringBefore( url, ":" );
527
528        return protocol;
529    }
530
531    private Path createWorkingDirectory( String targetRepository )
532        throws IOException
533    {
534        return Files.createTempDirectory( "temp" );
535    }
536
537    private void moveFileIfExists( Path fileToMove, Path directory )
538    {
539        if ( fileToMove != null && Files.exists(fileToMove) )
540        {
541            Path newLocation = directory.resolve( fileToMove.getFileName() );
542            try {
543                Files.deleteIfExists(newLocation);
544            } catch (IOException e) {
545                throw new RuntimeException(
546                        "Unable to overwrite existing target file: " + newLocation.toAbsolutePath(), e );
547            }
548
549            try {
550                Files.createDirectories(newLocation.getParent());
551            } catch (IOException e) {
552                e.printStackTrace();
553            }
554            try {
555                Files.move(fileToMove, newLocation );
556            } catch (IOException e) {
557                try {
558                    Files.copy(fileToMove, newLocation);
559                } catch (IOException e1) {
560                    if (Files.exists(newLocation)) {
561                        log.error( "Tried to copy file {} to {} but file with this name already exists.",
562                                fileToMove.getFileName(), newLocation.toAbsolutePath() );
563                    } else {
564                        throw new RuntimeException(
565                                "Cannot copy tmp file " + fileToMove.toAbsolutePath() + " to its final location", e );
566                    }
567                }
568            } finally {
569                org.apache.archiva.common.utils.FileUtils.deleteQuietly(fileToMove);
570            }
571        }
572    }
573
574    protected String addParameters( String path, RemoteRepository remoteRepository )
575    {
576        if ( remoteRepository.getExtraParameters().isEmpty() )
577        {
578            return path;
579        }
580
581        boolean question = false;
582
583        StringBuilder res = new StringBuilder( path == null ? "" : path );
584
585        for ( Map.Entry<String, String> entry : remoteRepository.getExtraParameters().entrySet() )
586        {
587            if ( !question )
588            {
589                res.append( '?' ).append( entry.getKey() ).append( '=' ).append( entry.getValue() );
590            }
591        }
592
593        return res.toString();
594    }
595}