shaded.org.eclipse.aether.internal.impl.DefaultUpdateCheckManager Maven / Gradle / Ivy
Show all versions of spring-cloud-contract-shade Show documentation
/*
* 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 shaded.shaded.org.eclipse.aether.internal.impl;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.SessionData;
import org.eclipse.aether.artifact.Artifact;
import shaded.shaded.org.eclipse.aether.impl.UpdateCheck;
import shaded.shaded.org.eclipse.aether.impl.UpdateCheckManager;
import shaded.shaded.org.eclipse.aether.impl.UpdatePolicyAnalyzer;
import org.eclipse.aether.metadata.Metadata;
import org.eclipse.aether.repository.AuthenticationDigest;
import org.eclipse.aether.repository.Proxy;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ResolutionErrorPolicy;
import shaded.shaded.org.eclipse.aether.spi.locator.Service;
import shaded.shaded.org.eclipse.aether.spi.locator.ServiceLocator;
import org.eclipse.aether.transfer.ArtifactNotFoundException;
import org.eclipse.aether.transfer.ArtifactTransferException;
import org.eclipse.aether.transfer.MetadataNotFoundException;
import org.eclipse.aether.transfer.MetadataTransferException;
import org.eclipse.aether.util.ConfigUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.util.Objects.requireNonNull;
/**
*/
@Singleton
@Named
public class DefaultUpdateCheckManager implements UpdateCheckManager, Service {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultUpdatePolicyAnalyzer.class);
private TrackingFileManager trackingFileManager;
private UpdatePolicyAnalyzer updatePolicyAnalyzer;
private static final String UPDATED_KEY_SUFFIX = ".lastUpdated";
private static final String ERROR_KEY_SUFFIX = ".error";
private static final String NOT_FOUND = "";
static final Object SESSION_CHECKS = new Object() {
@Override
public String toString() {
return "updateCheckManager.checks";
}
};
static final String CONFIG_PROP_SESSION_STATE = "aether.updateCheckManager.sessionState";
private static final int STATE_ENABLED = 0;
private static final int STATE_BYPASS = 1;
private static final int STATE_DISABLED = 2;
/**
* This "last modified" timestamp is used when no local file is present, signaling "first attempt" to cache a file,
* but as it is not present, outcome is simply always "go get it".
*
* Its meaning is "we never downloaded it", so go grab it.
*/
private static final long TS_NEVER = 0L;
/**
* This "last modified" timestamp is returned by {@link #getLastUpdated(Properties, String)} method when the
* timestamp entry is not found (due properties file not present or key not present in properties file, irrelevant).
* It means that the cached file (artifact or metadata) is present, but we cannot tell when was it downloaded. In
* this case, it is {@link UpdatePolicyAnalyzer} applying in-effect policy, that decide is update (re-download)
* needed or not. For example, if policy is "never", we should not re-download the file.
*
* Its meaning is "we downloaded it, but have no idea when", so let the policy decide its fate.
*/
private static final long TS_UNKNOWN = 1L;
@Deprecated
public DefaultUpdateCheckManager() {
// default ctor for ServiceLocator
}
@Inject
public DefaultUpdateCheckManager(
TrackingFileManager trackingFileManager, UpdatePolicyAnalyzer updatePolicyAnalyzer) {
setTrackingFileManager(trackingFileManager);
setUpdatePolicyAnalyzer(updatePolicyAnalyzer);
}
public void initService(ServiceLocator locator) {
setTrackingFileManager(locator.getService(TrackingFileManager.class));
setUpdatePolicyAnalyzer(locator.getService(UpdatePolicyAnalyzer.class));
}
public DefaultUpdateCheckManager setTrackingFileManager(TrackingFileManager trackingFileManager) {
this.trackingFileManager = requireNonNull(trackingFileManager);
return this;
}
public DefaultUpdateCheckManager setUpdatePolicyAnalyzer(UpdatePolicyAnalyzer updatePolicyAnalyzer) {
this.updatePolicyAnalyzer = requireNonNull(updatePolicyAnalyzer, "update policy analyzer cannot be null");
return this;
}
public void checkArtifact(RepositorySystemSession session, UpdateCheck check) {
requireNonNull(session, "session cannot be null");
requireNonNull(check, "check cannot be null");
if (check.getLocalLastUpdated() != 0
&& !isUpdatedRequired(session, check.getLocalLastUpdated(), check.getPolicy())) {
LOGGER.debug("Skipped remote request for {}, locally installed artifact up-to-date", check.getItem());
check.setRequired(false);
return;
}
Artifact artifact = check.getItem();
RemoteRepository repository = check.getRepository();
File artifactFile =
requireNonNull(check.getFile(), String.format("The artifact '%s' has no file attached", artifact));
boolean fileExists = check.isFileValid() && artifactFile.exists();
File touchFile = getArtifactTouchFile(artifactFile);
Properties props = read(touchFile);
String updateKey = getUpdateKey(session, artifactFile, repository);
String dataKey = getDataKey(repository);
String error = getError(props, dataKey);
long lastUpdated;
if (error == null) {
if (fileExists) {
// last update was successful
lastUpdated = artifactFile.lastModified();
} else {
// this is the first attempt ever
lastUpdated = TS_NEVER;
}
} else if (error.isEmpty()) {
// artifact did not exist
lastUpdated = getLastUpdated(props, dataKey);
} else {
// artifact could not be transferred
String transferKey = getTransferKey(session, repository);
lastUpdated = getLastUpdated(props, transferKey);
}
if (lastUpdated == TS_NEVER) {
check.setRequired(true);
} else if (isAlreadyUpdated(session, updateKey)) {
LOGGER.debug("Skipped remote request for {}, already updated during this session", check.getItem());
check.setRequired(false);
if (error != null) {
check.setException(newException(error, artifact, repository));
}
} else if (isUpdatedRequired(session, lastUpdated, check.getPolicy())) {
check.setRequired(true);
} else if (fileExists) {
LOGGER.debug("Skipped remote request for {}, locally cached artifact up-to-date", check.getItem());
check.setRequired(false);
} else {
int errorPolicy = Utils.getPolicy(session, artifact, repository);
int cacheFlag = getCacheFlag(error);
if ((errorPolicy & cacheFlag) != 0) {
check.setRequired(false);
check.setException(newException(error, artifact, repository));
} else {
check.setRequired(true);
}
}
}
private static int getCacheFlag(String error) {
if (error == null || error.isEmpty()) {
return ResolutionErrorPolicy.CACHE_NOT_FOUND;
} else {
return ResolutionErrorPolicy.CACHE_TRANSFER_ERROR;
}
}
private ArtifactTransferException newException(String error, Artifact artifact, RemoteRepository repository) {
if (error == null || error.isEmpty()) {
return new ArtifactNotFoundException(
artifact,
repository,
artifact
+ " was not found in " + repository.getUrl()
+ " during a previous attempt. This failure was"
+ " cached in the local repository and"
+ " resolution is not reattempted until the update interval of " + repository.getId()
+ " has elapsed or updates are forced",
true);
} else {
return new ArtifactTransferException(
artifact,
repository,
artifact + " failed to transfer from "
+ repository.getUrl() + " during a previous attempt. This failure"
+ " was cached in the local repository and"
+ " resolution is not reattempted until the update interval of " + repository.getId()
+ " has elapsed or updates are forced. Original error: " + error,
true);
}
}
public void checkMetadata(RepositorySystemSession session, UpdateCheck check) {
requireNonNull(session, "session cannot be null");
requireNonNull(check, "check cannot be null");
if (check.getLocalLastUpdated() != 0
&& !isUpdatedRequired(session, check.getLocalLastUpdated(), check.getPolicy())) {
LOGGER.debug("Skipped remote request for {} locally installed metadata up-to-date", check.getItem());
check.setRequired(false);
return;
}
Metadata metadata = check.getItem();
RemoteRepository repository = check.getRepository();
File metadataFile =
requireNonNull(check.getFile(), String.format("The metadata '%s' has no file attached", metadata));
boolean fileExists = check.isFileValid() && metadataFile.exists();
File touchFile = getMetadataTouchFile(metadataFile);
Properties props = read(touchFile);
String updateKey = getUpdateKey(session, metadataFile, repository);
String dataKey = getDataKey(metadataFile);
String error = getError(props, dataKey);
long lastUpdated;
if (error == null) {
if (fileExists) {
// last update was successful
lastUpdated = getLastUpdated(props, dataKey);
} else {
// this is the first attempt ever
lastUpdated = TS_NEVER;
}
} else if (error.isEmpty()) {
// metadata did not exist
lastUpdated = getLastUpdated(props, dataKey);
} else {
// metadata could not be transferred
String transferKey = getTransferKey(session, metadataFile, repository);
lastUpdated = getLastUpdated(props, transferKey);
}
if (lastUpdated == TS_NEVER) {
check.setRequired(true);
} else if (isAlreadyUpdated(session, updateKey)) {
LOGGER.debug("Skipped remote request for {}, already updated during this session", check.getItem());
check.setRequired(false);
if (error != null) {
check.setException(newException(error, metadata, repository));
}
} else if (isUpdatedRequired(session, lastUpdated, check.getPolicy())) {
check.setRequired(true);
} else if (fileExists) {
LOGGER.debug("Skipped remote request for {}, locally cached metadata up-to-date", check.getItem());
check.setRequired(false);
} else {
int errorPolicy = Utils.getPolicy(session, metadata, repository);
int cacheFlag = getCacheFlag(error);
if ((errorPolicy & cacheFlag) != 0) {
check.setRequired(false);
check.setException(newException(error, metadata, repository));
} else {
check.setRequired(true);
}
}
}
private MetadataTransferException newException(String error, Metadata metadata, RemoteRepository repository) {
if (error == null || error.isEmpty()) {
return new MetadataNotFoundException(
metadata,
repository,
metadata + " was not found in "
+ repository.getUrl() + " during a previous attempt."
+ " This failure was cached in the local repository and"
+ " resolution is not be reattempted until the update interval of " + repository.getId()
+ " has elapsed or updates are forced",
true);
} else {
return new MetadataTransferException(
metadata,
repository,
metadata + " failed to transfer from "
+ repository.getUrl() + " during a previous attempt."
+ " This failure was cached in the local repository and"
+ " resolution will not be reattempted until the update interval of " + repository.getId()
+ " has elapsed or updates are forced. Original error: " + error,
true);
}
}
private long getLastUpdated(Properties props, String key) {
String value = props.getProperty(key + UPDATED_KEY_SUFFIX, "");
try {
return (value.length() > 0) ? Long.parseLong(value) : TS_UNKNOWN;
} catch (NumberFormatException e) {
LOGGER.debug("Cannot parse last updated date {}, ignoring it", value, e);
return TS_UNKNOWN;
}
}
private String getError(Properties props, String key) {
return props.getProperty(key + ERROR_KEY_SUFFIX);
}
private File getArtifactTouchFile(File artifactFile) {
return new File(artifactFile.getPath() + UPDATED_KEY_SUFFIX);
}
private File getMetadataTouchFile(File metadataFile) {
return new File(metadataFile.getParent(), "resolver-status.properties");
}
private String getDataKey(RemoteRepository repository) {
Set mirroredUrls = Collections.emptySet();
if (repository.isRepositoryManager()) {
mirroredUrls = new TreeSet<>();
for (RemoteRepository mirroredRepository : repository.getMirroredRepositories()) {
mirroredUrls.add(normalizeRepoUrl(mirroredRepository.getUrl()));
}
}
StringBuilder buffer = new StringBuilder(1024);
buffer.append(normalizeRepoUrl(repository.getUrl()));
for (String mirroredUrl : mirroredUrls) {
buffer.append('+').append(mirroredUrl);
}
return buffer.toString();
}
private String getTransferKey(RepositorySystemSession session, RemoteRepository repository) {
return getRepoKey(session, repository);
}
private String getDataKey(File metadataFile) {
return metadataFile.getName();
}
private String getTransferKey(RepositorySystemSession session, File metadataFile, RemoteRepository repository) {
return metadataFile.getName() + '/' + getRepoKey(session, repository);
}
private String getRepoKey(RepositorySystemSession session, RemoteRepository repository) {
StringBuilder buffer = new StringBuilder(128);
Proxy proxy = repository.getProxy();
if (proxy != null) {
buffer.append(AuthenticationDigest.forProxy(session, repository)).append('@');
buffer.append(proxy.getHost()).append(':').append(proxy.getPort()).append('>');
}
buffer.append(AuthenticationDigest.forRepository(session, repository)).append('@');
buffer.append(repository.getContentType()).append('-');
buffer.append(repository.getId()).append('-');
buffer.append(normalizeRepoUrl(repository.getUrl()));
return buffer.toString();
}
private String normalizeRepoUrl(String url) {
String result = url;
if (url != null && url.length() > 0 && !url.endsWith("/")) {
result = url + '/';
}
return result;
}
private String getUpdateKey(RepositorySystemSession session, File file, RemoteRepository repository) {
return file.getAbsolutePath() + '|' + getRepoKey(session, repository);
}
private int getSessionState(RepositorySystemSession session) {
String mode = ConfigUtils.getString(session, "enabled", CONFIG_PROP_SESSION_STATE);
if (Boolean.parseBoolean(mode) || "enabled".equalsIgnoreCase(mode)) {
// perform update check at most once per session, regardless of update policy
return STATE_ENABLED;
} else if ("bypass".equalsIgnoreCase(mode)) {
// evaluate update policy but record update in session to prevent potential future checks
return STATE_BYPASS;
} else {
// no session state at all, always evaluate update policy
return STATE_DISABLED;
}
}
private boolean isAlreadyUpdated(RepositorySystemSession session, Object updateKey) {
if (getSessionState(session) >= STATE_BYPASS) {
return false;
}
SessionData data = session.getData();
Object checkedFiles = data.get(SESSION_CHECKS);
if (!(checkedFiles instanceof Map)) {
return false;
}
return ((Map) checkedFiles).containsKey(updateKey);
}
@SuppressWarnings("unchecked")
private void setUpdated(RepositorySystemSession session, Object updateKey) {
if (getSessionState(session) >= STATE_DISABLED) {
return;
}
SessionData data = session.getData();
Object checkedFiles = data.computeIfAbsent(SESSION_CHECKS, () -> new ConcurrentHashMap<>(256));
((Map