/*
* 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.component.ComponentIdentifier;
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
import org.gradle.api.internal.artifacts.repositories.resolver.MetadataFetchingCost;
import org.gradle.internal.component.external.model.ModuleComponentResolveMetadata;
import org.gradle.internal.component.model.ComponentOverrideMetadata;
import org.gradle.internal.resolve.ModuleVersionResolveException;
import org.gradle.internal.resolve.resolver.ComponentMetaDataResolver;
import org.gradle.internal.resolve.result.BuildableComponentResolveResult;
import org.gradle.internal.resolve.result.BuildableModuleComponentMetaDataResolveResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
public class RepositoryChainComponentMetaDataResolver implements ComponentMetaDataResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(RepositoryChainComponentMetaDataResolver.class);
private final List<ModuleComponentRepository> repositories = new ArrayList<ModuleComponentRepository>();
private final List<String> repositoryNames = new ArrayList<String>();
private final VersionedComponentChooser versionedComponentChooser;
private final Transformer<ModuleComponentResolveMetadata, RepositoryChainModuleResolution> metaDataFactory;
public RepositoryChainComponentMetaDataResolver(VersionedComponentChooser componentChooser, Transformer<ModuleComponentResolveMetadata, RepositoryChainModuleResolution> metaDataFactory) {
this.versionedComponentChooser = componentChooser;
this.metaDataFactory = metaDataFactory;
}
public void add(ModuleComponentRepository repository) {
repositories.add(repository);
repositoryNames.add(repository.getName());
}
public void resolve(ComponentIdentifier identifier, ComponentOverrideMetadata componentOverrideMetadata, BuildableComponentResolveResult result) {
if (!(identifier instanceof ModuleComponentIdentifier)) {
throw new UnsupportedOperationException("Can resolve meta-data for module components only.");
}
resolveModule((ModuleComponentIdentifier) identifier, componentOverrideMetadata, result);
}
@Override
public boolean isFetchingMetadataCheap(ComponentIdentifier identifier) {
if (identifier instanceof ModuleComponentIdentifier) {
for (ModuleComponentRepository repository : repositories) {
ModuleComponentRepositoryAccess localAccess = repository.getLocalAccess();
MetadataFetchingCost fetchingCost = localAccess.estimateMetadataFetchingCost((ModuleComponentIdentifier) identifier);
if (fetchingCost.isFast()) {
return true;
} else if (fetchingCost.isExpensive()) {
return false;
}
}
}
return true;
}
private void resolveModule(ModuleComponentIdentifier identifier, ComponentOverrideMetadata componentOverrideMetadata, BuildableComponentResolveResult result) {
LOGGER.debug("Attempting to resolve component for {} using repositories {}", identifier, repositoryNames);
List<Throwable> errors = new ArrayList<Throwable>();
List<ComponentMetaDataResolveState> resolveStates = new ArrayList<ComponentMetaDataResolveState>();
for (ModuleComponentRepository repository : repositories) {
resolveStates.add(new ComponentMetaDataResolveState(identifier, componentOverrideMetadata, repository, versionedComponentChooser));
}
final RepositoryChainModuleResolution latestResolved = findBestMatch(resolveStates, errors);
if (latestResolved != null) {
LOGGER.debug("Using {} from {}", latestResolved.module.getId(), latestResolved.repository);
for (Throwable error : errors) {
LOGGER.debug("Discarding resolve failure.", error);
}
result.resolved(metaDataFactory.transform(latestResolved));
return;
}
if (!errors.isEmpty()) {
result.failed(new ModuleVersionResolveException(identifier, errors));
} else {
for (ComponentMetaDataResolveState resolveState : resolveStates) {
resolveState.applyTo(result);
}
result.notFound(identifier);
}
}
private RepositoryChainModuleResolution findBestMatch(List<ComponentMetaDataResolveState> resolveStates, Collection<Throwable> failures) {
LinkedList<ComponentMetaDataResolveState> queue = new LinkedList<ComponentMetaDataResolveState>();
queue.addAll(resolveStates);
LinkedList<ComponentMetaDataResolveState> missing = new LinkedList<ComponentMetaDataResolveState>();
// A first pass to do local resolves only
RepositoryChainModuleResolution best = findBestMatch(queue, failures, missing);
if (best != null) {
return best;
}
// Nothing found - do a second pass
queue.addAll(missing);
missing.clear();
return findBestMatch(queue, failures, missing);
}
private RepositoryChainModuleResolution findBestMatch(LinkedList<ComponentMetaDataResolveState> queue, Collection<Throwable> failures, Collection<ComponentMetaDataResolveState> missing) {
RepositoryChainModuleResolution best = null;
while (!queue.isEmpty()) {
ComponentMetaDataResolveState request = queue.removeFirst();
BuildableModuleComponentMetaDataResolveResult metaDataResolveResult;
try {
metaDataResolveResult = request.resolve();
} catch (Throwable t) {
failures.add(t);
continue;
}
switch (metaDataResolveResult.getState()) {
case Failed:
failures.add(metaDataResolveResult.getFailure());
break;
case Missing:
// Queue this up for checking again later
if (request.canMakeFurtherAttempts()) {
missing.add(request);
}
break;
case Resolved:
RepositoryChainModuleResolution moduleResolution = new RepositoryChainModuleResolution(request.repository, metaDataResolveResult.getMetaData());
if (!metaDataResolveResult.getMetaData().isGenerated()) {
return moduleResolution;
}
best = best != null ? best : moduleResolution;
break;
default:
throw new IllegalStateException("Unexpected state for resolution: " + metaDataResolveResult.getState());
}
}
return best;
}
}