blob: a7136ae39c18bc186b812128a59ae9a1eec25c0a [file] [log] [blame]
/*
* 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.
*/
package org.apache.maven.artifact.repository.metadata;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.xml.stream.XMLStreamException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.MetadataStaxReader;
import org.apache.maven.artifact.repository.metadata.io.MetadataStaxWriter;
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.logging.AbstractLogEnabled;
/**
*/
@Named
@Singleton
@Deprecated
public class DefaultRepositoryMetadataManager extends AbstractLogEnabled implements RepositoryMetadataManager {
@Inject
private WagonManager wagonManager;
@Inject
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 {
try (InputStream in = Files.newInputStream(mappingFile.toPath())) {
return new Metadata(new MetadataStaxReader().read(in, false));
} catch (FileNotFoundException e) {
throw new RepositoryMetadataReadException("Cannot read metadata from '" + mappingFile + "'", e);
} catch (IOException | XMLStreamException e) {
throw new RepositoryMetadataReadException(
"Cannot read metadata from '" + mappingFile + "': " + e.getMessage(), e);
}
}
/**
* 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 (OutputStream out = Files.newOutputStream(metadataFile.toPath())) {
new MetadataStaxWriter().write(out, metadata.getDelegate());
} catch (IOException | XMLStreamException 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);
}
}
}