blob: 234810a6176865eb7e8089e981efc8428e8a6c4d [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
*
* https://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.ivy.core.cache;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.ivy.Ivy;
import org.apache.ivy.core.IvyPatternHelper;
import org.apache.ivy.core.module.descriptor.Artifact;
import org.apache.ivy.core.module.descriptor.DefaultArtifact;
import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
import org.apache.ivy.core.module.id.ArtifactRevisionId;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.core.module.id.ModuleRules;
import org.apache.ivy.core.pack.PackagingManager;
import org.apache.ivy.core.report.ArtifactDownloadReport;
import org.apache.ivy.core.report.DownloadStatus;
import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
import org.apache.ivy.core.resolve.ResolvedModuleRevision;
import org.apache.ivy.core.settings.IvySettings;
import org.apache.ivy.plugins.IvySettingsAware;
import org.apache.ivy.plugins.lock.LockStrategy;
import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
import org.apache.ivy.plugins.matcher.MapMatcher;
import org.apache.ivy.plugins.matcher.Matcher;
import org.apache.ivy.plugins.matcher.NoMatcher;
import org.apache.ivy.plugins.matcher.PatternMatcher;
import org.apache.ivy.plugins.namespace.NameSpaceHelper;
import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
import org.apache.ivy.plugins.parser.ParserSettings;
import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
import org.apache.ivy.plugins.repository.ArtifactResourceResolver;
import org.apache.ivy.plugins.repository.LocalizableResource;
import org.apache.ivy.plugins.repository.Repository;
import org.apache.ivy.plugins.repository.Resource;
import org.apache.ivy.plugins.repository.ResourceDownloader;
import org.apache.ivy.plugins.repository.ResourceHelper;
import org.apache.ivy.plugins.resolver.AbstractResolver;
import org.apache.ivy.plugins.resolver.DependencyResolver;
import org.apache.ivy.plugins.resolver.util.ResolvedResource;
import org.apache.ivy.util.Checks;
import org.apache.ivy.util.FileUtil;
import org.apache.ivy.util.HexEncoder;
import org.apache.ivy.util.Message;
import org.apache.ivy.util.PropertiesFile;
import static org.apache.ivy.util.StringUtils.isNullOrEmpty;
public class DefaultRepositoryCacheManager implements RepositoryCacheManager, IvySettingsAware {
private static final String DEFAULT_ARTIFACT_PATTERN = "[organisation]/[module](/[branch])/[type]s/[artifact]-[revision](-[classifier])(.[ext])";
private static final String DEFAULT_DATA_FILE_PATTERN = "[organisation]/[module](/[branch])/ivydata-[revision].properties";
private static final String DEFAULT_IVY_PATTERN = "[organisation]/[module](/[branch])/ivy-[revision].xml";
private static final int DEFAULT_MEMORY_CACHE_SIZE = 150;
private static MessageDigest SHA_DIGEST;
static {
try {
SHA_DIGEST = MessageDigest.getInstance("SHA1");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("The SHA1 algorithm is not available in your classpath", e);
}
}
private IvySettings settings;
private File basedir;
private LockStrategy lockStrategy;
private String name;
private String ivyPattern;
private String dataFilePattern = DEFAULT_DATA_FILE_PATTERN;
private String artifactPattern;
private String lockStrategyName;
private String changingPattern;
private String changingMatcherName = PatternMatcher.EXACT_OR_REGEXP;
private Boolean checkmodified;
private Boolean useOrigin;
private ModuleRules<Long> ttlRules = new ModuleRules<>();
private Long defaultTTL = null;
private ModuleDescriptorMemoryCache memoryModuleDescrCache;
private PackagingManager packagingManager = new PackagingManager();
private final List<ConfiguredTTL> configuredTTLs = new ArrayList<>();
public DefaultRepositoryCacheManager() {
}
public DefaultRepositoryCacheManager(String name, IvySettings settings, File basedir) {
setName(name);
setSettings(settings);
setBasedir(basedir);
}
public IvySettings getSettings() {
return settings;
}
public void setSettings(final IvySettings settings) {
this.settings = settings;
packagingManager.setSettings(settings);
// process and setup the configured TTLs (which weren't yet processed since they needed
// a settings instance to be present)
for (final ConfiguredTTL configuredTTL : configuredTTLs) {
this.addTTL(configuredTTL.attributes,
configuredTTL.matcher == null ? ExactPatternMatcher.INSTANCE : settings.getMatcher(configuredTTL.matcher), configuredTTL.duration);
}
// clear off the configured TTLs since we have now processed them and created TTL rules
// out of them
this.configuredTTLs.clear();
}
public File getIvyFileInCache(ModuleRevisionId mrid) {
String file = IvyPatternHelper.substitute(getIvyPattern(),
DefaultArtifact.newIvyArtifact(mrid, null));
return new File(getRepositoryCacheRoot(), file);
}
public String getIvyPattern() {
if (ivyPattern == null) {
if (settings != null) {
ivyPattern = settings.getDefaultCacheIvyPattern();
}
if (ivyPattern == null) {
ivyPattern = DEFAULT_IVY_PATTERN;
}
}
return ivyPattern;
}
public String getArtifactPattern() {
if (artifactPattern == null) {
if (settings != null) {
artifactPattern = settings.getDefaultCacheArtifactPattern();
}
if (artifactPattern == null) {
artifactPattern = DEFAULT_ARTIFACT_PATTERN;
}
}
return artifactPattern;
}
public void setArtifactPattern(String artifactPattern) {
CacheUtil.checkCachePattern(artifactPattern);
this.artifactPattern = artifactPattern;
}
public File getBasedir() {
if (basedir == null) {
basedir = settings.getDefaultRepositoryCacheBasedir();
}
return basedir;
}
public void setBasedir(File cache) {
this.basedir = cache;
}
public long getDefaultTTL() {
if (defaultTTL == null) {
defaultTTL = parseDuration(settings.getVariable("ivy.cache.ttl.default"));
}
return defaultTTL;
}
public void setDefaultTTL(long defaultTTL) {
this.defaultTTL = defaultTTL;
}
public void setDefaultTTL(String defaultTTL) {
this.defaultTTL = parseDuration(defaultTTL);
}
public String getDataFilePattern() {
return dataFilePattern;
}
public void setDataFilePattern(String dataFilePattern) {
CacheUtil.checkCachePattern(dataFilePattern);
this.dataFilePattern = dataFilePattern;
}
public void setIvyPattern(String ivyPattern) {
CacheUtil.checkCachePattern(ivyPattern);
this.ivyPattern = ivyPattern;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getChangingMatcherName() {
return changingMatcherName;
}
public void setChangingMatcher(String changingMatcherName) {
this.changingMatcherName = changingMatcherName;
}
public String getChangingPattern() {
return changingPattern;
}
public void setChangingPattern(String changingPattern) {
this.changingPattern = changingPattern;
}
public void addTTL(Map<String, String> attributes, PatternMatcher matcher, long duration) {
ttlRules.defineRule(new MapMatcher(attributes, matcher), duration);
}
public void addConfiguredTtl(final Map<String, String> attributes) {
final String durationValue = attributes.get("duration");
if (durationValue == null) {
throw new IllegalArgumentException("'duration' attribute is mandatory for ttl");
}
final long duration = parseDuration(durationValue);
final ConfiguredTTL configuredTTL = new ConfiguredTTL(duration, attributes.get("matcher"), attributes);
// Processing TTLs requires access to an initialized/usable IvySettings instance.
// We keep track of these configured TTLs and process them when the IvySettings instance
// becomes usable
this.configuredTTLs.add(configuredTTL);
}
public void setMemorySize(int size) {
memoryModuleDescrCache = new ModuleDescriptorMemoryCache(size);
}
public ModuleDescriptorMemoryCache getMemoryCache() {
if (memoryModuleDescrCache == null) {
memoryModuleDescrCache = new ModuleDescriptorMemoryCache(DEFAULT_MEMORY_CACHE_SIZE);
}
return memoryModuleDescrCache;
}
private static final Pattern DURATION_PATTERN = Pattern
.compile("(?:(\\d+)d)? ?(?:(\\d+)h)? ?(?:(\\d+)m)? ?(?:(\\d+)s)? ?(?:(\\d+)ms)?");
private static final int MILLIS_IN_SECONDS = 1000;
private static final int MILLIS_IN_MINUTES = 60 * MILLIS_IN_SECONDS;
private static final int MILLIS_IN_HOUR = 60 * MILLIS_IN_MINUTES;
private static final int MILLIS_IN_DAY = 24 * MILLIS_IN_HOUR;
private long parseDuration(String duration) {
if (duration == null) {
return 0;
}
if ("eternal".equals(duration)) {
return Long.MAX_VALUE;
}
java.util.regex.Matcher m = DURATION_PATTERN.matcher(duration);
if (m.matches()) {
// CheckStyle:MagicNumber| OFF
int days = getGroupIntValue(m, 1);
int hours = getGroupIntValue(m, 2);
int minutes = getGroupIntValue(m, 3);
int seconds = getGroupIntValue(m, 4);
int millis = getGroupIntValue(m, 5);
// CheckStyle:MagicNumber| ON
return days * MILLIS_IN_DAY + hours * MILLIS_IN_HOUR + minutes * MILLIS_IN_MINUTES
+ seconds * MILLIS_IN_SECONDS + millis;
}
throw new IllegalArgumentException("invalid duration '" + duration
+ "': it must match " + DURATION_PATTERN.pattern() + " or 'eternal'");
}
private int getGroupIntValue(java.util.regex.Matcher m, int groupNumber) {
String g = m.group(groupNumber);
return isNullOrEmpty(g) ? 0 : Integer.parseInt(g);
}
/**
* True if this cache should check lastmodified date to know if ivy files are up to date.
*
* @return boolean
*/
public boolean isCheckmodified() {
if (checkmodified == null) {
return getSettings() != null && Boolean
.parseBoolean(getSettings().getVariable("ivy.resolver.default.check.modified"));
}
return checkmodified;
}
public void setCheckmodified(boolean check) {
checkmodified = check;
}
/**
* True if this cache should use artifacts original location when possible, false if they
* should be copied to cache.
*
* @return boolean
*/
public boolean isUseOrigin() {
if (useOrigin == null) {
return getSettings() != null && getSettings().isDefaultUseOrigin();
}
return useOrigin;
}
public void setUseOrigin(boolean b) {
useOrigin = b;
}
/**
* Returns a File object pointing to where the artifact can be found on the local file system.
* This is usually in the cache, but it can be directly in the repository if it is local and if
* the resolve has been done with useOrigin = true
*
* @param artifact Artifact
* @return File
*/
public File getArchiveFileInCache(Artifact artifact) {
ArtifactOrigin origin = getSavedArtifactOrigin(artifact);
return getArchiveFileInCache(artifact, origin);
}
/**
* Returns a File object pointing to where the artifact can be found on the local file system.
* This is usually in the cache, but it can be directly in the repository if it is local and if
* the resolve has been done with useOrigin = true
*
* @param artifact Artifact
* @param origin ArtifactOrigin
* @return File
*/
public File getArchiveFileInCache(Artifact artifact, ArtifactOrigin origin) {
File archive = new File(getRepositoryCacheRoot(), getArchivePathInCache(artifact, origin));
if (!archive.exists() && !ArtifactOrigin.isUnknown(origin) && origin.isLocal()) {
File original = Checks.checkAbsolute(parseArtifactOriginFilePath(origin), artifact
+ " origin location");
if (original.exists()) {
return original;
}
}
return archive;
}
/**
* Returns a File object pointing to where the artifact can be found on the local file system,
* using or not the original location depending on the availability of origin information
* provided as parameter and the setting of useOrigin. If useOrigin is false, this method will
* always return the file in the cache.
*
* @param artifact Artifact
* @param origin ArtifactOrigin
* @param useOrigin boolean
* @return File
*/
private File getArchiveFileInCache(Artifact artifact, ArtifactOrigin origin, boolean useOrigin) {
if (useOrigin && !ArtifactOrigin.isUnknown(origin) && origin.isLocal()) {
return Checks.checkAbsolute(parseArtifactOriginFilePath(origin), artifact + " origin location");
}
return new File(getRepositoryCacheRoot(), getArchivePathInCache(artifact, origin));
}
public String getArchivePathInCache(Artifact artifact) {
return IvyPatternHelper.substitute(getArtifactPattern(), artifact);
}
public String getArchivePathInCache(Artifact artifact, ArtifactOrigin origin) {
if (isOriginalMetadataArtifact(artifact)) {
return IvyPatternHelper.substitute(getIvyPattern() + ".original", artifact, origin);
}
return IvyPatternHelper.substitute(getArtifactPattern(), artifact, origin);
}
/**
* Saves the information of which resolver was used to resolve a md, so that this info can be
* retrieve later (even after a jvm restart) by getSavedResolverName(ModuleDescriptor md)
*
* @param md
* the module descriptor resolved
* @param name
* resolver name
*/
private void saveResolver(ModuleDescriptor md, String name) {
// should always be called with a lock on module metadata artifact
PropertiesFile cdf = getCachedDataFile(md);
cdf.setProperty("resolver", name);
cdf.save();
}
/**
* Saves the information of which resolver was used to resolve a md, so that this info can be
* retrieve later (even after a jvm restart) by getSavedArtResolverName(ModuleDescriptor md)
*
* @param md
* the module descriptor resolved
* @param metadataResolverName
* metadata resolver name
* @param artifactResolverName
* artifact resolver name
*/
public void saveResolvers(ModuleDescriptor md, String metadataResolverName,
String artifactResolverName) {
ModuleRevisionId mrid = md.getResolvedModuleRevisionId();
if (!lockMetadataArtifact(mrid)) {
Message.error("impossible to acquire lock for " + mrid);
return;
}
try {
PropertiesFile cdf = getCachedDataFile(md);
cdf.setProperty("resolver", metadataResolverName);
cdf.setProperty("artifact.resolver", artifactResolverName);
cdf.save();
} finally {
unlockMetadataArtifact(mrid);
}
}
private String getSavedResolverName(ModuleDescriptor md) {
// should always be called with a lock on module metadata artifact
PropertiesFile cdf = getCachedDataFile(md);
return cdf.getProperty("resolver");
}
private String getSavedArtResolverName(ModuleDescriptor md) {
// should always be called with a lock on module metadata artifact
PropertiesFile cdf = getCachedDataFile(md);
return cdf.getProperty("artifact.resolver");
}
void saveArtifactOrigin(Artifact artifact, ArtifactOrigin origin) {
// should always be called with a lock on module metadata artifact
PropertiesFile cdf = getCachedDataFile(artifact.getModuleRevisionId());
cdf.setProperty(getIsLocalKey(artifact), String.valueOf(origin.isLocal()));
cdf.setProperty(getLocationKey(artifact), origin.getLocation());
cdf.setProperty(getOriginalKey(artifact), getPrefixKey(origin.getArtifact()));
if (origin.getLastChecked() != null) {
cdf.setProperty(getLastCheckedKey(artifact), origin.getLastChecked().toString());
}
cdf.setProperty(getExistsKey(artifact), Boolean.toString(origin.isExists()));
cdf.save();
}
private void removeSavedArtifactOrigin(Artifact artifact) {
// should always be called with a lock on module metadata artifact
PropertiesFile cdf = getCachedDataFile(artifact.getModuleRevisionId());
cdf.remove(getLocationKey(artifact));
cdf.remove(getIsLocalKey(artifact));
cdf.remove(getLastCheckedKey(artifact));
cdf.remove(getOriginalKey(artifact));
cdf.save();
}
private static final Pattern ARTIFACT_KEY_PATTERN = Pattern
.compile(".*:(.*)#(.*)#(.*)#(.*)(\\.location)?");
public ArtifactOrigin getSavedArtifactOrigin(Artifact artifact) {
ModuleRevisionId mrid = artifact.getModuleRevisionId();
if (!lockMetadataArtifact(mrid)) {
Message.error("impossible to acquire lock for " + mrid);
return ArtifactOrigin.unknown(artifact);
}
try {
PropertiesFile cdf = getCachedDataFile(artifact.getModuleRevisionId());
String location = cdf.getProperty(getLocationKey(artifact));
String local = cdf.getProperty(getIsLocalKey(artifact));
String lastChecked = cdf.getProperty(getLastCheckedKey(artifact));
String exists = cdf.getProperty(getExistsKey(artifact));
String original = cdf.getProperty(getOriginalKey(artifact));
boolean isLocal = Boolean.valueOf(local);
if (location == null) {
// origin has not been specified, return null
return ArtifactOrigin.unknown(artifact);
}
if (original != null) {
// original artifact key artifact:[name]#[type]#[ext]#[hashcode]
java.util.regex.Matcher m = ARTIFACT_KEY_PATTERN.matcher(original);
if (m.matches()) {
String origName = m.group(1);
String origType = m.group(2);
String origExt = m.group(3);
ArtifactRevisionId originArtifactId = ArtifactRevisionId.newInstance(
artifact.getModuleRevisionId(), origName, origType, origExt);
// second check: verify the hashcode of the cached artifact
if (m.group(4).equals("" + originArtifactId.hashCode())) {
try {
artifact = new DefaultArtifact(originArtifactId,
artifact.getPublicationDate(), new URL(location), true);
} catch (MalformedURLException e) {
Message.debug(e);
}
}
}
} else {
// Fallback if cached with old version:
// if the origin artifact has another extension (e.g. .pom) then make a synthetic
// origin artifact for it
if (!location.endsWith("." + artifact.getExt())) {
// try to find other cached artifact info with same location. This must be the
// origin. We must parse the key as we do not know for sure what the original
// artifact is named.
String ownLocationKey = getLocationKey(artifact);
for (Map.Entry<Object, Object> entry : cdf.entrySet()) {
if (entry.getValue().equals(location)
&& !ownLocationKey.equals(entry.getKey())) {
// found a match, key is
// artifact:[name]#[type]#[ext]#[hashcode].location
java.util.regex.Matcher m = ARTIFACT_KEY_PATTERN.matcher((String) entry
.getKey());
if (m.matches()) {
String origName = m.group(1);
String origType = m.group(2);
String origExt = m.group(3);
// first check: the type should end in .original
if (!origType.endsWith(".original")) {
continue;
}
ArtifactRevisionId originArtifactId = ArtifactRevisionId
.newInstance(artifact.getModuleRevisionId(), origName,
origType, origExt);
// second check: verify the hashcode of the cached artifact
if (m.group(4).equals("" + originArtifactId.hashCode())) {
try {
artifact = new DefaultArtifact(originArtifactId,
artifact.getPublicationDate(), new URL(location),
true);
} catch (MalformedURLException e) {
Message.debug(e);
}
}
break;
}
}
}
}
}
ArtifactOrigin origin = new ArtifactOrigin(artifact, isLocal, location);
if (lastChecked != null) {
origin.setLastChecked(Long.valueOf(lastChecked));
}
if (exists != null) {
origin.setExist(Boolean.valueOf(exists));
}
return origin;
} finally {
unlockMetadataArtifact(mrid);
}
}
/**
* Creates the unique prefix key that will reference the artifact within the properties.
*
* @param artifact
* the artifact to create the unique key from. Cannot be null.
* @return the unique prefix key as a string.
*/
private String getPrefixKey(Artifact artifact) {
// use just some visual cue;
// use the hashcode as a uuid for the artifact (fingers crossed)
return String.format("artifact:%s#%s#%s#%d", artifact.getName(), artifact.getType(),
artifact.getExt(), artifact.getId().hashCode());
}
/**
* Returns the key used to identify the location of the artifact.
*
* @param artifact
* the artifact to generate the key from. Cannot be null.
* @return the key to be used to reference the artifact location.
*/
private String getLocationKey(Artifact artifact) {
return getPrefixKey(artifact) + ".location";
}
/**
* Returns the key used to identify if the artifact is local.
*
* @param artifact
* the artifact to generate the key from. Cannot be null.
* @return the key to be used to reference the artifact locality.
*/
private String getIsLocalKey(Artifact artifact) {
return getPrefixKey(artifact) + ".is-local";
}
/**
* Returns the key used to identify the last time the artifact was checked to be up to date.
*
* @param artifact
* the artifact to generate the key from. Cannot be null.
* @return the key to be used to reference the artifact's last check date.
*/
private String getLastCheckedKey(Artifact artifact) {
return getPrefixKey(artifact) + ".lastchecked";
}
/**
* Returns the key used to identify the existence of the remote artifact.
*
* @param artifact
* the artifact to generate the key from. Cannot be null.
* @return the key to be used to reference the existence of the artifact.
*/
private String getExistsKey(Artifact artifact) {
return getPrefixKey(artifact) + ".exists";
}
/**
* Returns the key used to identify the original artifact.
*
* @param artifact
* the artifact to generate the key from. Cannot be null.
* @return the key to be used to reference the original artifact.
*/
private String getOriginalKey(Artifact artifact) {
return getPrefixKey(artifact) + ".original";
}
private PropertiesFile getCachedDataFile(ModuleDescriptor md) {
return getCachedDataFile(md.getResolvedModuleRevisionId());
}
private PropertiesFile getCachedDataFile(ModuleRevisionId mRevId) {
File file = new File(getRepositoryCacheRoot(), IvyPatternHelper.substitute(
getDataFilePattern(), mRevId));
assertInsideCache(file);
return new PropertiesFile(file, "ivy cached data file for " + mRevId);
}
/**
* A resolver-specific ivydata file, only used for caching dynamic revisions, e.g.
* integration-repo.
*/
private PropertiesFile getCachedDataFile(String resolverName, ModuleRevisionId mRevId) {
// we append ".${resolverName} onto the end of the regular ivydata location
File file = new File(getRepositoryCacheRoot(),
IvyPatternHelper.substitute(getDataFilePattern(), mRevId) + "." + resolverName);
assertInsideCache(file);
return new PropertiesFile(file, "ivy cached data file for " + mRevId);
}
public ResolvedModuleRevision findModuleInCache(DependencyDescriptor dd,
ModuleRevisionId requestedRevisionId, CacheMetadataOptions options,
String expectedResolver) {
if (isCheckmodified(dd, requestedRevisionId, options)) {
Message.verbose("don't use cache for " + requestedRevisionId + ": checkModified=true");
return null;
}
if (!options.isUseCacheOnly() && isChanging(dd, requestedRevisionId, options)) {
Message.verbose("don't use cache for " + requestedRevisionId + ": changing=true");
return null;
}
return doFindModuleInCache(requestedRevisionId, options, expectedResolver);
}
private ResolvedModuleRevision doFindModuleInCache(ModuleRevisionId mrid,
CacheMetadataOptions options, String expectedResolver) {
if (!lockMetadataArtifact(mrid)) {
Message.error("impossible to acquire lock for " + mrid);
return null;
}
boolean unlock = true;
try {
if (settings.getVersionMatcher().isDynamic(mrid)) {
String resolvedRevision = getResolvedRevision(expectedResolver, mrid, options);
if (resolvedRevision == null) {
return null;
}
Message.verbose("found resolved revision in cache: " + mrid + " => "
+ resolvedRevision);
// we have found another module in the cache, make sure we unlock
// the original module
unlockMetadataArtifact(mrid);
mrid = ModuleRevisionId.newInstance(mrid, resolvedRevision);
// don't forget to request a lock on the new module!
if (!lockMetadataArtifact(mrid)) {
Message.error("impossible to acquire lock for " + mrid);
// we couldn't lock the new module, so no need to unlock it
unlock = false;
return null;
}
}
File ivyFile = getIvyFileInCache(mrid);
if (ivyFile.exists()) {
// found in cache !
try {
ModuleDescriptorParser parser = getModuleDescriptorParser(ivyFile);
ModuleDescriptor depMD = getMdFromCache(parser, options, ivyFile);
String resolverName = getSavedResolverName(depMD);
String artResolverName = getSavedArtResolverName(depMD);
DependencyResolver resolver = settings.getResolver(resolverName);
if (resolver == null) {
Message.debug("\tresolver not found: " + resolverName
+ " => trying to use the one configured for " + mrid);
resolver = settings.getResolver(depMD.getResolvedModuleRevisionId());
if (resolver != null) {
Message.debug("\tconfigured resolver found for "
+ depMD.getResolvedModuleRevisionId() + ": "
+ resolver.getName() + ": saving this data");
saveResolver(depMD, resolver.getName());
}
}
DependencyResolver artResolver = settings.getResolver(artResolverName);
if (artResolver == null) {
artResolver = resolver;
}
if (resolver != null) {
Message.debug("\tfound ivy file in cache for " + mrid + " (resolved by "
+ resolver.getName() + "): " + ivyFile);
if (expectedResolver == null || expectedResolver.equals(resolver.getName())) {
MetadataArtifactDownloadReport madr = new MetadataArtifactDownloadReport(
depMD.getMetadataArtifact());
madr.setDownloadStatus(DownloadStatus.NO);
madr.setSearched(false);
madr.setLocalFile(ivyFile);
madr.setSize(ivyFile.length());
madr.setArtifactOrigin(getSavedArtifactOrigin(depMD
.getMetadataArtifact()));
if (madr.getArtifactOrigin().isExists()) {
if (madr.getArtifactOrigin().isLocal()
&& madr.getArtifactOrigin().getArtifact().getUrl() != null) {
madr.setOriginalLocalFile(new File(madr.getArtifactOrigin()
.getArtifact().getUrl().toURI()));
} else {
// find locally cached file
madr.setOriginalLocalFile(getArchiveFileInCache(madr
.getArtifactOrigin().getArtifact()));
}
}
return new ResolvedModuleRevision(resolver, artResolver, depMD, madr);
}
Message.debug("found module in cache but with a different resolver: "
+ "discarding: " + mrid + "; expected resolver="
+ expectedResolver + "; resolver=" + resolver.getName());
} else {
Message.debug("\tresolver not found: " + resolverName
+ " => cannot use cached ivy file for " + mrid);
}
} catch (Exception e) {
// will try with resolver
Message.debug("\tproblem while parsing cached ivy file for: " + mrid, e);
}
} else {
Message.debug("\tno ivy file in cache for " + mrid + ": tried " + ivyFile);
}
} finally {
if (unlock) {
unlockMetadataArtifact(mrid);
}
}
return null;
}
/**
* Choose write module descriptor parser for a given moduleDescriptor
*
* @param moduleDescriptorFile
* a given module descriptor
* @return ModuleDescriptorParser
*/
protected ModuleDescriptorParser getModuleDescriptorParser(File moduleDescriptorFile) {
return XmlModuleDescriptorParser.getInstance();
}
private class MyModuleDescriptorProvider implements ModuleDescriptorProvider {
private final ModuleDescriptorParser mdParser;
private final ParserSettings settings;
public MyModuleDescriptorProvider(ModuleDescriptorParser mdParser, ParserSettings settings) {
this.mdParser = mdParser;
this.settings = settings;
}
public ModuleDescriptor provideModule(ParserSettings ivySettings, File descriptorURL,
boolean validate) throws ParseException, IOException {
return mdParser.parseDescriptor(settings, descriptorURL.toURI().toURL(), validate);
}
}
private ModuleDescriptor getMdFromCache(ModuleDescriptorParser mdParser,
CacheMetadataOptions options, File ivyFile) throws ParseException, IOException {
ModuleDescriptorMemoryCache cache = getMemoryCache();
ModuleDescriptorProvider mdProvider = new MyModuleDescriptorProvider(mdParser, settings);
return cache.get(ivyFile, settings, options.isValidate(), mdProvider);
}
private ModuleDescriptor getStaledMd(ModuleDescriptorParser mdParser,
CacheMetadataOptions options, File ivyFile, ParserSettings parserSettings)
throws ParseException, IOException {
ModuleDescriptorMemoryCache cache = getMemoryCache();
ModuleDescriptorProvider mdProvider = new MyModuleDescriptorProvider(mdParser,
parserSettings);
return cache.getStale(ivyFile, settings, options.isValidate(), mdProvider);
}
/**
* Called by doFindModuleInCache to lookup the dynamic {@code mrid} in the ivycache's ivydata
* file.
*/
private String getResolvedRevision(String expectedResolver, ModuleRevisionId mrid, CacheMetadataOptions options) {
if (!lockMetadataArtifact(mrid)) {
Message.error("impossible to acquire lock for " + mrid);
return null;
}
try {
if (options.isForce()) {
Message.verbose("refresh mode: no check for cached resolved revision for " + mrid);
return null;
}
// If a resolver is asking for its specific dynamic revision, avoid looking at a different one
PropertiesFile cachedResolvedRevision;
if (expectedResolver != null) {
cachedResolvedRevision = getCachedDataFile(expectedResolver, mrid);
} else {
cachedResolvedRevision = getCachedDataFile(mrid);
}
String resolvedRevision = cachedResolvedRevision.getProperty("resolved.revision");
if (resolvedRevision == null) {
Message.verbose(getName() + ": no cached resolved revision for " + mrid);
return null;
}
String resolvedTime = cachedResolvedRevision.getProperty("resolved.time");
if (resolvedTime == null) {
Message.verbose(getName()
+ ": inconsistent or old cache: no cached resolved time for " + mrid);
saveResolvedRevision(expectedResolver, mrid, resolvedRevision);
return resolvedRevision;
}
if (options.isCheckTTL()) {
long expiration = Long.parseLong(resolvedTime) + getTTL(mrid);
// negative expiration means that Long.MAX_VALUE has been exceeded
if (expiration > 0 && System.currentTimeMillis() > expiration) {
Message.verbose(getName() + ": cached resolved revision expired for " + mrid);
return null;
}
}
return resolvedRevision;
} finally {
unlockMetadataArtifact(mrid);
}
}
@Deprecated
public void saveResolvedRevision(ModuleRevisionId mrid, String revision) {
saveResolvedRevision(null, mrid, revision);
}
public void saveResolvedRevision(String resolverName, ModuleRevisionId mrid, String revision) {
if (!lockMetadataArtifact(mrid)) {
Message.error("impossible to acquire lock for " + mrid);
return;
}
try {
PropertiesFile cachedResolvedRevision;
if (resolverName == null) {
cachedResolvedRevision = getCachedDataFile(mrid);
} else {
cachedResolvedRevision = getCachedDataFile(resolverName, mrid);
}
cachedResolvedRevision.setProperty("resolved.time",
String.valueOf(System.currentTimeMillis()));
cachedResolvedRevision.setProperty("resolved.revision", revision);
if (resolverName != null) {
cachedResolvedRevision.setProperty("resolver", resolverName);
}
cachedResolvedRevision.save();
} finally {
unlockMetadataArtifact(mrid);
}
}
public long getTTL(ModuleRevisionId mrid) {
Long ttl = ttlRules.getRule(mrid);
return ttl == null ? getDefaultTTL() : ttl;
}
@Override
public String toString() {
return name;
}
public File getRepositoryCacheRoot() {
return getBasedir();
}
public LockStrategy getLockStrategy() {
if (lockStrategy == null) {
if (lockStrategyName != null) {
lockStrategy = settings.getLockStrategy(lockStrategyName);
} else {
lockStrategy = settings.getDefaultLockStrategy();
}
}
return lockStrategy;
}
public void setLockStrategy(LockStrategy lockStrategy) {
this.lockStrategy = lockStrategy;
}
public void setLockStrategy(String lockStrategyName) {
this.lockStrategyName = lockStrategyName;
}
public ArtifactDownloadReport download(Artifact artifact,
ArtifactResourceResolver resourceResolver, ResourceDownloader resourceDownloader,
CacheDownloadOptions options) {
final ArtifactDownloadReport adr = new ArtifactDownloadReport(artifact);
boolean useOrigin = isUseOrigin();
// TODO: see if we could lock on the artifact to download only, instead of the module
// metadata artifact. We'd need to store artifact origin and is local in artifact specific
// file to do so, or lock the metadata artifact only to update artifact origin, which would
// mean acquiring nested locks, which can be a dangerous thing
ModuleRevisionId mrid = artifact.getModuleRevisionId();
if (!lockMetadataArtifact(mrid)) {
adr.setDownloadStatus(DownloadStatus.FAILED);
adr.setDownloadDetails("impossible to get lock for " + mrid);
return adr;
}
try {
DownloadListener listener = options.getListener();
if (listener != null) {
listener.needArtifact(this, artifact);
}
ArtifactOrigin origin = getSavedArtifactOrigin(artifact);
// if we can use origin file, we just ask ivy for the file in cache, and it will
// return the original one if possible. If we are not in useOrigin mode, we use the
// getArchivePath method which always return a path in the actual cache
File archiveFile = getArchiveFileInCache(artifact, origin, useOrigin);
if (archiveFile.exists() && !options.isForce()) {
adr.setDownloadStatus(DownloadStatus.NO);
adr.setSize(archiveFile.length());
adr.setArtifactOrigin(origin);
adr.setLocalFile(archiveFile);
} else {
long start = System.currentTimeMillis();
try {
ResolvedResource artifactRef = resourceResolver.resolve(artifact);
if (artifactRef != null) {
Resource artifactRes = artifactRef.getResource();
origin = new ArtifactOrigin(artifact, artifactRes.isLocal(),
artifactRes.getName());
if (useOrigin && artifactRes.isLocal()) {
if (artifactRes instanceof LocalizableResource) {
origin.setLocation(((LocalizableResource) artifactRes).getFile()
.getAbsolutePath());
}
saveArtifactOrigin(artifact, origin);
archiveFile = getArchiveFileInCache(artifact, origin);
adr.setDownloadStatus(DownloadStatus.NO);
adr.setSize(archiveFile.length());
adr.setArtifactOrigin(origin);
adr.setLocalFile(archiveFile);
} else {
// refresh archive file now that we better now its origin
archiveFile = getArchiveFileInCache(artifact, origin, useOrigin);
if (ResourceHelper.equals(artifactRes, archiveFile)) {
throw new IllegalStateException("invalid settings for '"
+ resourceResolver
+ "': pointing repository to ivy cache is forbidden !");
}
assertInsideCache(archiveFile);
if (listener != null) {
listener.startArtifactDownload(this, artifactRef, artifact, origin);
}
resourceDownloader.download(artifact, artifactRes, archiveFile);
adr.setSize(archiveFile.length());
saveArtifactOrigin(artifact, origin);
adr.setDownloadTimeMillis(System.currentTimeMillis() - start);
adr.setDownloadStatus(DownloadStatus.SUCCESSFUL);
adr.setArtifactOrigin(origin);
adr.setLocalFile(archiveFile);
}
} else {
adr.setDownloadStatus(DownloadStatus.FAILED);
adr.setDownloadDetails(ArtifactDownloadReport.MISSING_ARTIFACT);
adr.setDownloadTimeMillis(System.currentTimeMillis() - start);
}
} catch (Exception ex) {
Message.debug(ex);
adr.setDownloadStatus(DownloadStatus.FAILED);
adr.setDownloadDetails(ex.getMessage());
adr.setDownloadTimeMillis(System.currentTimeMillis() - start);
}
}
if (adr.getDownloadStatus() != DownloadStatus.FAILED) {
unpackArtifact(artifact, adr, options);
}
if (listener != null) {
listener.endArtifactDownload(this, artifact, adr, archiveFile);
}
return adr;
} finally {
unlockMetadataArtifact(mrid);
}
}
private void unpackArtifact(Artifact artifact, ArtifactDownloadReport adr,
CacheDownloadOptions options) {
Artifact unpacked = packagingManager.getUnpackedArtifact(artifact);
if (unpacked == null) {
// nothing to unpack
return;
}
File archiveFile = getArchiveFileInCache(unpacked, null, false);
if (archiveFile.exists() && !options.isForce()) {
adr.setUnpackedLocalFile(archiveFile);
adr.setUnpackedArtifact(unpacked);
} else {
Message.info("\tUnpacking " + artifact.getId());
try {
final Artifact unpackedArtifact = packagingManager.unpackArtifact(artifact, adr.getLocalFile(), archiveFile);
adr.setUnpackedLocalFile(archiveFile);
adr.setUnpackedArtifact(unpackedArtifact);
} catch (Exception e) {
Message.debug(e);
adr.setDownloadStatus(DownloadStatus.FAILED);
adr.setDownloadDetails("The packed artifact " + artifact.getId()
+ " could not be unpacked (" + e.getMessage() + ")");
}
}
}
public ArtifactDownloadReport downloadRepositoryResource(final Resource resource, String name,
String type, String extension, CacheResourceOptions options, Repository repository) {
String hash = computeResourceNameHash(resource);
ModuleRevisionId mrid = ModuleRevisionId.newInstance("_repository_metadata_", hash,
Ivy.getWorkingRevision());
Artifact artifact = new DefaultArtifact(mrid, null, name, type, extension);
final ArtifactDownloadReport adr = new ArtifactDownloadReport(artifact);
boolean useOrigin = isUseOrigin();
try {
DownloadListener listener = options.getListener();
if (listener != null) {
listener.needArtifact(this, artifact);
}
ArtifactOrigin savedOrigin = getSavedArtifactOrigin(artifact);
File archiveFile = getArchiveFileInCache(artifact, savedOrigin, useOrigin);
ArtifactOrigin origin = new ArtifactOrigin(artifact, resource.isLocal(),
resource.getName());
if (!options.isForce()
// if the local file has been checked to be up to date enough recently, don't download
&& checkCacheUptodate(archiveFile, resource, savedOrigin, origin,
options.getTtl())) {
if (archiveFile.exists()) {
saveArtifactOrigin(artifact, origin);
adr.setDownloadStatus(DownloadStatus.NO);
adr.setSize(archiveFile.length());
adr.setArtifactOrigin(savedOrigin);
adr.setLocalFile(archiveFile);
} else {
// we trust the cache to says that the resource doesn't exist
adr.setDownloadStatus(DownloadStatus.FAILED);
adr.setDownloadDetails("Remote resource is known to not exist");
}
} else {
long start = System.currentTimeMillis();
origin.setLastChecked(start);
try {
ResolvedResource artifactRef = new ResolvedResource(resource,
Ivy.getWorkingRevision());
if (useOrigin && resource.isLocal()) {
saveArtifactOrigin(artifact, origin);
archiveFile = getArchiveFileInCache(artifact, origin);
adr.setDownloadStatus(DownloadStatus.NO);
adr.setSize(archiveFile.length());
adr.setArtifactOrigin(origin);
adr.setLocalFile(archiveFile);
} else {
if (listener != null) {
listener.startArtifactDownload(this, artifactRef, artifact, origin);
}
// actual download
assertInsideCache(archiveFile);
if (archiveFile.exists()) {
archiveFile.delete();
}
File part = new File(archiveFile.getAbsolutePath() + ".part");
repository.get(resource.getName(), part);
if (!part.renameTo(archiveFile)) {
throw new IOException(
"impossible to move part file to definitive one: " + part
+ " -> " + archiveFile);
}
adr.setSize(archiveFile.length());
saveArtifactOrigin(artifact, origin);
adr.setDownloadTimeMillis(System.currentTimeMillis() - start);
adr.setDownloadStatus(DownloadStatus.SUCCESSFUL);
adr.setArtifactOrigin(origin);
adr.setLocalFile(archiveFile);
}
} catch (Exception ex) {
Message.debug(ex);
origin.setExist(false);
saveArtifactOrigin(artifact, origin);
adr.setDownloadStatus(DownloadStatus.FAILED);
adr.setDownloadDetails(ex.getMessage());
adr.setDownloadTimeMillis(System.currentTimeMillis() - start);
}
}
if (listener != null) {
listener.endArtifactDownload(this, artifact, adr, archiveFile);
}
return adr;
} finally {
unlockMetadataArtifact(mrid);
}
}
/**
* Compute a SHA1 of the resource name, encoded in base64, so we can use it as a file name.
*
* @param resource
* the resource which name will be hashed
* @return the hash
*/
private String computeResourceNameHash(Resource resource) {
return HexEncoder.encode(SHA_DIGEST.digest(resource.getName().getBytes(StandardCharsets.UTF_8)));
}
/**
* Check that a cached file can be considered up to date and thus not downloaded
*
* @param archiveFile
* the file in the cache
* @param resource
* the remote resource to check
* @param savedOrigin
* the saved origin which contains that last checked date
* @param origin
* the origin in which to store the new last checked date
* @param ttl
* the time to live to consider the cache up to date
* @return <code>true</code> if the cache is considered up to date
*/
private boolean checkCacheUptodate(File archiveFile, Resource resource,
ArtifactOrigin savedOrigin, ArtifactOrigin origin, long ttl) {
long time = System.currentTimeMillis();
if (savedOrigin.getLastChecked() != null
&& (time - savedOrigin.getLastChecked()) < ttl) {
// still in the ttl period, no need to check, trust the cache
return archiveFile.exists() || !savedOrigin.isExists();
}
if (!archiveFile.exists()) {
// the the file doesn't exist in the cache, obviously not up to date
return false;
}
origin.setLastChecked(time);
// check if the local resource is up to date regarding the remote one
return archiveFile.lastModified() >= resource.getLastModified();
}
public void originalToCachedModuleDescriptor(DependencyResolver resolver,
ResolvedResource originalMetadataRef, Artifact requestedMetadataArtifact,
ResolvedModuleRevision rmr, ModuleDescriptorWriter writer) {
ModuleDescriptor md = rmr.getDescriptor();
Artifact originalMetadataArtifact = getOriginalMetadataArtifact(requestedMetadataArtifact);
File mdFileInCache = getIvyFileInCache(md.getResolvedModuleRevisionId());
ModuleRevisionId mrid = requestedMetadataArtifact.getModuleRevisionId();
if (!lockMetadataArtifact(mrid)) {
Message.warn("impossible to acquire lock for: " + mrid);
return;
}
try {
File originalFileInCache = getArchiveFileInCache(originalMetadataArtifact);
writer.write(originalMetadataRef, md, originalFileInCache, mdFileInCache);
getMemoryCache().putInCache(mdFileInCache, new ParserSettingsMonitor(settings), true,
md);
saveResolvers(md, resolver.getName(), resolver.getName());
if (!md.isDefault()) {
rmr.getReport().setOriginalLocalFile(originalFileInCache);
}
rmr.getReport().setLocalFile(mdFileInCache);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
String metadataRef;
if (originalMetadataRef == null) {
metadataRef = String.valueOf(md.getResolvedModuleRevisionId());
} else {
metadataRef = String.valueOf(originalMetadataRef);
}
Message.warn("impossible to put metadata file in cache: " + metadataRef, e);
} finally {
unlockMetadataArtifact(mrid);
}
}
public ResolvedModuleRevision cacheModuleDescriptor(DependencyResolver resolver,
final ResolvedResource mdRef, DependencyDescriptor dd, Artifact moduleArtifact,
ResourceDownloader downloader, CacheMetadataOptions options) throws ParseException {
Date cachedPublicationDate = null;
ArtifactDownloadReport report;
ModuleRevisionId mrid = moduleArtifact.getModuleRevisionId();
if (!lockMetadataArtifact(mrid)) {
Message.error("impossible to acquire lock for " + mrid);
return null;
}
BackupResourceDownloader backupDownloader = new BackupResourceDownloader(downloader);
try {
if (!moduleArtifact.isMetadata()) {
// the descriptor we are trying to cache is a default one, not much to do
// just make sure the old artifacts are deleted...
if (isChanging(dd, mrid, options)) {
long repoLastModified = mdRef.getLastModified();
Artifact transformedArtifact = NameSpaceHelper.transform(moduleArtifact,
options.getNamespace().getToSystemTransformer());
ArtifactOrigin origin = getSavedArtifactOrigin(transformedArtifact);
File artFile = getArchiveFileInCache(transformedArtifact, origin, false);
if (artFile.exists() && repoLastModified > artFile.lastModified()) {
// artifacts have changed, they should be downloaded again
Message.verbose(mrid + " has changed: deleting old artifacts");
Message.debug("deleting " + artFile);
if (!artFile.delete()) {
Message.error("Couldn't delete outdated artifact from cache: "
+ artFile);
return null;
}
removeSavedArtifactOrigin(transformedArtifact);
}
}
return null;
}
// now let's see if we can find it in cache and if it is up to date
ResolvedModuleRevision rmr = doFindModuleInCache(mrid, options, null);
if (rmr != null) {
if (rmr.getDescriptor().isDefault() && rmr.getResolver() != resolver) {
Message.verbose("\t" + getName() + ": found revision in cache: " + mrid
+ " (resolved by " + rmr.getResolver().getName()
+ "): but it's a default one, maybe we can find a better one");
} else {
if (!isCheckmodified(dd, mrid, options) && !isChanging(dd, mrid, options)) {
Message.verbose("\t" + getName() + ": revision in cache: " + mrid);
rmr.getReport().setSearched(true);
return rmr;
}
long repLastModified = mdRef.getLastModified();
long cacheLastModified = rmr.getDescriptor().getLastModified();
if (!rmr.getDescriptor().isDefault() && repLastModified <= cacheLastModified) {
Message.verbose("\t" + getName() + ": revision in cache (not updated): "
+ mrid);
rmr.getReport().setSearched(true);
return rmr;
}
Message.verbose("\t" + getName()
+ ": revision in cache is not up to date: " + mrid);
if (isChanging(dd, mrid, options)) {
// ivy file has been updated, we should see if it has a new publication
// date to see if a new download is required (in case the dependency is
// a changing one)
cachedPublicationDate = rmr.getDescriptor()
.getResolvedPublicationDate();
}
}
}
Artifact originalMetadataArtifact = getOriginalMetadataArtifact(moduleArtifact);
// now download module descriptor and parse it
report = download(originalMetadataArtifact, new ArtifactResourceResolver() {
public ResolvedResource resolve(Artifact artifact) {
return mdRef;
}
}, backupDownloader, new CacheDownloadOptions().setListener(options.getListener())
.setForce(true));
Message.verbose("\t" + report);
if (report.getDownloadStatus() == DownloadStatus.FAILED) {
Message.warn("problem while downloading module descriptor: " + mdRef.getResource()
+ ": " + report.getDownloadDetails() + " ("
+ report.getDownloadTimeMillis() + "ms)");
return null;
}
try {
ModuleDescriptorParser parser = ModuleDescriptorParserRegistry.getInstance()
.getParser(mdRef.getResource());
ParserSettings parserSettings = settings;
if (resolver instanceof AbstractResolver) {
parserSettings = ((AbstractResolver) resolver).getParserSettings();
}
ModuleDescriptor md = getStaledMd(parser, options, report.getLocalFile(),
parserSettings);
if (md == null) {
throw new IllegalStateException(
"module descriptor parser returned a null module descriptor, which is not allowed. parser="
+ parser + "; parser class=" + parser.getClass().getName()
+ "; module descriptor resource=" + mdRef.getResource());
}
Message.debug("\t" + getName() + ": parsed downloaded md file for " + mrid
+ "; parsed=" + md.getModuleRevisionId());
// check if we should delete old artifacts
boolean deleteOldArtifacts = false;
if (cachedPublicationDate != null
&& !cachedPublicationDate.equals(md.getResolvedPublicationDate())) {
// artifacts have changed, they should be downloaded again
Message.verbose(mrid + " has changed: deleting old artifacts");
deleteOldArtifacts = true;
}
if (deleteOldArtifacts) {
for (String conf : md.getConfigurationsNames()) {
for (Artifact art : md.getArtifacts(conf)) {
Artifact transformedArtifact = NameSpaceHelper.transform(art,
options.getNamespace().getToSystemTransformer());
ArtifactOrigin origin = getSavedArtifactOrigin(transformedArtifact);
File artFile = getArchiveFileInCache(transformedArtifact, origin, false);
if (artFile.exists()) {
Message.debug("deleting " + artFile);
if (!artFile.delete()) {
// Old artifacts couldn't get deleted!
// Restore the original ivy file so the next time we
// resolve the old artifacts are deleted again
backupDownloader.restore();
Message.error("Couldn't delete outdated artifact from cache: "
+ artFile);
return null;
}
}
removeSavedArtifactOrigin(transformedArtifact);
}
}
} else if (isChanging(dd, mrid, options)) {
Message.verbose(mrid
+ " is changing, but has not changed: will trust cached artifacts if any");
}
MetadataArtifactDownloadReport madr = new MetadataArtifactDownloadReport(
md.getMetadataArtifact());
madr.setSearched(true);
madr.setDownloadStatus(report.getDownloadStatus());
madr.setDownloadDetails(report.getDownloadDetails());
madr.setArtifactOrigin(report.getArtifactOrigin());
madr.setDownloadTimeMillis(report.getDownloadTimeMillis());
madr.setOriginalLocalFile(report.getLocalFile());
madr.setSize(report.getSize());
Artifact transformedMetadataArtifact = NameSpaceHelper.transform(
md.getMetadataArtifact(), options.getNamespace().getToSystemTransformer());
saveArtifactOrigin(transformedMetadataArtifact, report.getArtifactOrigin());
return new ResolvedModuleRevision(resolver, resolver, md, madr);
} catch (IOException ex) {
Message.warn("io problem while parsing ivy file: " + mdRef.getResource(), ex);
return null;
}
} finally {
unlockMetadataArtifact(mrid);
backupDownloader.cleanUp();
}
}
// lock used to lock all metadata related information access
private boolean lockMetadataArtifact(ModuleRevisionId mrid) {
Artifact artifact = getDefaultMetadataArtifact(mrid);
try {
// we need to provide an artifact origin to be sure we do not end up in a stack
// overflow if the cache pattern is using original name, and the substitution thus
// trying to get the saved artifact origin value which in turns calls this method
return getLockStrategy().lockArtifact(artifact,
getArchiveFileInCache(artifact, getDefaultMetadataArtifactOrigin(mrid)));
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // reset interrupt status
throw new RuntimeException("operation interrupted");
}
}
private void unlockMetadataArtifact(ModuleRevisionId mrid) {
Artifact artifact = getDefaultMetadataArtifact(mrid);
getLockStrategy().unlockArtifact(artifact,
getArchiveFileInCache(artifact, getDefaultMetadataArtifactOrigin(mrid)));
}
private ArtifactOrigin getDefaultMetadataArtifactOrigin(ModuleRevisionId mrid) {
final String location;
try {
location = this.getIvyFileInCache(mrid).toURI().toURL().toExternalForm();
} catch (MalformedURLException e) {
throw new RuntimeException("Failed to determine artifact origin for " + mrid);
}
// it's important to say the origin is not local to make sure it won't ever be used for
// anything else than original token
return new ArtifactOrigin(DefaultArtifact.newIvyArtifact(mrid, null), false, location);
}
private Artifact getDefaultMetadataArtifact(ModuleRevisionId mrid) {
return new DefaultArtifact(mrid, new Date(), "metadata", "metadata", "ivy", true);
}
// not used any more, but may be useful for finer grained locking when downloading artifacts
// private boolean lockArtifact(Artifact artifact) {
// try {
// return getLockStrategy().lockArtifact(artifact,
// getArchiveFileInCache(artifact, null));
// } catch (InterruptedException e) {
// Thread.currentThread().interrupt(); // reset interrupt status
// throw new RuntimeException("operation interrupted");
// }
// }
//
// private void unlockArtifact(Artifact artifact) {
// getLockStrategy().unlockArtifact(artifact, getArchiveFileInCache(artifact, null));
// }
public Artifact getOriginalMetadataArtifact(Artifact moduleArtifact) {
return DefaultArtifact.cloneWithAnotherType(moduleArtifact, moduleArtifact.getType()
+ ".original");
}
private boolean isOriginalMetadataArtifact(Artifact artifact) {
return artifact.isMetadata() && artifact.getType().endsWith(".original");
}
private boolean isChanging(DependencyDescriptor dd, ModuleRevisionId requestedRevisionId,
CacheMetadataOptions options) {
return dd.isChanging()
|| getChangingMatcher(options).matches(requestedRevisionId.getRevision());
}
private Matcher getChangingMatcher(CacheMetadataOptions options) {
String changingPattern = options.getChangingPattern() != null ? options
.getChangingPattern() : this.changingPattern;
if (changingPattern == null) {
return NoMatcher.INSTANCE;
}
String changingMatcherName = options.getChangingMatcherName() != null ? options
.getChangingMatcherName() : this.changingMatcherName;
PatternMatcher matcher = settings.getMatcher(changingMatcherName);
if (matcher == null) {
throw new IllegalStateException("unknown matcher '" + changingMatcherName
+ "'. It is set as changing matcher in " + this);
}
return matcher.getMatcher(changingPattern);
}
private boolean isCheckmodified(DependencyDescriptor dd, ModuleRevisionId requestedRevisionId,
CacheMetadataOptions options) {
return options.isCheckmodified() == null ? isCheckmodified() : options.isCheckmodified();
}
public void clean() {
FileUtil.forceDelete(getBasedir());
}
public void dumpSettings() {
Message.verbose("\t" + getName());
Message.debug("\t\tivyPattern: " + getIvyPattern());
Message.debug("\t\tartifactPattern: " + getArtifactPattern());
Message.debug("\t\tlockingStrategy: " + getLockStrategy().getName());
Message.debug("\t\tchangingPattern: " + getChangingPattern());
Message.debug("\t\tchangingMatcher: " + getChangingMatcherName());
}
/**
* @throws IllegalArgumentException if the given path points outside of the cache.
*/
public final void assertInsideCache(File fileInCache) {
File root = getRepositoryCacheRoot();
if (root != null && !FileUtil.isLeadingPath(root, fileInCache)) {
throw new IllegalArgumentException(fileInCache + " is outside of the cache");
}
}
/**
* If the {@link ArtifactOrigin#getLocation() location of the artifact origin} is a
* {@code file:} scheme URI, then this method parses that URI and returns back the
* path of the file it represents. Else returns back {@link ArtifactOrigin#getLocation()}
* @param origin The artifact origin
* @return
*/
private static String parseArtifactOriginFilePath(final ArtifactOrigin origin) {
if (origin == null || origin.getLocation() == null) {
return null;
}
final String location = origin.getLocation();
if (!location.startsWith("file:")) {
// no need to parse it into a URI, if it's not a "file" scheme URI
return location;
}
final URI uri;
try {
uri = new URI(location);
} catch (URISyntaxException e) {
return location;
}
if (!uri.isAbsolute()) {
// no scheme in URI, just return the original location
return location;
} else if (uri.getScheme().equals("file")) {
// return the file path
return uri.getPath();
}
// not a "file" scheme URI, just return back the original location
return location;
}
/**
* Resource downloader which makes a copy of the previously existing file before overriding it.
* <p>
* The backup file can be restored or cleaned up later
*/
private final class BackupResourceDownloader implements ResourceDownloader {
private ResourceDownloader delegate;
private File backup;
private String originalPath;
private BackupResourceDownloader(ResourceDownloader delegate) {
this.delegate = delegate;
}
public void download(Artifact artifact, Resource resource, File dest) throws IOException {
// keep a copy of the original file
if (dest.exists()) {
originalPath = dest.getAbsolutePath();
backup = new File(dest.getAbsolutePath() + ".backup");
FileUtil.copy(dest, backup, null, true);
}
delegate.download(artifact, resource, dest);
}
public void restore() throws IOException {
if (backup != null && backup.exists()) {
File original = new File(originalPath);
FileUtil.copy(backup, original, null, true);
backup.delete();
}
}
public void cleanUp() {
if (backup != null && backup.exists()) {
backup.delete();
}
}
}
private static final class ConfiguredTTL {
// attributes on the TTL, that don't contribute to module matching
private static final Set<String> attributesNotContributingToMatching = new HashSet<>();
static {
attributesNotContributingToMatching.add("duration");
attributesNotContributingToMatching.add("matcher");
}
private final String matcher;
private final long duration;
private final Map<String, String> attributes;
private ConfiguredTTL(final long duration, final String matcher, final Map<String, String> attributes) {
this.matcher = matcher;
this.duration = duration;
if (attributes == null) {
this.attributes = Collections.emptyMap();
} else {
final Map<String, String> attrs = new HashMap<>(attributes);
for (final String removable : attributesNotContributingToMatching) {
attrs.remove(removable);
}
this.attributes = Collections.unmodifiableMap(attrs);
}
}
}
}