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