/*
* Copyright 2011 the original author or authors.
*
* Licensed 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.gradle.api.internal.artifacts.ivyservice.ivyresolve;
import org.gradle.api.Transformer;
import org.gradle.api.artifacts.ArtifactIdentifier;
import org.gradle.api.artifacts.ComponentMetadataSupplier;
import org.gradle.api.artifacts.ModuleIdentifier;
import org.gradle.api.artifacts.ModuleVersionIdentifier;
import org.gradle.api.artifacts.ModuleVersionSelector;
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
import org.gradle.api.internal.artifacts.ComponentMetadataProcessor;
import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
import org.gradle.api.internal.artifacts.ImmutableModuleIdentifierFactory;
import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePolicy;
import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ModuleVersionsCache;
import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleArtifactsCache;
import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleMetaDataCache;
import org.gradle.api.internal.artifacts.repositories.resolver.MetadataFetchingCost;
import org.gradle.api.internal.component.ArtifactType;
import org.gradle.internal.component.external.model.FixedComponentArtifacts;
import org.gradle.internal.component.external.model.ModuleComponentArtifactMetadata;
import org.gradle.internal.component.external.model.ModuleComponentResolveMetadata;
import org.gradle.internal.component.model.ComponentArtifactMetadata;
import org.gradle.internal.component.model.ComponentOverrideMetadata;
import org.gradle.internal.component.model.ComponentResolveMetadata;
import org.gradle.internal.component.model.DependencyMetadata;
import org.gradle.internal.component.model.ModuleSource;
import org.gradle.internal.resolve.ArtifactNotFoundException;
import org.gradle.internal.resolve.ArtifactResolveException;
import org.gradle.internal.resolve.result.BuildableArtifactResolveResult;
import org.gradle.internal.resolve.result.BuildableArtifactSetResolveResult;
import org.gradle.internal.resolve.result.BuildableComponentArtifactsResolveResult;
import org.gradle.internal.resolve.result.BuildableModuleComponentMetaDataResolveResult;
import org.gradle.internal.resolve.result.BuildableModuleVersionListingResolveResult;
import org.gradle.internal.resolve.result.DefaultBuildableArtifactSetResolveResult;
import org.gradle.internal.resource.cached.CachedArtifact;
import org.gradle.internal.resource.cached.CachedArtifactIndex;
import org.gradle.internal.resource.cached.ivy.ArtifactAtRepositoryKey;
import org.gradle.util.BuildCommencedTimeProvider;
import org.gradle.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.math.BigInteger;
import java.util.Set;
public class CachingModuleComponentRepository implements ModuleComponentRepository {
private static final Logger LOGGER = LoggerFactory.getLogger(CachingModuleComponentRepository.class);
private final ModuleVersionsCache moduleVersionsCache;
private final ModuleMetaDataCache moduleMetaDataCache;
private final ModuleArtifactsCache moduleArtifactsCache;
private final CachedArtifactIndex artifactAtRepositoryCachedResolutionIndex;
private final CachePolicy cachePolicy;
private final ModuleComponentRepository delegate;
private final BuildCommencedTimeProvider timeProvider;
private final ComponentMetadataProcessor metadataProcessor;
private final ImmutableModuleIdentifierFactory moduleIdentifierFactory;
private LocateInCacheRepositoryAccess locateInCacheRepositoryAccess = new LocateInCacheRepositoryAccess();
private ResolveAndCacheRepositoryAccess resolveAndCacheRepositoryAccess = new ResolveAndCacheRepositoryAccess();
public CachingModuleComponentRepository(ModuleComponentRepository delegate, ModuleVersionsCache moduleVersionsCache, ModuleMetaDataCache moduleMetaDataCache,
ModuleArtifactsCache moduleArtifactsCache, CachedArtifactIndex artifactAtRepositoryCachedResolutionIndex,
CachePolicy cachePolicy, BuildCommencedTimeProvider timeProvider,
ComponentMetadataProcessor metadataProcessor,
ImmutableModuleIdentifierFactory moduleIdentifierFactory) {
this.delegate = delegate;
this.moduleMetaDataCache = moduleMetaDataCache;
this.moduleVersionsCache = moduleVersionsCache;
this.moduleArtifactsCache = moduleArtifactsCache;
this.artifactAtRepositoryCachedResolutionIndex = artifactAtRepositoryCachedResolutionIndex;
this.timeProvider = timeProvider;
this.cachePolicy = cachePolicy;
this.metadataProcessor = metadataProcessor;
this.moduleIdentifierFactory = moduleIdentifierFactory;
}
public String getId() {
return delegate.getId();
}
public String getName() {
return delegate.getName();
}
@Override
public String toString() {
return delegate.toString();
}
public ModuleComponentRepositoryAccess getLocalAccess() {
return locateInCacheRepositoryAccess;
}
public ModuleComponentRepositoryAccess getRemoteAccess() {
return resolveAndCacheRepositoryAccess;
}
public ComponentMetadataSupplier createMetadataSupplier() {
return delegate.createMetadataSupplier();
}
private ModuleIdentifier getCacheKey(ModuleVersionSelector requested) {
return moduleIdentifierFactory.module(requested.getGroup(), requested.getName());
}
private class LocateInCacheRepositoryAccess implements ModuleComponentRepositoryAccess {
@Override
public String toString() {
return "cache lookup for " + delegate.toString();
}
@Override
public void listModuleVersions(DependencyMetadata dependency, BuildableModuleVersionListingResolveResult result) {
// First try to determine the artifacts in-memory (e.g using the metadata): don't use the cache in this case
delegate.getLocalAccess().listModuleVersions(dependency, result);
if (result.hasResult()) {
return;
}
listModuleVersionsFromCache(dependency, result);
}
private void listModuleVersionsFromCache(DependencyMetadata dependency, BuildableModuleVersionListingResolveResult result) {
ModuleVersionSelector requested = dependency.getRequested();
final ModuleIdentifier moduleId = getCacheKey(requested);
ModuleVersionsCache.CachedModuleVersionList cachedModuleVersionList = moduleVersionsCache.getCachedModuleResolution(delegate, moduleId);
if (cachedModuleVersionList != null) {
Set<String> versionList = cachedModuleVersionList.getModuleVersions();
Set<ModuleVersionIdentifier> versions = CollectionUtils.collect(versionList, new Transformer<ModuleVersionIdentifier, String>() {
public ModuleVersionIdentifier transform(String original) {
return new DefaultModuleVersionIdentifier(moduleId, original);
}
});
if (cachePolicy.mustRefreshVersionList(moduleId, versions, cachedModuleVersionList.getAgeMillis())) {
LOGGER.debug("Version listing in dynamic revision cache is expired: will perform fresh resolve of '{}' in '{}'", requested, delegate.getName());
} else {
result.listed(versionList);
// When age == 0, verified since the start of this build, assume listing hasn't changed
result.setAuthoritative(cachedModuleVersionList.getAgeMillis() == 0);
}
}
}
@Override
public void resolveComponentMetaData(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata requestMetaData, BuildableModuleComponentMetaDataResolveResult result) {
// First try to determine the artifacts in-memory (e.g using the metadata): don't use the cache in this case
delegate.getLocalAccess().resolveComponentMetaData(moduleComponentIdentifier, requestMetaData, result);
if (result.hasResult()) {
return;
}
resolveComponentMetaDataFromCache(moduleComponentIdentifier, requestMetaData, result);
}
private void resolveComponentMetaDataFromCache(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata requestMetaData, BuildableModuleComponentMetaDataResolveResult result) {
ModuleMetaDataCache.CachedMetaData cachedMetaData = moduleMetaDataCache.getCachedModuleDescriptor(delegate, moduleComponentIdentifier);
if (cachedMetaData == null) {
return;
}
if (cachedMetaData.isMissing()) {
if (cachePolicy.mustRefreshMissingModule(moduleComponentIdentifier, cachedMetaData.getAgeMillis())) {
LOGGER.debug("Cached meta-data for missing module is expired: will perform fresh resolve of '{}' in '{}'", moduleComponentIdentifier, delegate.getName());
return;
}
LOGGER.debug("Detected non-existence of module '{}' in resolver cache '{}'", moduleComponentIdentifier, delegate.getName());
result.missing();
// When age == 0, verified since the start of this build, assume still missing
result.setAuthoritative(cachedMetaData.getAgeMillis() == 0);
return;
}
ModuleComponentResolveMetadata metaData = cachedMetaData.getMetaData();
metaData = metadataProcessor.processMetadata(metaData);
if (requestMetaData.isChanging() || metaData.isChanging()) {
if (cachePolicy.mustRefreshChangingModule(moduleComponentIdentifier, cachedMetaData.getModuleVersion(), cachedMetaData.getAgeMillis())) {
LOGGER.debug("Cached meta-data for changing module is expired: will perform fresh resolve of '{}' in '{}'", moduleComponentIdentifier, delegate.getName());
return;
}
LOGGER.debug("Found cached version of changing module '{}' in '{}'", moduleComponentIdentifier, delegate.getName());
} else {
if (cachePolicy.mustRefreshModule(moduleComponentIdentifier, cachedMetaData.getModuleVersion(), cachedMetaData.getAgeMillis())) {
LOGGER.debug("Cached meta-data for module must be refreshed: will perform fresh resolve of '{}' in '{}'", moduleComponentIdentifier, delegate.getName());
return;
}
}
LOGGER.debug("Using cached module metadata for module '{}' in '{}'", moduleComponentIdentifier, delegate.getName());
metaData = metaData.withSource(new CachingModuleSource(cachedMetaData.getDescriptorHash(), metaData.isChanging(), metaData.getSource()));
result.resolved(metaData);
// When age == 0, verified since the start of this build, assume the meta-data hasn't changed
result.setAuthoritative(cachedMetaData.getAgeMillis() == 0);
}
@Override
public void resolveArtifactsWithType(ComponentResolveMetadata component, ArtifactType artifactType, BuildableArtifactSetResolveResult result) {
final CachingModuleSource cachedModuleSource = (CachingModuleSource) component.getSource();
// First try to determine the artifacts in-memory (e.g using the metadata): don't use the cache in this case
delegate.getLocalAccess().resolveArtifactsWithType(component.withSource(cachedModuleSource.getDelegate()), artifactType, result);
if (result.hasResult()) {
return;
}
resolveModuleArtifactsFromCache(cacheKey(artifactType), component, result, cachedModuleSource);
}
@Override
public void resolveArtifacts(ComponentResolveMetadata component, BuildableComponentArtifactsResolveResult result) {
final CachingModuleSource cachedModuleSource = (CachingModuleSource) component.getSource();
// First try to determine the artifacts in-memory (e.g using the metadata): don't use the cache in this case
delegate.getLocalAccess().resolveArtifacts(component.withSource(cachedModuleSource.getDelegate()), result);
if (result.hasResult()) {
return;
}
DefaultBuildableArtifactSetResolveResult artifactsResolveResult = new DefaultBuildableArtifactSetResolveResult();
resolveModuleArtifactsFromCache("component:", component, artifactsResolveResult, cachedModuleSource);
if (artifactsResolveResult.hasResult()) {
result.resolved(new FixedComponentArtifacts(artifactsResolveResult.getResult()));
}
}
private void resolveModuleArtifactsFromCache(String contextId, ComponentResolveMetadata component, BuildableArtifactSetResolveResult result, CachingModuleSource cachedModuleSource) {
ModuleArtifactsCache.CachedArtifacts cachedModuleArtifacts = moduleArtifactsCache.getCachedArtifacts(delegate, component.getComponentId(), contextId);
BigInteger moduleDescriptorHash = cachedModuleSource.getDescriptorHash();
if (cachedModuleArtifacts != null) {
if (!cachePolicy.mustRefreshModuleArtifacts(component.getId(), null, cachedModuleArtifacts.getAgeMillis(),
cachedModuleSource.isChangingModule(), moduleDescriptorHash.equals(cachedModuleArtifacts.getDescriptorHash()))) {
result.resolved(cachedModuleArtifacts.getArtifacts());
return;
}
LOGGER.debug("Artifact listing has expired: will perform fresh resolve of '{}' for '{}' in '{}'", contextId, component.getId(), delegate.getName());
}
}
@Override
public void resolveArtifact(ComponentArtifactMetadata artifact, ModuleSource moduleSource, BuildableArtifactResolveResult result) {
final CachingModuleSource cachedModuleSource = (CachingModuleSource) moduleSource;
// First try to determine the artifacts in-memory (e.g using the metadata): don't use the cache in this case
delegate.getLocalAccess().resolveArtifact(artifact, cachedModuleSource.getDelegate(), result);
if (result.hasResult()) {
return;
}
resolveArtifactFromCache(artifact, cachedModuleSource, result);
}
@Override
public MetadataFetchingCost estimateMetadataFetchingCost(ModuleComponentIdentifier moduleComponentIdentifier) {
if (isMetadataAvailableInCacheAndUpToDate(moduleComponentIdentifier)) {
return MetadataFetchingCost.FAST;
}
return delegate.getRemoteAccess().estimateMetadataFetchingCost(moduleComponentIdentifier);
}
private boolean isMetadataAvailableInCacheAndUpToDate(ModuleComponentIdentifier moduleComponentIdentifier) {
ModuleMetaDataCache.CachedMetaData cachedMetaData = moduleMetaDataCache.getCachedModuleDescriptor(delegate, moduleComponentIdentifier);
if (cachedMetaData == null) {
return false;
}
if (cachedMetaData.isMissing()) {
if (cachePolicy.mustRefreshMissingModule(moduleComponentIdentifier, cachedMetaData.getAgeMillis())) {
return false;
}
return true;
}
ModuleComponentResolveMetadata metaData = cachedMetaData.getMetaData();
metaData = metadataProcessor.processMetadata(metaData);
if (metaData.isChanging()) {
if (cachePolicy.mustRefreshChangingModule(moduleComponentIdentifier, cachedMetaData.getModuleVersion(), cachedMetaData.getAgeMillis())) {
return false;
}
} else {
if (cachePolicy.mustRefreshModule(moduleComponentIdentifier, cachedMetaData.getModuleVersion(), cachedMetaData.getAgeMillis())) {
return false;
}
}
return true;
}
private void resolveArtifactFromCache(ComponentArtifactMetadata artifact, CachingModuleSource moduleSource, BuildableArtifactResolveResult result) {
CachedArtifact cached = artifactAtRepositoryCachedResolutionIndex.lookup(artifactCacheKey(artifact));
final BigInteger descriptorHash = moduleSource.getDescriptorHash();
if (cached != null) {
long age = timeProvider.getCurrentTime() - cached.getCachedAt();
final boolean isChangingModule = moduleSource.isChangingModule();
ArtifactIdentifier artifactIdentifier = ((ModuleComponentArtifactMetadata) artifact).toArtifactIdentifier();
if (cached.isMissing()) {
if (!cachePolicy.mustRefreshArtifact(artifactIdentifier, null, age, isChangingModule, descriptorHash.equals(cached.getDescriptorHash()))) {
LOGGER.debug("Detected non-existence of artifact '{}' in resolver cache", artifact);
for (String location : cached.attemptedLocations()) {
result.attempted(location);
}
result.notFound(artifact.getId());
}
} else {
File cachedArtifactFile = cached.getCachedFile();
if (!cachePolicy.mustRefreshArtifact(artifactIdentifier, cachedArtifactFile, age, isChangingModule, descriptorHash.equals(cached.getDescriptorHash()))) {
LOGGER.debug("Found artifact '{}' in resolver cache: {}", artifact, cachedArtifactFile);
result.resolved(cachedArtifactFile);
}
}
}
}
}
private class ResolveAndCacheRepositoryAccess implements ModuleComponentRepositoryAccess {
@Override
public String toString() {
return "cache > " + delegate.getRemoteAccess().toString();
}
@Override
public void listModuleVersions(DependencyMetadata dependency, BuildableModuleVersionListingResolveResult result) {
delegate.getRemoteAccess().listModuleVersions(dependency, result);
switch (result.getState()) {
case Listed:
ModuleIdentifier moduleId = getCacheKey(dependency.getRequested());
Set<String> versionList = result.getVersions();
moduleVersionsCache.cacheModuleVersionList(delegate, moduleId, versionList);
break;
case Failed:
break;
default:
throw new IllegalStateException("Unexpected state on listModuleVersions: " + result.getState());
}
}
@Override
public void resolveComponentMetaData(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata requestMetaData, BuildableModuleComponentMetaDataResolveResult result) {
ComponentOverrideMetadata forced = requestMetaData.withChanging();
delegate.getRemoteAccess().resolveComponentMetaData(moduleComponentIdentifier, forced, result);
switch (result.getState()) {
case Missing:
moduleMetaDataCache.cacheMissing(delegate, moduleComponentIdentifier);
break;
case Resolved:
ModuleComponentResolveMetadata metaData = result.getMetaData();
ModuleSource moduleSource = metaData.getSource();
ModuleMetaDataCache.CachedMetaData cachedMetaData = moduleMetaDataCache.cacheMetaData(delegate, metaData);
metaData = metadataProcessor.processMetadata(metaData);
moduleSource = new CachingModuleSource(cachedMetaData.getDescriptorHash(), requestMetaData.isChanging() || metaData.isChanging(), moduleSource);
metaData = metaData.withSource(moduleSource);
result.resolved(metaData);
break;
case Failed:
break;
default:
throw new IllegalStateException("Unexpected resolve state: " + result.getState());
}
}
@Override
public void resolveArtifactsWithType(ComponentResolveMetadata component, ArtifactType artifactType, BuildableArtifactSetResolveResult result) {
final CachingModuleSource moduleSource = (CachingModuleSource) component.getSource();
delegate.getRemoteAccess().resolveArtifactsWithType(component.withSource(moduleSource.getDelegate()), artifactType, result);
if (result.getFailure() == null) {
moduleArtifactsCache.cacheArtifacts(delegate, component.getComponentId(), cacheKey(artifactType), moduleSource.getDescriptorHash(), result.getResult());
}
}
@Override
public void resolveArtifacts(ComponentResolveMetadata component, BuildableComponentArtifactsResolveResult result) {
final CachingModuleSource moduleSource = (CachingModuleSource) component.getSource();
delegate.getRemoteAccess().resolveArtifacts(component.withSource(moduleSource.getDelegate()), result);
if (result.getFailure() == null) {
FixedComponentArtifacts artifacts = (FixedComponentArtifacts) result.getResult();
moduleArtifactsCache.cacheArtifacts(delegate, component.getComponentId(), "component:", moduleSource.getDescriptorHash(), artifacts.getArtifacts());
}
}
@Override
public void resolveArtifact(ComponentArtifactMetadata artifact, ModuleSource moduleSource, BuildableArtifactResolveResult result) {
final CachingModuleSource cachingModuleSource = (CachingModuleSource) moduleSource;
delegate.getRemoteAccess().resolveArtifact(artifact, cachingModuleSource.getDelegate(), result);
LOGGER.debug("Downloaded artifact '{}' from resolver: {}", artifact, delegate.getName());
ArtifactResolveException failure = result.getFailure();
if (failure == null) {
artifactAtRepositoryCachedResolutionIndex.store(artifactCacheKey(artifact), result.getResult(), cachingModuleSource.getDescriptorHash());
} else if (failure instanceof ArtifactNotFoundException) {
artifactAtRepositoryCachedResolutionIndex.storeMissing(artifactCacheKey(artifact), result.getAttempted(), cachingModuleSource.getDescriptorHash());
}
}
@Override
public MetadataFetchingCost estimateMetadataFetchingCost(ModuleComponentIdentifier moduleComponentIdentifier) {
return delegate.getLocalAccess().estimateMetadataFetchingCost(moduleComponentIdentifier);
}
}
private String cacheKey(ArtifactType artifactType) {
return "artifacts:" + artifactType.name();
}
private ArtifactAtRepositoryKey artifactCacheKey(ComponentArtifactMetadata artifact) {
return new ArtifactAtRepositoryKey(delegate.getId(), artifact.getId());
}
static class CachingModuleSource implements ModuleSource {
private final BigInteger descriptorHash;
private final boolean changingModule;
private final ModuleSource delegate;
public CachingModuleSource(BigInteger descriptorHash, boolean changingModule, ModuleSource delegate) {
this.delegate = delegate;
this.descriptorHash = descriptorHash;
this.changingModule = changingModule;
}
@Override
public String toString() {
return "{descriptor: " + descriptorHash + ", changing: " + changingModule + ", source: " + delegate + "}";
}
public BigInteger getDescriptorHash() {
return descriptorHash;
}
public boolean isChangingModule() {
return changingModule;
}
public ModuleSource getDelegate() {
return delegate;
}
}
}