blob: 8ade23f4c56cf100bfff2cb46cb9e001f94fbf10 [file] [log] [blame]
package org.apache.maven.artifact.repository.metadata;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.apache.maven.artifact.metadata.ArtifactMetadata;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
import org.apache.maven.artifact.repository.DefaultRepositoryRequest;
import org.apache.maven.artifact.repository.RepositoryRequest;
import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader;
import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Writer;
import org.apache.maven.repository.legacy.UpdateCheckManager;
import org.apache.maven.repository.legacy.WagonManager;
import org.apache.maven.wagon.ResourceDoesNotExistException;
import org.apache.maven.wagon.TransferFailedException;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.AbstractLogEnabled;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.WriterFactory;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Jason van Zyl
*/
@Component( role = RepositoryMetadataManager.class )
public class DefaultRepositoryMetadataManager
extends AbstractLogEnabled
implements RepositoryMetadataManager
{
@Requirement
private WagonManager wagonManager;
@Requirement
private UpdateCheckManager updateCheckManager;
public void resolve( RepositoryMetadata metadata, List<ArtifactRepository> remoteRepositories,
ArtifactRepository localRepository )
throws RepositoryMetadataResolutionException
{
RepositoryRequest request = new DefaultRepositoryRequest();
request.setLocalRepository( localRepository );
request.setRemoteRepositories( remoteRepositories );
resolve( metadata, request );
}
public void resolve( RepositoryMetadata metadata, RepositoryRequest request )
throws RepositoryMetadataResolutionException
{
ArtifactRepository localRepo = request.getLocalRepository();
List<ArtifactRepository> remoteRepositories = request.getRemoteRepositories();
if ( !request.isOffline() )
{
Date localCopyLastModified = null;
if ( metadata.getBaseVersion() != null )
{
localCopyLastModified = getLocalCopyLastModified( localRepo, metadata );
}
for ( ArtifactRepository repository : remoteRepositories )
{
ArtifactRepositoryPolicy policy = metadata.getPolicy( repository );
File file =
new File( localRepo.getBasedir(), localRepo.pathOfLocalRepositoryMetadata( metadata, repository ) );
boolean update;
if ( !policy.isEnabled() )
{
update = false;
if ( getLogger().isDebugEnabled() )
{
getLogger().debug( "Skipping update check for " + metadata.getKey() + " (" + file
+ ") from disabled repository " + repository.getId() + " ("
+ repository.getUrl() + ")" );
}
}
else if ( request.isForceUpdate() )
{
update = true;
}
else if ( localCopyLastModified != null && !policy.checkOutOfDate( localCopyLastModified ) )
{
update = false;
if ( getLogger().isDebugEnabled() )
{
getLogger().debug(
"Skipping update check for " + metadata.getKey() + " (" + file + ") from repository "
+ repository.getId() + " (" + repository.getUrl() + ") in favor of local copy" );
}
}
else
{
update = updateCheckManager.isUpdateRequired( metadata, repository, file );
}
if ( update )
{
getLogger().info( metadata.getKey() + ": checking for updates from " + repository.getId() );
try
{
wagonManager.getArtifactMetadata( metadata, repository, file, policy.getChecksumPolicy() );
}
catch ( ResourceDoesNotExistException e )
{
getLogger().debug( metadata + " could not be found on repository: " + repository.getId() );
// delete the local copy so the old details aren't used.
if ( file.exists() )
{
if ( !file.delete() )
{
// sleep for 10ms just in case this is windows holding a file lock
try
{
Thread.sleep( 10 );
}
catch ( InterruptedException ie )
{
// ignore
}
file.delete(); // if this fails, forget about it
}
}
}
catch ( TransferFailedException e )
{
getLogger().warn( metadata + " could not be retrieved from repository: " + repository.getId()
+ " due to an error: " + e.getMessage() );
getLogger().debug( "Exception", e );
}
finally
{
updateCheckManager.touch( metadata, repository, file );
}
}
// TODO should this be inside the above check?
// touch file so that this is not checked again until interval has passed
if ( file.exists() )
{
file.setLastModified( System.currentTimeMillis() );
}
}
}
try
{
mergeMetadata( metadata, remoteRepositories, localRepo );
}
catch ( RepositoryMetadataStoreException e )
{
throw new RepositoryMetadataResolutionException(
"Unable to store local copy of metadata: " + e.getMessage(), e );
}
}
private Date getLocalCopyLastModified( ArtifactRepository localRepository, RepositoryMetadata metadata )
{
String metadataPath = localRepository.pathOfLocalRepositoryMetadata( metadata, localRepository );
File metadataFile = new File( localRepository.getBasedir(), metadataPath );
return metadataFile.isFile() ? new Date( metadataFile.lastModified() ) : null;
}
private void mergeMetadata( RepositoryMetadata metadata, List<ArtifactRepository> remoteRepositories,
ArtifactRepository localRepository )
throws RepositoryMetadataStoreException
{
// TODO currently this is first wins, but really we should take the latest by comparing either the
// snapshot timestamp, or some other timestamp later encoded into the metadata.
// TODO this needs to be repeated here so the merging doesn't interfere with the written metadata
// - we'd be much better having a pristine input, and an ongoing metadata for merging instead
Map<ArtifactRepository, Metadata> previousMetadata = new HashMap<>();
ArtifactRepository selected = null;
for ( ArtifactRepository repository : remoteRepositories )
{
ArtifactRepositoryPolicy policy = metadata.getPolicy( repository );
if ( policy.isEnabled() && loadMetadata( metadata, repository, localRepository, previousMetadata ) )
{
metadata.setRepository( repository );
selected = repository;
}
}
if ( loadMetadata( metadata, localRepository, localRepository, previousMetadata ) )
{
metadata.setRepository( null );
selected = localRepository;
}
updateSnapshotMetadata( metadata, previousMetadata, selected, localRepository );
}
private void updateSnapshotMetadata( RepositoryMetadata metadata,
Map<ArtifactRepository, Metadata> previousMetadata,
ArtifactRepository selected, ArtifactRepository localRepository )
throws RepositoryMetadataStoreException
{
// TODO this could be a lot nicer... should really be in the snapshot transformation?
if ( metadata.isSnapshot() )
{
Metadata prevMetadata = metadata.getMetadata();
for ( ArtifactRepository repository : previousMetadata.keySet() )
{
Metadata m = previousMetadata.get( repository );
if ( repository.equals( selected ) )
{
if ( m.getVersioning() == null )
{
m.setVersioning( new Versioning() );
}
if ( m.getVersioning().getSnapshot() == null )
{
m.getVersioning().setSnapshot( new Snapshot() );
}
}
else
{
if ( ( m.getVersioning() != null ) && ( m.getVersioning().getSnapshot() != null )
&& m.getVersioning().getSnapshot().isLocalCopy() )
{
m.getVersioning().getSnapshot().setLocalCopy( false );
metadata.setMetadata( m );
metadata.storeInLocalRepository( localRepository, repository );
}
}
}
metadata.setMetadata( prevMetadata );
}
}
private boolean loadMetadata( RepositoryMetadata repoMetadata, ArtifactRepository remoteRepository,
ArtifactRepository localRepository,
Map<ArtifactRepository, Metadata> previousMetadata )
{
boolean setRepository = false;
File metadataFile = new File( localRepository.getBasedir(),
localRepository.pathOfLocalRepositoryMetadata( repoMetadata, remoteRepository ) );
if ( metadataFile.exists() )
{
Metadata metadata;
try
{
metadata = readMetadata( metadataFile );
}
catch ( RepositoryMetadataReadException e )
{
if ( getLogger().isDebugEnabled() )
{
getLogger().warn( e.getMessage(), e );
}
else
{
getLogger().warn( e.getMessage() );
}
return setRepository;
}
if ( repoMetadata.isSnapshot() && ( previousMetadata != null ) )
{
previousMetadata.put( remoteRepository, metadata );
}
if ( repoMetadata.getMetadata() != null )
{
setRepository = repoMetadata.getMetadata().merge( metadata );
}
else
{
repoMetadata.setMetadata( metadata );
setRepository = true;
}
}
return setRepository;
}
/*
* TODO share with DefaultPluginMappingManager.
*/
protected Metadata readMetadata( File mappingFile )
throws RepositoryMetadataReadException
{
Metadata result;
try ( Reader reader = ReaderFactory.newXmlReader( mappingFile ) )
{
MetadataXpp3Reader mappingReader = new MetadataXpp3Reader();
result = mappingReader.read( reader, false );
}
catch ( FileNotFoundException e )
{
throw new RepositoryMetadataReadException( "Cannot read metadata from '" + mappingFile + "'", e );
}
catch ( IOException | XmlPullParserException e )
{
throw new RepositoryMetadataReadException(
"Cannot read metadata from '" + mappingFile + "': " + e.getMessage(), e );
}
return result;
}
/**
* Ensures the last updated timestamp of the specified metadata does not refer to the future and fixes the local
* metadata if necessary to allow proper merging/updating of metadata during deployment.
*/
private void fixTimestamp( File metadataFile, Metadata metadata, Metadata reference )
{
boolean changed = false;
if ( metadata != null && reference != null )
{
Versioning versioning = metadata.getVersioning();
Versioning versioningRef = reference.getVersioning();
if ( versioning != null && versioningRef != null )
{
String lastUpdated = versioning.getLastUpdated();
String now = versioningRef.getLastUpdated();
if ( lastUpdated != null && now != null && now.compareTo( lastUpdated ) < 0 )
{
getLogger().warn(
"The last updated timestamp in " + metadataFile + " refers to the future (now = " + now
+ ", lastUpdated = " + lastUpdated + "). Please verify that the clocks of all"
+ " deploying machines are reasonably synchronized." );
versioning.setLastUpdated( now );
changed = true;
}
}
}
if ( changed )
{
getLogger().debug( "Repairing metadata in " + metadataFile );
try ( Writer writer = WriterFactory.newXmlWriter( metadataFile ) )
{
new MetadataXpp3Writer().write( writer, metadata );
}
catch ( IOException e )
{
String msg = "Could not write fixed metadata to " + metadataFile + ": " + e.getMessage();
if ( getLogger().isDebugEnabled() )
{
getLogger().warn( msg, e );
}
else
{
getLogger().warn( msg );
}
}
}
}
public void resolveAlways( RepositoryMetadata metadata, ArtifactRepository localRepository,
ArtifactRepository remoteRepository )
throws RepositoryMetadataResolutionException
{
File file;
try
{
file = getArtifactMetadataFromDeploymentRepository( metadata, localRepository, remoteRepository );
}
catch ( TransferFailedException e )
{
throw new RepositoryMetadataResolutionException(
metadata + " could not be retrieved from repository: " + remoteRepository.getId() + " due to an error: "
+ e.getMessage(), e );
}
try
{
if ( file.exists() )
{
Metadata prevMetadata = readMetadata( file );
metadata.setMetadata( prevMetadata );
}
}
catch ( RepositoryMetadataReadException e )
{
throw new RepositoryMetadataResolutionException( e.getMessage(), e );
}
}
private File getArtifactMetadataFromDeploymentRepository( ArtifactMetadata metadata, ArtifactRepository localRepo,
ArtifactRepository remoteRepository )
throws TransferFailedException
{
File file =
new File( localRepo.getBasedir(), localRepo.pathOfLocalRepositoryMetadata( metadata, remoteRepository ) );
try
{
wagonManager.getArtifactMetadataFromDeploymentRepository( metadata, remoteRepository, file,
ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN );
}
catch ( ResourceDoesNotExistException e )
{
getLogger().info(
metadata + " could not be found on repository: " + remoteRepository.getId() + ", so will be created" );
// delete the local copy so the old details aren't used.
if ( file.exists() )
{
if ( !file.delete() )
{
// sleep for 10ms just in case this is windows holding a file lock
try
{
Thread.sleep( 10 );
}
catch ( InterruptedException ie )
{
// ignore
}
file.delete(); // if this fails, forget about it
}
}
}
finally
{
if ( metadata instanceof RepositoryMetadata )
{
updateCheckManager.touch( (RepositoryMetadata) metadata, remoteRepository, file );
}
}
return file;
}
public void deploy( ArtifactMetadata metadata, ArtifactRepository localRepository,
ArtifactRepository deploymentRepository )
throws RepositoryMetadataDeploymentException
{
File file;
if ( metadata instanceof RepositoryMetadata )
{
getLogger().info( "Retrieving previous metadata from " + deploymentRepository.getId() );
try
{
file = getArtifactMetadataFromDeploymentRepository( metadata, localRepository, deploymentRepository );
}
catch ( TransferFailedException e )
{
throw new RepositoryMetadataDeploymentException(
metadata + " could not be retrieved from repository: " + deploymentRepository.getId()
+ " due to an error: " + e.getMessage(), e );
}
if ( file.isFile() )
{
try
{
fixTimestamp( file, readMetadata( file ), ( (RepositoryMetadata) metadata ).getMetadata() );
}
catch ( RepositoryMetadataReadException e )
{
// will be reported via storeInlocalRepository
}
}
}
else
{
// It's a POM - we don't need to retrieve it first
file = new File( localRepository.getBasedir(),
localRepository.pathOfLocalRepositoryMetadata( metadata, deploymentRepository ) );
}
try
{
metadata.storeInLocalRepository( localRepository, deploymentRepository );
}
catch ( RepositoryMetadataStoreException e )
{
throw new RepositoryMetadataDeploymentException( "Error installing metadata: " + e.getMessage(), e );
}
try
{
wagonManager.putArtifactMetadata( file, metadata, deploymentRepository );
}
catch ( TransferFailedException e )
{
throw new RepositoryMetadataDeploymentException( "Error while deploying metadata: " + e.getMessage(), e );
}
}
public void install( ArtifactMetadata metadata, ArtifactRepository localRepository )
throws RepositoryMetadataInstallationException
{
try
{
metadata.storeInLocalRepository( localRepository, localRepository );
}
catch ( RepositoryMetadataStoreException e )
{
throw new RepositoryMetadataInstallationException( "Error installing metadata: " + e.getMessage(), e );
}
}
}