/*
* 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.repositories.transport;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.credentials.Credentials;
import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.StartParameterResolutionOverride;
import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.DefaultExternalResourceCachePolicy;
import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.ExternalResourceCachePolicy;
import org.gradle.api.internal.file.TemporaryFileProvider;
import org.gradle.authentication.Authentication;
import org.gradle.cache.internal.ProducerGuard;
import org.gradle.internal.authentication.AuthenticationInternal;
import org.gradle.internal.logging.progress.ProgressLoggerFactory;
import org.gradle.internal.operations.BuildOperationExecutor;
import org.gradle.internal.resource.cached.CachedExternalResourceIndex;
import org.gradle.internal.resource.connector.ResourceConnectorFactory;
import org.gradle.internal.resource.connector.ResourceConnectorSpecification;
import org.gradle.internal.resource.transfer.ExternalResourceConnector;
import org.gradle.internal.resource.transport.ResourceConnectorRepositoryTransport;
import org.gradle.internal.resource.transport.file.FileTransport;
import org.gradle.util.BuildCommencedTimeProvider;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public class RepositoryTransportFactory {
private final List<ResourceConnectorFactory> registeredProtocols = Lists.newArrayList();
private final TemporaryFileProvider temporaryFileProvider;
private final CachedExternalResourceIndex<String> cachedExternalResourceIndex;
private final ProgressLoggerFactory progressLoggerFactory;
private final BuildCommencedTimeProvider timeProvider;
private final CacheLockingManager cacheLockingManager;
private final BuildOperationExecutor buildOperationExecutor;
private final StartParameterResolutionOverride startParameterResolutionOverride;
private final ProducerGuard<URI> producerGuard;
public RepositoryTransportFactory(Collection<ResourceConnectorFactory> resourceConnectorFactory,
ProgressLoggerFactory progressLoggerFactory,
TemporaryFileProvider temporaryFileProvider,
CachedExternalResourceIndex<String> cachedExternalResourceIndex,
BuildCommencedTimeProvider timeProvider,
CacheLockingManager cacheLockingManager,
BuildOperationExecutor buildOperationExecutor,
StartParameterResolutionOverride startParameterResolutionOverride,
ProducerGuard<URI> producerGuard) {
this.progressLoggerFactory = progressLoggerFactory;
this.temporaryFileProvider = temporaryFileProvider;
this.cachedExternalResourceIndex = cachedExternalResourceIndex;
this.timeProvider = timeProvider;
this.cacheLockingManager = cacheLockingManager;
this.buildOperationExecutor = buildOperationExecutor;
this.startParameterResolutionOverride = startParameterResolutionOverride;
this.producerGuard = producerGuard;
for (ResourceConnectorFactory connectorFactory : resourceConnectorFactory) {
register(connectorFactory);
}
}
public void register(ResourceConnectorFactory resourceConnectorFactory) {
registeredProtocols.add(resourceConnectorFactory);
}
public Set<String> getRegisteredProtocols() {
Set<String> validSchemes = Sets.newLinkedHashSet();
for (ResourceConnectorFactory registeredProtocol : registeredProtocols) {
validSchemes.addAll(registeredProtocol.getSupportedProtocols());
}
return validSchemes;
}
public RepositoryTransport createTransport(String scheme, String name, Collection<Authentication> authentications) {
return createTransport(Collections.singleton(scheme), name, authentications);
}
public RepositoryTransport createTransport(Set<String> schemes, String name, Collection<Authentication> authentications) {
validateSchemes(schemes);
ResourceConnectorFactory connectorFactory = findConnectorFactory(schemes);
// Ensure resource transport protocol, authentication types and credentials are all compatible
validateConnectorFactoryCredentials(schemes, connectorFactory, authentications);
// File resources are handled slightly differently at present.
// file:// repos are treated differently
// 1) we don't cache their files
// 2) we don't do progress logging for "downloading"
if (Collections.singleton("file").containsAll(schemes)) {
return new FileTransport(name);
}
ResourceConnectorSpecification connectionDetails = new DefaultResourceConnectorSpecification(authentications);
ExternalResourceConnector resourceConnector = connectorFactory.createResourceConnector(connectionDetails);
resourceConnector = startParameterResolutionOverride.overrideExternalResourceConnnector(resourceConnector);
ExternalResourceCachePolicy cachePolicy = new DefaultExternalResourceCachePolicy();
cachePolicy = startParameterResolutionOverride.overrideExternalResourceCachePolicy(cachePolicy);
return new ResourceConnectorRepositoryTransport(name, progressLoggerFactory, temporaryFileProvider, cachedExternalResourceIndex, timeProvider, cacheLockingManager, resourceConnector, buildOperationExecutor, cachePolicy, producerGuard);
}
private void validateSchemes(Set<String> schemes) {
Set<String> validSchemes = getRegisteredProtocols();
for (String scheme : schemes) {
if (!validSchemes.contains(scheme)) {
throw new InvalidUserDataException(String.format("Not a supported repository protocol '%s': valid protocols are %s", scheme, validSchemes));
}
}
}
private void validateConnectorFactoryCredentials(Set<String> schemes, ResourceConnectorFactory factory, Collection<Authentication> authentications) {
Set<Class<? extends Authentication>> configuredAuthenticationTypes = Sets.newHashSet();
for (Authentication authentication : authentications) {
AuthenticationInternal authenticationInternal = (AuthenticationInternal) authentication;
boolean isAuthenticationSupported = false;
Credentials credentials = authenticationInternal.getCredentials();
boolean needCredentials = authenticationInternal.requiresCredentials();
for (Class<?> authenticationType : factory.getSupportedAuthentication()) {
if (authenticationType.isAssignableFrom(authentication.getClass())) {
isAuthenticationSupported = true;
break;
}
}
if (!isAuthenticationSupported) {
throw new InvalidUserDataException(String.format("Authentication scheme %s is not supported by protocol '%s'",
authentication, schemes.iterator().next()));
}
if (credentials != null) {
if (!((AuthenticationInternal) authentication).supports(credentials)) {
throw new InvalidUserDataException(String.format("Credentials type of '%s' is not supported by authentication scheme %s",
credentials.getClass().getSimpleName(), authentication));
}
} else {
if (needCredentials) {
throw new InvalidUserDataException("You cannot configure authentication schemes for this repository type if no credentials are provided.");
}
}
if (!configuredAuthenticationTypes.add(authenticationInternal.getType())) {
throw new InvalidUserDataException(String.format("You cannot configure multiple authentication schemes of the same type. The duplicate one is %s.", authentication));
}
}
}
private ResourceConnectorFactory findConnectorFactory(Set<String> schemes) {
for (ResourceConnectorFactory protocolRegistration : registeredProtocols) {
if (protocolRegistration.getSupportedProtocols().containsAll(schemes)) {
return protocolRegistration;
}
}
throw new InvalidUserDataException("You cannot mix different URL schemes for a single repository. Please declare separate repositories.");
}
private class DefaultResourceConnectorSpecification implements ResourceConnectorSpecification {
private final Collection<Authentication> authentications;
private DefaultResourceConnectorSpecification(Collection<Authentication> authentications) {
this.authentications = authentications;
}
@Override
public <T> T getCredentials(Class<T> type) {
if (authentications == null || authentications.size() < 1) {
return null;
}
Credentials credentials = ((AuthenticationInternal)authentications.iterator().next()).getCredentials();
if(credentials == null) {
return null;
}
if (type.isAssignableFrom(credentials.getClass())) {
return type.cast(credentials);
} else {
throw new IllegalArgumentException(String.format("Credentials must be an instance of '%s'.", type.getCanonicalName()));
}
}
@Override
public Collection<Authentication> getAuthentications() {
return authentications;
}
}
}