1 package org.apache.archiva.maven.proxy;
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
21 import org.apache.archiva.maven.common.proxy.WagonFactory;
22 import org.apache.archiva.maven.common.proxy.WagonFactoryException;
23 import org.apache.archiva.maven.common.proxy.WagonFactoryRequest;
24 import org.apache.archiva.proxy.DefaultRepositoryProxyHandler;
25 import org.apache.archiva.proxy.NotFoundException;
26 import org.apache.archiva.proxy.NotModifiedException;
27 import org.apache.archiva.proxy.ProxyException;
28 import org.apache.archiva.proxy.model.NetworkProxy;
29 import org.apache.archiva.proxy.model.ProxyConnector;
30 import org.apache.archiva.proxy.model.RepositoryProxyHandler;
31 import org.apache.archiva.repository.ManagedRepository;
32 import org.apache.archiva.repository.RemoteRepository;
33 import org.apache.archiva.repository.RepositoryCredentials;
34 import org.apache.archiva.repository.RepositoryType;
35 import org.apache.archiva.repository.base.PasswordCredentials;
36 import org.apache.archiva.repository.storage.StorageAsset;
37 import org.apache.commons.lang3.StringUtils;
38 import org.apache.maven.wagon.ConnectionException;
39 import org.apache.maven.wagon.ResourceDoesNotExistException;
40 import org.apache.maven.wagon.Wagon;
41 import org.apache.maven.wagon.WagonException;
42 import org.apache.maven.wagon.authentication.AuthenticationException;
43 import org.apache.maven.wagon.authentication.AuthenticationInfo;
44 import org.apache.maven.wagon.proxy.ProxyInfo;
45 import org.apache.maven.wagon.repository.Repository;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48 import org.springframework.stereotype.Service;
50 import javax.inject.Inject;
51 import java.io.IOException;
53 import java.nio.file.Files;
54 import java.nio.file.Path;
55 import java.util.ArrayList;
56 import java.util.List;
58 import java.util.concurrent.ConcurrentHashMap;
59 import java.util.concurrent.ConcurrentMap;
62 * DefaultRepositoryProxyHandler
63 * TODO exception handling needs work - "not modified" is not really an exceptional case, and it has more layers than
64 * your average brown onion
66 @Service( "repositoryProxyHandler#maven" )
67 public class MavenRepositoryProxyHandler extends DefaultRepositoryProxyHandler {
69 private static final Logger log = LoggerFactory.getLogger( MavenRepositoryProxyHandler.class );
71 private static final List<RepositoryType> REPOSITORY_TYPES = new ArrayList<>();
74 REPOSITORY_TYPES.add(RepositoryType.MAVEN);
78 private WagonFactory wagonFactory;
80 private ConcurrentMap<String, ProxyInfo> networkProxyMap = new ConcurrentHashMap<>();
83 public void initialize() {
87 private void updateWagonProxyInfo(Map<String, NetworkProxy> proxyList) {
88 this.networkProxyMap.clear();
89 for (Map.Entry<String, NetworkProxy> proxyEntry : proxyList.entrySet()) {
90 String key = proxyEntry.getKey();
91 NetworkProxy networkProxyDef = proxyEntry.getValue();
93 ProxyInfo proxy = new ProxyInfo();
95 proxy.setType(networkProxyDef.getProtocol());
96 proxy.setHost(networkProxyDef.getHost());
97 proxy.setPort(networkProxyDef.getPort());
98 proxy.setUserName(networkProxyDef.getUsername());
99 proxy.setPassword(new String(networkProxyDef.getPassword()));
101 this.networkProxyMap.put(key, proxy);
106 public void setNetworkProxies(Map<String, NetworkProxy> networkProxies ) {
107 super.setNetworkProxies( networkProxies );
108 updateWagonProxyInfo( networkProxies );
113 * @param remoteRepository
115 * @param checksumFiles
119 * @param workingDirectory
121 * @throws ProxyException
122 * @throws NotModifiedException
125 protected void transferResources( ProxyConnector connector, RemoteRepository remoteRepository,
126 StorageAsset tmpResource, StorageAsset[] checksumFiles, String url, String remotePath, StorageAsset resource,
127 Path workingDirectory, ManagedRepository repository )
128 throws ProxyException, NotModifiedException {
131 URI repoUrl = remoteRepository.getLocation( );
132 String protocol = repoUrl.getScheme( );
133 NetworkProxy networkProxy = null;
134 String proxyId = connector.getProxyId();
135 if (StringUtils.isNotBlank(proxyId)) {
137 networkProxy = getNetworkProxy(proxyId);
139 WagonFactoryRequest wagonFactoryRequest = new WagonFactoryRequest("wagon#" + protocol,
140 remoteRepository.getExtraHeaders());
141 if (networkProxy == null) {
143 log.warn("No network proxy with id {} found for connector {}->{}", proxyId,
144 connector.getSourceRepository().getId(), connector.getTargetRepository().getId());
146 wagonFactoryRequest = wagonFactoryRequest.networkProxy(networkProxy);
148 wagon = wagonFactory.getWagon(wagonFactoryRequest);
150 throw new ProxyException("Unsupported target repository protocol: " + protocol);
153 boolean connected = connectToRepository(connector, wagon, remoteRepository);
155 transferArtifact(wagon, remoteRepository, remotePath, resource.getFilePath(),
158 // TODO: these should be used to validate the download based on the policies, not always downloaded
160 // save on connections since md5 is rarely used
161 for ( StorageAsset checksumFile : checksumFiles )
163 String ext = "." + StringUtils.substringAfterLast( checksumFile.getName( ), "." );
164 transferChecksum( wagon, remoteRepository, remotePath, resource.getFilePath( ), ext,
165 checksumFile.getFilePath( ) );
169 catch (NotModifiedException e) {
170 // Do not cache url here.
173 catch ( ProxyException e) {
174 urlFailureCache.cacheFailure(url);
177 catch ( WagonFactoryException e) {
178 throw new ProxyException(e.getMessage(), e);
183 } catch (ConnectionException e) {
184 log.warn("Unable to disconnect wagon.", e);
190 protected void transferArtifact( Wagon wagon, RemoteRepository remoteRepository, String remotePath,
192 StorageAsset destFile )
193 throws ProxyException {
194 transferSimpleFile(wagon, remoteRepository, remotePath, resource, destFile.getFilePath());
199 * Quietly transfer the checksum file from the remote repository to the local file.
202 * @param wagon the wagon instance (should already be connected) to use.
203 * @param remoteRepository the remote repository to transfer from.
204 * @param remotePath the remote path to the resource to get.
205 * @param resource the local file that should contain the downloaded contents
206 * @param ext the type of checksum to transfer (example: ".md5" or ".sha1")
207 * @throws ProxyException if copying the downloaded file into place did not succeed.
209 protected void transferChecksum( Wagon wagon, RemoteRepository remoteRepository, String remotePath,
210 Path resource, String ext,
212 throws ProxyException {
213 String url = remoteRepository.getLocation().toString() + remotePath + ext;
215 // Transfer checksum does not use the policy.
216 if (urlFailureCache.hasFailedBefore(url)) {
221 transferSimpleFile(wagon, remoteRepository, remotePath + ext, resource, destFile);
222 log.debug("Checksum {} Downloaded: {} to move to {}", url, destFile, resource);
223 } catch (NotFoundException e) {
224 urlFailureCache.cacheFailure(url);
225 log.debug("Transfer failed, checksum not found: {}", url);
226 // Consume it, do not pass this on.
227 } catch (NotModifiedException e) {
228 log.debug("Transfer skipped, checksum not modified: {}", url);
229 // Consume it, do not pass this on.
230 } catch (ProxyException e) {
231 urlFailureCache.cacheFailure(url);
232 log.warn("Transfer failed on checksum: {} : {}", url, e.getMessage(), e);
233 // Critical issue, pass it on.
239 * Perform the transfer of the remote file to the local file specified.
241 * @param wagon the wagon instance to use.
242 * @param remoteRepository the remote repository to use
243 * @param remotePath the remote path to attempt to get
244 * @param origFile the local file to save to
245 * @throws ProxyException if there was a problem moving the downloaded file into place.
247 protected void transferSimpleFile( Wagon wagon, RemoteRepository remoteRepository, String remotePath,
248 Path origFile, Path destFile )
249 throws ProxyException {
250 assert (remotePath != null);
252 // Transfer the file.
256 if (!Files.exists(origFile)) {
257 log.debug("Retrieving {} from {}", remotePath, remoteRepository.getId());
258 wagon.get(addParameters(remotePath, remoteRepository), destFile.toFile());
260 // You wouldn't get here on failure, a WagonException would have been thrown.
261 log.debug("Downloaded successfully.");
264 log.debug("Retrieving {} from {} if updated", remotePath, remoteRepository.getId());
266 success = wagon.getIfNewer(addParameters(remotePath, remoteRepository), destFile.toFile(),
267 Files.getLastModifiedTime(origFile).toMillis());
268 } catch (IOException e) {
269 throw new ProxyException("Failed to the modification time of " + origFile.toAbsolutePath());
272 throw new NotModifiedException(
273 "Not downloaded, as local file is newer than remote side: " + origFile.toAbsolutePath());
276 if (Files.exists(destFile)) {
277 log.debug("Downloaded successfully.");
280 } catch (ResourceDoesNotExistException e) {
281 throw new NotFoundException(
282 "Resource [" + remoteRepository.getLocation() + "/" + remotePath + "] does not exist: " + e.getMessage(),
284 } catch (WagonException e) {
285 // TODO: shouldn't have to drill into the cause, but TransferFailedException is often not descriptive enough
288 "Download failure on resource [" + remoteRepository.getLocation() + "/" + remotePath + "]:" + e.getMessage();
289 if (e.getCause() != null) {
290 msg += " (cause: " + e.getCause() + ")";
292 throw new ProxyException(msg, e);
297 * Using wagon, connect to the remote repository.
299 * @param connector the connector configuration to utilize (for obtaining network proxy configuration from)
300 * @param wagon the wagon instance to establish the connection on.
301 * @param remoteRepository the remote repository to connect to.
302 * @return true if the connection was successful. false if not connected.
304 protected boolean connectToRepository(ProxyConnector connector, Wagon wagon,
305 RemoteRepository remoteRepository) {
306 final ProxyInfo networkProxy =
307 connector.getProxyId() == null ? null : this.networkProxyMap.get(connector.getProxyId());
309 if (log.isDebugEnabled()) {
310 if (networkProxy != null) {
311 // TODO: move to proxyInfo.toString()
312 String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
313 + " to connect to remote repository " + remoteRepository.getLocation();
314 if (networkProxy.getNonProxyHosts() != null) {
315 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
317 if (StringUtils.isNotBlank(networkProxy.getUserName())) {
318 msg += "; as user: " + networkProxy.getUserName();
324 AuthenticationInfo authInfo = null;
325 String username = "";
326 String password = "";
327 RepositoryCredentials repCred = remoteRepository.getLoginCredentials();
328 if (repCred != null && repCred instanceof PasswordCredentials ) {
329 PasswordCredentials pwdCred = (PasswordCredentials) repCred;
330 username = pwdCred.getUsername();
331 password = pwdCred.getPassword() == null ? "" : new String(pwdCred.getPassword());
334 if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password)) {
335 log.debug("Using username {} to connect to remote repository {}", username, remoteRepository.getLocation());
336 authInfo = new AuthenticationInfo();
337 authInfo.setUserName(username);
338 authInfo.setPassword(password);
341 // Convert seconds to milliseconds
343 long timeoutInMilliseconds = remoteRepository.getTimeout().toMillis();
345 // Set timeout read and connect
346 // FIXME olamy having 2 config values
347 wagon.setReadTimeout((int) timeoutInMilliseconds);
348 wagon.setTimeout((int) timeoutInMilliseconds);
351 Repository wagonRepository =
352 new Repository(remoteRepository.getId(), remoteRepository.getLocation().toString());
353 wagon.connect(wagonRepository, authInfo, networkProxy);
355 } catch (ConnectionException | AuthenticationException e) {
356 log.warn("Could not connect to {}: {}", remoteRepository.getId(), e.getMessage());
363 public WagonFactory getWagonFactory() {
367 public void setWagonFactory(WagonFactory wagonFactory) {
368 this.wagonFactory = wagonFactory;
372 public List<RepositoryType> supports() {
373 return REPOSITORY_TYPES;
377 public void addNetworkproxy( String id, NetworkProxy networkProxy )
383 public <T extends RepositoryProxyHandler> T getHandler( Class<T> clazz ) throws IllegalArgumentException
385 if (clazz.isAssignableFrom( this.getClass() )) {
388 throw new IllegalArgumentException( "This Proxy Handler is no subclass of " + clazz );