1 package org.apache.archiva.proxy.maven;
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
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
22 import org.apache.archiva.configuration.NetworkProxyConfiguration;
23 import org.apache.archiva.model.RepositoryURL;
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.repository.*;
31 import org.apache.archiva.repository.content.StorageAsset;
32 import org.apache.commons.lang.StringUtils;
33 import org.apache.maven.wagon.ConnectionException;
34 import org.apache.maven.wagon.ResourceDoesNotExistException;
35 import org.apache.maven.wagon.Wagon;
36 import org.apache.maven.wagon.WagonException;
37 import org.apache.maven.wagon.authentication.AuthenticationException;
38 import org.apache.maven.wagon.authentication.AuthenticationInfo;
39 import org.apache.maven.wagon.proxy.ProxyInfo;
40 import org.apache.maven.wagon.repository.Repository;
41 import org.springframework.stereotype.Service;
43 import javax.inject.Inject;
44 import java.io.IOException;
45 import java.nio.file.Files;
46 import java.nio.file.Path;
47 import java.util.ArrayList;
48 import java.util.List;
50 import java.util.concurrent.ConcurrentHashMap;
51 import java.util.concurrent.ConcurrentMap;
54 * DefaultRepositoryProxyHandler
55 * TODO exception handling needs work - "not modified" is not really an exceptional case, and it has more layers than
56 * your average brown onion
58 @Service("repositoryProxyConnectors#maven")
59 public class MavenRepositoryProxyHandler extends DefaultRepositoryProxyHandler {
61 private static final List<RepositoryType> REPOSITORY_TYPES = new ArrayList<>();
64 REPOSITORY_TYPES.add(RepositoryType.MAVEN);
68 private WagonFactory wagonFactory;
70 private ConcurrentMap<String, ProxyInfo> networkProxyMap = new ConcurrentHashMap<>();
73 public void initialize() {
77 private void updateWagonProxyInfo(Map<String, NetworkProxy> proxyList) {
78 this.networkProxyMap.clear();
79 List<NetworkProxyConfiguration> networkProxies = getArchivaConfiguration().getConfiguration().getNetworkProxies();
80 for (Map.Entry<String, NetworkProxy> proxyEntry : proxyList.entrySet()) {
81 String key = proxyEntry.getKey();
82 NetworkProxy networkProxyDef = proxyEntry.getValue();
84 ProxyInfo proxy = new ProxyInfo();
86 proxy.setType(networkProxyDef.getProtocol());
87 proxy.setHost(networkProxyDef.getHost());
88 proxy.setPort(networkProxyDef.getPort());
89 proxy.setUserName(networkProxyDef.getUsername());
90 proxy.setPassword(networkProxyDef.getPassword());
92 this.networkProxyMap.put(key, proxy);
97 public void setNetworkProxies(Map<String, NetworkProxy> proxies) {
98 super.setNetworkProxies(proxies);
99 updateWagonProxyInfo(proxies);
104 * @param remoteRepository
106 * @param checksumFiles
110 * @param workingDirectory
112 * @throws ProxyException
113 * @throws NotModifiedException
115 protected void transferResources( ProxyConnector connector, RemoteRepositoryContent remoteRepository,
116 Path tmpResource, Path[] checksumFiles, String url, String remotePath, StorageAsset resource,
117 Path workingDirectory, ManagedRepositoryContent repository )
118 throws ProxyException, NotModifiedException {
121 RepositoryURL repoUrl = remoteRepository.getURL();
122 String protocol = repoUrl.getProtocol();
123 NetworkProxy networkProxy = null;
124 String proxyId = connector.getProxyId();
125 if (StringUtils.isNotBlank(proxyId)) {
127 networkProxy = getNetworkProxy(proxyId);
129 WagonFactoryRequest wagonFactoryRequest = new WagonFactoryRequest("wagon#" + protocol,
130 remoteRepository.getRepository().getExtraHeaders());
131 if (networkProxy == null) {
133 log.warn("No network proxy with id {} found for connector {}->{}", proxyId,
134 connector.getSourceRepository().getId(), connector.getTargetRepository().getId());
136 wagonFactoryRequest = wagonFactoryRequest.networkProxy(networkProxy);
138 wagon = wagonFactory.getWagon(wagonFactoryRequest);
140 throw new ProxyException("Unsupported target repository protocol: " + protocol);
144 throw new ProxyException("Unsupported target repository protocol: " + protocol);
147 boolean connected = connectToRepository(connector, wagon, remoteRepository);
149 transferArtifact(wagon, remoteRepository, remotePath, repository, resource.getFilePath(), workingDirectory,
152 // TODO: these should be used to validate the download based on the policies, not always downloaded
154 // save on connections since md5 is rarely used
155 for (int i=0; i<checksumFiles.length; i++) {
156 String ext = "."+StringUtils.substringAfterLast( checksumFiles[i].getFileName( ).toString( ), "." );
157 transferChecksum(wagon, remoteRepository, remotePath, repository, resource.getFilePath(), ext,
161 } catch (NotFoundException e) {
162 urlFailureCache.cacheFailure(url);
164 } catch (NotModifiedException e) {
165 // Do not cache url here.
167 } catch (ProxyException e) {
168 urlFailureCache.cacheFailure(url);
170 } catch (WagonFactoryException e) {
171 throw new ProxyException(e.getMessage(), e);
176 } catch (ConnectionException e) {
177 log.warn("Unable to disconnect wagon.", e);
183 protected void transferArtifact(Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
184 ManagedRepositoryContent repository, Path resource, Path tmpDirectory,
186 throws ProxyException {
187 transferSimpleFile(wagon, remoteRepository, remotePath, repository, resource, destFile);
192 * Quietly transfer the checksum file from the remote repository to the local file.
195 * @param wagon the wagon instance (should already be connected) to use.
196 * @param remoteRepository the remote repository to transfer from.
197 * @param remotePath the remote path to the resource to get.
198 * @param repository the managed repository that will hold the file
199 * @param resource the local file that should contain the downloaded contents
200 * @param ext the type of checksum to transfer (example: ".md5" or ".sha1")
201 * @throws ProxyException if copying the downloaded file into place did not succeed.
203 protected void transferChecksum( Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
204 ManagedRepositoryContent repository, Path resource, String ext,
206 throws ProxyException {
207 String url = remoteRepository.getURL().getUrl() + remotePath + ext;
209 // Transfer checksum does not use the policy.
210 if (urlFailureCache.hasFailedBefore(url)) {
215 transferSimpleFile(wagon, remoteRepository, remotePath + ext, repository, resource, destFile);
216 log.debug("Checksum {} Downloaded: {} to move to {}", url, destFile, resource);
217 } catch (NotFoundException e) {
218 urlFailureCache.cacheFailure(url);
219 log.debug("Transfer failed, checksum not found: {}", url);
220 // Consume it, do not pass this on.
221 } catch (NotModifiedException e) {
222 log.debug("Transfer skipped, checksum not modified: {}", url);
223 // Consume it, do not pass this on.
224 } catch (ProxyException e) {
225 urlFailureCache.cacheFailure(url);
226 log.warn("Transfer failed on checksum: {} : {}", url, e.getMessage(), e);
227 // Critical issue, pass it on.
233 * Perform the transfer of the remote file to the local file specified.
235 * @param wagon the wagon instance to use.
236 * @param remoteRepository the remote repository to use
237 * @param remotePath the remote path to attempt to get
238 * @param repository the managed repository that will hold the file
239 * @param origFile the local file to save to
240 * @throws ProxyException if there was a problem moving the downloaded file into place.
242 protected void transferSimpleFile(Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
243 ManagedRepositoryContent repository, Path origFile, Path destFile)
244 throws ProxyException {
245 assert (remotePath != null);
247 // Transfer the file.
249 boolean success = false;
251 if (!Files.exists(origFile)) {
252 log.debug("Retrieving {} from {}", remotePath, remoteRepository.getRepository().getName());
253 wagon.get(addParameters(remotePath, remoteRepository.getRepository()), destFile.toFile());
256 // You wouldn't get here on failure, a WagonException would have been thrown.
257 log.debug("Downloaded successfully.");
259 log.debug("Retrieving {} from {} if updated", remotePath, remoteRepository.getRepository().getName());
261 success = wagon.getIfNewer(addParameters(remotePath, remoteRepository.getRepository()), destFile.toFile(),
262 Files.getLastModifiedTime(origFile).toMillis());
263 } catch (IOException e) {
264 throw new ProxyException("Failed to the modification time of " + origFile.toAbsolutePath());
267 throw new NotModifiedException(
268 "Not downloaded, as local file is newer than remote side: " + origFile.toAbsolutePath());
271 if (Files.exists(destFile)) {
272 log.debug("Downloaded successfully.");
275 } catch (ResourceDoesNotExistException e) {
276 throw new NotFoundException(
277 "Resource [" + remoteRepository.getURL() + "/" + remotePath + "] does not exist: " + e.getMessage(),
279 } catch (WagonException e) {
280 // TODO: shouldn't have to drill into the cause, but TransferFailedException is often not descriptive enough
283 "Download failure on resource [" + remoteRepository.getURL() + "/" + remotePath + "]:" + e.getMessage();
284 if (e.getCause() != null) {
285 msg += " (cause: " + e.getCause() + ")";
287 throw new ProxyException(msg, e);
292 * Using wagon, connect to the remote repository.
294 * @param connector the connector configuration to utilize (for obtaining network proxy configuration from)
295 * @param wagon the wagon instance to establish the connection on.
296 * @param remoteRepository the remote repository to connect to.
297 * @return true if the connection was successful. false if not connected.
299 protected boolean connectToRepository(ProxyConnector connector, Wagon wagon,
300 RemoteRepositoryContent remoteRepository) {
301 boolean connected = false;
303 final ProxyInfo networkProxy =
304 connector.getProxyId() == null ? null : this.networkProxyMap.get(connector.getProxyId());
306 if (log.isDebugEnabled()) {
307 if (networkProxy != null) {
308 // TODO: move to proxyInfo.toString()
309 String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
310 + " to connect to remote repository " + remoteRepository.getURL();
311 if (networkProxy.getNonProxyHosts() != null) {
312 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
314 if (StringUtils.isNotBlank(networkProxy.getUserName())) {
315 msg += "; as user: " + networkProxy.getUserName();
321 AuthenticationInfo authInfo = null;
322 String username = "";
323 String password = "";
324 RepositoryCredentials repCred = remoteRepository.getRepository().getLoginCredentials();
325 if (repCred != null && repCred instanceof PasswordCredentials) {
326 PasswordCredentials pwdCred = (PasswordCredentials) repCred;
327 username = pwdCred.getUsername();
328 password = pwdCred.getPassword() == null ? "" : new String(pwdCred.getPassword());
331 if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password)) {
332 log.debug("Using username {} to connect to remote repository {}", username, remoteRepository.getURL());
333 authInfo = new AuthenticationInfo();
334 authInfo.setUserName(username);
335 authInfo.setPassword(password);
338 // Convert seconds to milliseconds
340 long timeoutInMilliseconds = remoteRepository.getRepository().getTimeout().toMillis();
342 // Set timeout read and connect
343 // FIXME olamy having 2 config values
344 wagon.setReadTimeout((int) timeoutInMilliseconds);
345 wagon.setTimeout((int) timeoutInMilliseconds);
348 Repository wagonRepository =
349 new Repository(remoteRepository.getId(), remoteRepository.getURL().toString());
350 wagon.connect(wagonRepository, authInfo, networkProxy);
352 } catch (ConnectionException | AuthenticationException e) {
353 log.warn("Could not connect to {}: {}", remoteRepository.getRepository().getName(), e.getMessage());
361 public WagonFactory getWagonFactory() {
365 public void setWagonFactory(WagonFactory wagonFactory) {
366 this.wagonFactory = wagonFactory;
370 public List<RepositoryType> supports() {
371 return REPOSITORY_TYPES;