blob: 4c4156a071721d994faf7de64ef1158870c0b769 [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.repository.legacy;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.Date;
import java.util.Properties;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
import org.apache.maven.artifact.repository.Authentication;
import org.apache.maven.artifact.repository.metadata.RepositoryMetadata;
import org.apache.maven.repository.Proxy;
import org.codehaus.plexus.logging.AbstractLogEnabled;
import org.codehaus.plexus.logging.Logger;
/**
* DefaultUpdateCheckManager
*/
@Named
@Singleton
@Deprecated
public class DefaultUpdateCheckManager extends AbstractLogEnabled implements UpdateCheckManager {
private static final String ERROR_KEY_SUFFIX = ".error";
public DefaultUpdateCheckManager() {}
public DefaultUpdateCheckManager(Logger logger) {
enableLogging(logger);
}
public static final String LAST_UPDATE_TAG = ".lastUpdated";
private static final String TOUCHFILE_NAME = "resolver-status.properties";
public boolean isUpdateRequired(Artifact artifact, ArtifactRepository repository) {
File file = artifact.getFile();
ArtifactRepositoryPolicy policy = artifact.isSnapshot() ? repository.getSnapshots() : repository.getReleases();
if (!policy.isEnabled()) {
if (getLogger().isDebugEnabled()) {
getLogger()
.debug("Skipping update check for " + artifact + " (" + file + ") from " + repository.getId()
+ " (" + repository.getUrl() + ")");
}
return false;
}
if (getLogger().isDebugEnabled()) {
getLogger()
.debug("Determining update check for " + artifact + " (" + file + ") from " + repository.getId()
+ " (" + repository.getUrl() + ")");
}
if (file == null) {
// TODO throw something instead?
return true;
}
Date lastCheckDate;
if (file.exists()) {
lastCheckDate = new Date(file.lastModified());
} else {
File touchfile = getTouchfile(artifact);
lastCheckDate = readLastUpdated(touchfile, getRepositoryKey(repository));
}
return (lastCheckDate == null) || policy.checkOutOfDate(lastCheckDate);
}
public boolean isUpdateRequired(RepositoryMetadata metadata, ArtifactRepository repository, File file) {
// Here, we need to determine which policy to use. Release updateInterval will be used when
// the metadata refers to a release artifact or meta-version, and snapshot updateInterval will be used when
// it refers to a snapshot artifact or meta-version.
// NOTE: Release metadata includes version information about artifacts that have been released, to allow
// meta-versions like RELEASE and LATEST to resolve, and also to allow retrieval of the range of valid, released
// artifacts available.
ArtifactRepositoryPolicy policy = metadata.getPolicy(repository);
if (!policy.isEnabled()) {
if (getLogger().isDebugEnabled()) {
getLogger()
.debug("Skipping update check for " + metadata.getKey() + " (" + file + ") from "
+ repository.getId() + " (" + repository.getUrl() + ")");
}
return false;
}
if (getLogger().isDebugEnabled()) {
getLogger()
.debug("Determining update check for " + metadata.getKey() + " (" + file + ") from "
+ repository.getId() + " (" + repository.getUrl() + ")");
}
if (file == null) {
// TODO throw something instead?
return true;
}
Date lastCheckDate = readLastUpdated(metadata, repository, file);
return (lastCheckDate == null) || policy.checkOutOfDate(lastCheckDate);
}
private Date readLastUpdated(RepositoryMetadata metadata, ArtifactRepository repository, File file) {
File touchfile = getTouchfile(metadata, file);
String key = getMetadataKey(repository, file);
return readLastUpdated(touchfile, key);
}
public String getError(Artifact artifact, ArtifactRepository repository) {
File touchFile = getTouchfile(artifact);
return getError(touchFile, getRepositoryKey(repository));
}
public void touch(Artifact artifact, ArtifactRepository repository, String error) {
File file = artifact.getFile();
File touchfile = getTouchfile(artifact);
if (file.exists()) {
touchfile.delete();
} else {
writeLastUpdated(touchfile, getRepositoryKey(repository), error);
}
}
public void touch(RepositoryMetadata metadata, ArtifactRepository repository, File file) {
File touchfile = getTouchfile(metadata, file);
String key = getMetadataKey(repository, file);
writeLastUpdated(touchfile, key, null);
}
String getMetadataKey(ArtifactRepository repository, File file) {
return repository.getId() + '.' + file.getName() + LAST_UPDATE_TAG;
}
String getRepositoryKey(ArtifactRepository repository) {
StringBuilder buffer = new StringBuilder(256);
Proxy proxy = repository.getProxy();
if (proxy != null) {
if (proxy.getUserName() != null) {
int hash = (proxy.getUserName() + proxy.getPassword()).hashCode();
buffer.append(hash).append('@');
}
buffer.append(proxy.getHost()).append(':').append(proxy.getPort()).append('>');
}
// consider the username&password because a repo manager might block artifacts depending on authorization
Authentication auth = repository.getAuthentication();
if (auth != null) {
int hash = (auth.getUsername() + auth.getPassword()).hashCode();
buffer.append(hash).append('@');
}
// consider the URL (instead of the id) as this most closely relates to the contents in the repo
buffer.append(repository.getUrl());
return buffer.toString();
}
private void writeLastUpdated(File touchfile, String key, String error) {
synchronized (touchfile.getAbsolutePath().intern()) {
if (!touchfile.getParentFile().exists()
&& !touchfile.getParentFile().mkdirs()) {
getLogger()
.debug("Failed to create directory: " + touchfile.getParent()
+ " for tracking artifact metadata resolution.");
return;
}
FileChannel channel = null;
FileLock lock = null;
try {
Properties props = new Properties();
channel = new RandomAccessFile(touchfile, "rw").getChannel();
lock = channel.lock();
if (touchfile.canRead()) {
getLogger().debug("Reading resolution-state from: " + touchfile);
props.load(Channels.newInputStream(channel));
}
props.setProperty(key, Long.toString(System.currentTimeMillis()));
if (error != null) {
props.setProperty(key + ERROR_KEY_SUFFIX, error);
} else {
props.remove(key + ERROR_KEY_SUFFIX);
}
getLogger().debug("Writing resolution-state to: " + touchfile);
channel.truncate(0);
props.store(Channels.newOutputStream(channel), "Last modified on: " + new Date());
lock.release();
lock = null;
channel.close();
channel = null;
} catch (IOException e) {
getLogger()
.debug(
"Failed to record lastUpdated information for resolution.\nFile: "
+ touchfile.toString() + "; key: " + key,
e);
} finally {
if (lock != null) {
try {
lock.release();
} catch (IOException e) {
getLogger()
.debug("Error releasing exclusive lock for resolution tracking file: " + touchfile, e);
}
}
if (channel != null) {
try {
channel.close();
} catch (IOException e) {
getLogger().debug("Error closing FileChannel for resolution tracking file: " + touchfile, e);
}
}
}
}
}
Date readLastUpdated(File touchfile, String key) {
getLogger().debug("Searching for " + key + " in resolution tracking file.");
Properties props = read(touchfile);
if (props != null) {
String rawVal = props.getProperty(key);
if (rawVal != null) {
try {
return new Date(Long.parseLong(rawVal));
} catch (NumberFormatException e) {
getLogger().debug("Cannot parse lastUpdated date: '" + rawVal + "'. Ignoring.", e);
}
}
}
return null;
}
private String getError(File touchFile, String key) {
Properties props = read(touchFile);
if (props != null) {
return props.getProperty(key + ERROR_KEY_SUFFIX);
}
return null;
}
private Properties read(File touchfile) {
if (!touchfile.canRead()) {
getLogger().debug("Skipped unreadable resolution tracking file: " + touchfile);
return null;
}
synchronized (touchfile.getAbsolutePath().intern()) {
try {
Properties props = new Properties();
try (FileInputStream in = new FileInputStream(touchfile)) {
try (FileLock lock = in.getChannel().lock(0, Long.MAX_VALUE, true)) {
getLogger().debug("Reading resolution-state from: " + touchfile);
props.load(in);
return props;
}
}
} catch (IOException e) {
getLogger().debug("Failed to read resolution tracking file: " + touchfile, e);
return null;
}
}
}
File getTouchfile(Artifact artifact) {
StringBuilder sb = new StringBuilder(128);
sb.append(artifact.getArtifactId());
sb.append('-').append(artifact.getBaseVersion());
if (artifact.getClassifier() != null) {
sb.append('-').append(artifact.getClassifier());
}
sb.append('.').append(artifact.getType()).append(LAST_UPDATE_TAG);
return new File(artifact.getFile().getParentFile(), sb.toString());
}
File getTouchfile(RepositoryMetadata metadata, File file) {
return new File(file.getParent(), TOUCHFILE_NAME);
}
}