blob: 3b6f1e5d9ceb093fcef26c4038e59e22c89e44c1 [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.ivy.core.cache;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.Date;
import java.util.Map;
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.ModuleRevisionId;
import org.apache.ivy.core.module.id.ModuleRules;
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.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.repository.url.URLResource;
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;
/**
* Fork default CacheManager as default cache resolver use {@link XmlModuleDescriptorParser} to resolve files from cache
* If parent module is resolved from cache with {@link XmlModuleDescriptorParser} then easyant is not able to handle
* inherit properties or plugins We use the same package as original {@link RepositoryCacheManager} to access
* {@link ModuleDescriptorMemoryCache}
*/
public class EasyAntRepositoryCacheManager 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;
public EasyAntRepositoryCacheManager() {
}
public EasyAntRepositoryCacheManager(String name, IvySettings settings, File basedir) {
setName(name);
setSettings(settings);
setBasedir(basedir);
}
public IvySettings getSettings() {
return settings;
}
public void setSettings(IvySettings settings) {
this.settings = settings;
}
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 attributes, PatternMatcher matcher, long duration) {
ttlRules.defineRule(new MapMatcher(attributes, matcher), duration);
}
public void addConfiguredTtl(Map/* <String,String> */attributes) {
String duration = (String) attributes.remove("duration");
if (duration == null) {
throw new IllegalArgumentException("'duration' attribute is mandatory for ttl");
}
String matcher = (String) attributes.remove("matcher");
addTTL(attributes, matcher == null ? ExactPatternMatcher.INSTANCE : settings.getMatcher(matcher),
parseDuration(duration));
}
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;
} else {
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 g == null || g.isEmpty() ? 0 : Integer.parseInt(g);
}
/**
* True if this cache should check lastmodified date to know if ivy files are up to date.
*
* @return
*/
public boolean isCheckmodified() {
if (checkmodified == null) {
if (getSettings() != null) {
String check = getSettings().getVariable("ivy.resolver.default.check.modified");
return check != null ? Boolean.valueOf(check) : false;
} else {
return false;
}
} else {
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.
*/
public boolean isUseOrigin() {
if (useOrigin == null) {
if (getSettings() != null) {
return getSettings().isDefaultUseOrigin();
} else {
return false;
}
} else {
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
*/
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
*/
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(origin.getLocation(), 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.
*/
private File getArchiveFileInCache(Artifact artifact, ArtifactOrigin origin, boolean useOrigin) {
if (useOrigin && !ArtifactOrigin.isUnknown(origin) && origin.isLocal()) {
return Checks.checkAbsolute(origin.getLocation(), artifact + " origin location");
} else {
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);
} else {
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
*/
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());
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.save();
}
public ArtifactOrigin getSavedArtifactOrigin(Artifact artifact) {
ModuleRevisionId mrid = artifact.getModuleRevisionId();
if (!lockMetadataArtifact(mrid)) {
Message.error("impossible to acquire lock for " + mrid);
return ArtifactOrigin.unkwnown(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));
boolean isLocal = Boolean.valueOf(local);
if (location == null) {
// origin has not been specified, return null
return ArtifactOrigin.unkwnown(artifact);
}
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 the hashcode as a uuid for the artifact (fingers crossed)
int hashCode = artifact.getId().hashCode();
// use just some visual cue
return "artifact:" + artifact.getName() + "#" + artifact.getType() + "#" + artifact.getExt() + "#" + 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) {
String prefix = getPrefixKey(artifact);
return prefix + ".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) {
String prefix = getPrefixKey(artifact);
return prefix + ".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) {
String prefix = getPrefixKey(artifact);
return prefix + ".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) {
String prefix = getPrefixKey(artifact);
return prefix + ".exists";
}
private PropertiesFile getCachedDataFile(ModuleDescriptor md) {
return getCachedDataFile(md.getResolvedModuleRevisionId());
}
private PropertiesFile getCachedDataFile(ModuleRevisionId mRevId) {
return new PropertiesFile(new File(getRepositoryCacheRoot(), IvyPatternHelper.substitute(getDataFilePattern(),
mRevId)), "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 (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(mrid, options);
if (resolvedRevision != 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;
}
} else {
return null;
}
}
File ivyFile = getIvyFileInCache(mrid);
if (ivyFile.exists()) {
// found in cache !
try {
ModuleDescriptorParser parser = ModuleDescriptorParserRegistry.getInstance().getParser(
new URLResource(ivyFile.toURI().toURL()));
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()));
return new ResolvedModuleRevision(resolver, artResolver, depMD, madr);
} else {
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 (ParseException e) {
// will try with resolver
Message.debug("\tproblem while parsing cached ivy file for: " + mrid + ": " + e.getMessage());
} catch (IOException e) {
// will try with resolver
Message.debug("\tproblem while accessing cached ivy file for: " + mrid + ": " + e.getMessage());
}
} else {
Message.debug("\tno ivy file in cache for " + mrid + ": tried " + ivyFile);
}
} finally {
if (unlock) {
unlockMetadataArtifact(mrid);
}
}
return null;
}
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);
}
private String getResolvedRevision(ModuleRevisionId mrid, CacheMetadataOptions options) {
if (!lockMetadataArtifact(mrid)) {
Message.error("impossible to acquire lock for " + mrid);
return null;
}
try {
String resolvedRevision;
if (options.isForce()) {
Message.verbose("refresh mode: no check for cached resolved revision for " + mrid);
return null;
}
PropertiesFile cachedResolvedRevision = getCachedDataFile(mrid);
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(mrid, resolvedRevision);
return resolvedRevision;
}
if (options.isCheckTTL()) {
long expiration = Long.parseLong(resolvedTime) + getTTL(mrid);
if (expiration > 0 // negative expiration means that Long.MAX_VALUE has been exceeded
&& System.currentTimeMillis() > expiration) {
Message.verbose(getName() + ": cached resolved revision expired for " + mrid);
return null;
}
}
return resolvedRevision;
} finally {
unlockMetadataArtifact(mrid);
}
}
public void saveResolvedRevision(ModuleRevisionId mrid, String revision) {
if (!lockMetadataArtifact(mrid)) {
Message.error("impossible to acquire lock for " + mrid);
return;
}
try {
PropertiesFile cachedResolvedRevision = getCachedDataFile(mrid);
cachedResolvedRevision.setProperty("resolved.time", String.valueOf(System.currentTimeMillis()));
cachedResolvedRevision.setProperty("resolved.revision", revision);
cachedResolvedRevision.save();
} finally {
unlockMetadataArtifact(mrid);
}
}
public long getTTL(ModuleRevisionId mrid) {
Long ttl = (Long) ttlRules.getRule(mrid);
return ttl == null ? getDefaultTTL() : ttl;
}
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) {
origin = new ArtifactOrigin(artifact, artifactRef.getResource().isLocal(), artifactRef
.getResource().getName());
if (useOrigin && artifactRef.getResource().isLocal()) {
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(artifactRef.getResource(), archiveFile)) {
throw new IllegalStateException("invalid settings for '" + resourceResolver
+ "': pointing repository to ivy cache is forbidden !");
}
if (listener != null) {
listener.startArtifactDownload(this, artifactRef, artifact, origin);
}
resourceDownloader.download(artifact, artifactRef.getResource(), 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) {
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);
}
}
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
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) {
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) {
byte[] shaDigest;
try {
shaDigest = SHA_DIGEST.digest(resource.getName().getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 not supported", e);
}
return HexEncoder.encode(shaDigest);
}
/**
* 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
if (!archiveFile.exists()) {
// but if the local archive doesn't exist, trust the cache only if the cached origin
// says that the remote resource doesn't exist either
return !savedOrigin.isExists();
}
return true;
}
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 orginalMetadataRef,
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(orginalMetadataRef, 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 (Exception e) {
Message.warn("impossible to put metadata file in cache: "
+ (orginalMetadataRef == null ? String.valueOf(md.getResolvedModuleRevisionId()) : String
.valueOf(orginalMetadataRef)) + ". " + e.getClass().getName() + ": " + e.getMessage());
} 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;
} else {
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) {
String[] confs = md.getConfigurationsNames();
for (String conf : confs) {
Artifact[] arts = md.getArtifacts(conf);
for (Artifact art : arts) {
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.getMessage());
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) {
// 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, getIvyFileInCache(mrid).getPath());
}
private Artifact getDefaultMetadataArtifact(ModuleRevisionId mrid) {
return new DefaultArtifact(mrid, new Date(), "metadata", "metadata", "ivy", true);
}
// not used any more, but maybe useful for finer grain 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) {
if (options.isCheckmodified() != null) {
return options.isCheckmodified();
}
return 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());
}
/**
* Resource downloader which makes a copy of the previously existing file before overriding it.
* <p>
* The backup file can be restored or cleanuped 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();
}
}
}
}