You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

MavenRepositoryProxyHandler.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. package org.apache.archiva.proxy.maven;
  2. /*
  3. * Licensed to the Apache Software Foundation (ASF) under one
  4. * or more contributor license agreements. See the NOTICE file
  5. * distributed with this work for additional information
  6. * regarding copyright ownership. The ASF licenses this file
  7. * to you under the Apache License, Version 2.0 (the
  8. * "License"); you may not use this file except in compliance
  9. * with the License. You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  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
  18. * under the License.
  19. */
  20. import org.apache.archiva.configuration.NetworkProxyConfiguration;
  21. import org.apache.archiva.model.RepositoryURL;
  22. import org.apache.archiva.proxy.DefaultRepositoryProxyHandler;
  23. import org.apache.archiva.proxy.NotFoundException;
  24. import org.apache.archiva.proxy.NotModifiedException;
  25. import org.apache.archiva.proxy.ProxyException;
  26. import org.apache.archiva.proxy.model.NetworkProxy;
  27. import org.apache.archiva.proxy.model.ProxyConnector;
  28. import org.apache.archiva.repository.*;
  29. import org.apache.commons.lang.StringUtils;
  30. import org.apache.maven.wagon.ConnectionException;
  31. import org.apache.maven.wagon.ResourceDoesNotExistException;
  32. import org.apache.maven.wagon.Wagon;
  33. import org.apache.maven.wagon.WagonException;
  34. import org.apache.maven.wagon.authentication.AuthenticationException;
  35. import org.apache.maven.wagon.authentication.AuthenticationInfo;
  36. import org.apache.maven.wagon.proxy.ProxyInfo;
  37. import org.apache.maven.wagon.repository.Repository;
  38. import org.springframework.stereotype.Service;
  39. import javax.inject.Inject;
  40. import java.io.IOException;
  41. import java.nio.file.Files;
  42. import java.nio.file.Path;
  43. import java.util.ArrayList;
  44. import java.util.List;
  45. import java.util.Map;
  46. import java.util.concurrent.ConcurrentHashMap;
  47. import java.util.concurrent.ConcurrentMap;
  48. /**
  49. * DefaultRepositoryProxyHandler
  50. * TODO exception handling needs work - "not modified" is not really an exceptional case, and it has more layers than
  51. * your average brown onion
  52. */
  53. @Service("repositoryProxyConnectors#maven")
  54. public class MavenRepositoryProxyHandler extends DefaultRepositoryProxyHandler {
  55. private static final List<RepositoryType> REPOSITORY_TYPES = new ArrayList<>();
  56. static {
  57. REPOSITORY_TYPES.add(RepositoryType.MAVEN);
  58. }
  59. @Inject
  60. private WagonFactory wagonFactory;
  61. private ConcurrentMap<String, ProxyInfo> networkProxyMap = new ConcurrentHashMap<>();
  62. @Override
  63. public void initialize() {
  64. super.initialize();
  65. }
  66. private void updateWagonProxyInfo(Map<String, NetworkProxy> proxyList) {
  67. this.networkProxyMap.clear();
  68. List<NetworkProxyConfiguration> networkProxies = getArchivaConfiguration().getConfiguration().getNetworkProxies();
  69. for (Map.Entry<String, NetworkProxy> proxyEntry : proxyList.entrySet()) {
  70. String key = proxyEntry.getKey();
  71. NetworkProxy networkProxyDef = proxyEntry.getValue();
  72. ProxyInfo proxy = new ProxyInfo();
  73. proxy.setType(networkProxyDef.getProtocol());
  74. proxy.setHost(networkProxyDef.getHost());
  75. proxy.setPort(networkProxyDef.getPort());
  76. proxy.setUserName(networkProxyDef.getUsername());
  77. proxy.setPassword(networkProxyDef.getPassword());
  78. this.networkProxyMap.put(key, proxy);
  79. }
  80. }
  81. @Override
  82. public void setNetworkProxies(Map<String, NetworkProxy> proxies) {
  83. super.setNetworkProxies(proxies);
  84. updateWagonProxyInfo(proxies);
  85. }
  86. /**
  87. * @param connector
  88. * @param remoteRepository
  89. * @param tmpMd5
  90. * @param tmpSha1
  91. * @param tmpResource
  92. * @param url
  93. * @param remotePath
  94. * @param resource
  95. * @param workingDirectory
  96. * @param repository
  97. * @throws ProxyException
  98. * @throws NotModifiedException
  99. */
  100. protected void transferResources(ProxyConnector connector, RemoteRepositoryContent remoteRepository, Path tmpMd5,
  101. Path tmpSha1, Path tmpResource, String url, String remotePath, Path resource,
  102. Path workingDirectory, ManagedRepositoryContent repository)
  103. throws ProxyException, NotModifiedException {
  104. Wagon wagon = null;
  105. try {
  106. RepositoryURL repoUrl = remoteRepository.getURL();
  107. String protocol = repoUrl.getProtocol();
  108. NetworkProxy networkProxy = null;
  109. String proxyId = connector.getProxyId();
  110. if (StringUtils.isNotBlank(proxyId)) {
  111. networkProxy = getNetworkProxy(proxyId);
  112. }
  113. WagonFactoryRequest wagonFactoryRequest = new WagonFactoryRequest("wagon#" + protocol,
  114. remoteRepository.getRepository().getExtraHeaders());
  115. if (networkProxy == null) {
  116. log.warn("No network proxy with id {} found for connector {}->{}", proxyId,
  117. connector.getSourceRepository().getId(), connector.getTargetRepository().getId());
  118. } else {
  119. wagonFactoryRequest = wagonFactoryRequest.networkProxy(networkProxy);
  120. }
  121. wagon = wagonFactory.getWagon(wagonFactoryRequest);
  122. if (wagon == null) {
  123. throw new ProxyException("Unsupported target repository protocol: " + protocol);
  124. }
  125. if (wagon == null) {
  126. throw new ProxyException("Unsupported target repository protocol: " + protocol);
  127. }
  128. boolean connected = connectToRepository(connector, wagon, remoteRepository);
  129. if (connected) {
  130. transferArtifact(wagon, remoteRepository, remotePath, repository, resource, workingDirectory,
  131. tmpResource);
  132. // TODO: these should be used to validate the download based on the policies, not always downloaded
  133. // to
  134. // save on connections since md5 is rarely used
  135. transferChecksum(wagon, remoteRepository, remotePath, repository, resource, workingDirectory, ".sha1",
  136. tmpSha1);
  137. transferChecksum(wagon, remoteRepository, remotePath, repository, resource, workingDirectory, ".md5",
  138. tmpMd5);
  139. }
  140. } catch (NotFoundException e) {
  141. urlFailureCache.cacheFailure(url);
  142. throw e;
  143. } catch (NotModifiedException e) {
  144. // Do not cache url here.
  145. throw e;
  146. } catch (ProxyException e) {
  147. urlFailureCache.cacheFailure(url);
  148. throw e;
  149. } catch (WagonFactoryException e) {
  150. throw new ProxyException(e.getMessage(), e);
  151. } finally {
  152. if (wagon != null) {
  153. try {
  154. wagon.disconnect();
  155. } catch (ConnectionException e) {
  156. log.warn("Unable to disconnect wagon.", e);
  157. }
  158. }
  159. }
  160. }
  161. protected void transferArtifact(Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
  162. ManagedRepositoryContent repository, Path resource, Path tmpDirectory,
  163. Path destFile)
  164. throws ProxyException {
  165. transferSimpleFile(wagon, remoteRepository, remotePath, repository, resource, destFile);
  166. }
  167. /**
  168. * <p>
  169. * Quietly transfer the checksum file from the remote repository to the local file.
  170. * </p>
  171. *
  172. * @param wagon the wagon instance (should already be connected) to use.
  173. * @param remoteRepository the remote repository to transfer from.
  174. * @param remotePath the remote path to the resource to get.
  175. * @param repository the managed repository that will hold the file
  176. * @param resource the local file that should contain the downloaded contents
  177. * @param tmpDirectory the temporary directory to download to
  178. * @param ext the type of checksum to transfer (example: ".md5" or ".sha1")
  179. * @throws ProxyException if copying the downloaded file into place did not succeed.
  180. */
  181. protected void transferChecksum(Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
  182. ManagedRepositoryContent repository, Path resource, Path tmpDirectory, String ext,
  183. Path destFile)
  184. throws ProxyException {
  185. String url = remoteRepository.getURL().getUrl() + remotePath + ext;
  186. // Transfer checksum does not use the policy.
  187. if (urlFailureCache.hasFailedBefore(url)) {
  188. return;
  189. }
  190. try {
  191. transferSimpleFile(wagon, remoteRepository, remotePath + ext, repository, resource, destFile);
  192. log.debug("Checksum {} Downloaded: {} to move to {}", url, destFile, resource);
  193. } catch (NotFoundException e) {
  194. urlFailureCache.cacheFailure(url);
  195. log.debug("Transfer failed, checksum not found: {}", url);
  196. // Consume it, do not pass this on.
  197. } catch (NotModifiedException e) {
  198. log.debug("Transfer skipped, checksum not modified: {}", url);
  199. // Consume it, do not pass this on.
  200. } catch (ProxyException e) {
  201. urlFailureCache.cacheFailure(url);
  202. log.warn("Transfer failed on checksum: {} : {}", url, e.getMessage(), e);
  203. // Critical issue, pass it on.
  204. throw e;
  205. }
  206. }
  207. /**
  208. * Perform the transfer of the remote file to the local file specified.
  209. *
  210. * @param wagon the wagon instance to use.
  211. * @param remoteRepository the remote repository to use
  212. * @param remotePath the remote path to attempt to get
  213. * @param repository the managed repository that will hold the file
  214. * @param origFile the local file to save to
  215. * @throws ProxyException if there was a problem moving the downloaded file into place.
  216. */
  217. protected void transferSimpleFile(Wagon wagon, RemoteRepositoryContent remoteRepository, String remotePath,
  218. ManagedRepositoryContent repository, Path origFile, Path destFile)
  219. throws ProxyException {
  220. assert (remotePath != null);
  221. // Transfer the file.
  222. try {
  223. boolean success = false;
  224. if (!Files.exists(origFile)) {
  225. log.debug("Retrieving {} from {}", remotePath, remoteRepository.getRepository().getName());
  226. wagon.get(addParameters(remotePath, remoteRepository.getRepository()), destFile.toFile());
  227. success = true;
  228. // You wouldn't get here on failure, a WagonException would have been thrown.
  229. log.debug("Downloaded successfully.");
  230. } else {
  231. log.debug("Retrieving {} from {} if updated", remotePath, remoteRepository.getRepository().getName());
  232. try {
  233. success = wagon.getIfNewer(addParameters(remotePath, remoteRepository.getRepository()), destFile.toFile(),
  234. Files.getLastModifiedTime(origFile).toMillis());
  235. } catch (IOException e) {
  236. throw new ProxyException("Failed to the modification time of " + origFile.toAbsolutePath());
  237. }
  238. if (!success) {
  239. throw new NotModifiedException(
  240. "Not downloaded, as local file is newer than remote side: " + origFile.toAbsolutePath());
  241. }
  242. if (Files.exists(destFile)) {
  243. log.debug("Downloaded successfully.");
  244. }
  245. }
  246. } catch (ResourceDoesNotExistException e) {
  247. throw new NotFoundException(
  248. "Resource [" + remoteRepository.getURL() + "/" + remotePath + "] does not exist: " + e.getMessage(),
  249. e);
  250. } catch (WagonException e) {
  251. // TODO: shouldn't have to drill into the cause, but TransferFailedException is often not descriptive enough
  252. String msg =
  253. "Download failure on resource [" + remoteRepository.getURL() + "/" + remotePath + "]:" + e.getMessage();
  254. if (e.getCause() != null) {
  255. msg += " (cause: " + e.getCause() + ")";
  256. }
  257. throw new ProxyException(msg, e);
  258. }
  259. }
  260. /**
  261. * Using wagon, connect to the remote repository.
  262. *
  263. * @param connector the connector configuration to utilize (for obtaining network proxy configuration from)
  264. * @param wagon the wagon instance to establish the connection on.
  265. * @param remoteRepository the remote repository to connect to.
  266. * @return true if the connection was successful. false if not connected.
  267. */
  268. protected boolean connectToRepository(ProxyConnector connector, Wagon wagon,
  269. RemoteRepositoryContent remoteRepository) {
  270. boolean connected = false;
  271. final ProxyInfo networkProxy =
  272. connector.getProxyId() == null ? null : this.networkProxyMap.get(connector.getProxyId());
  273. if (log.isDebugEnabled()) {
  274. if (networkProxy != null) {
  275. // TODO: move to proxyInfo.toString()
  276. String msg = "Using network proxy " + networkProxy.getHost() + ":" + networkProxy.getPort()
  277. + " to connect to remote repository " + remoteRepository.getURL();
  278. if (networkProxy.getNonProxyHosts() != null) {
  279. msg += "; excluding hosts: " + networkProxy.getNonProxyHosts();
  280. }
  281. if (StringUtils.isNotBlank(networkProxy.getUserName())) {
  282. msg += "; as user: " + networkProxy.getUserName();
  283. }
  284. log.debug(msg);
  285. }
  286. }
  287. AuthenticationInfo authInfo = null;
  288. String username = "";
  289. String password = "";
  290. RepositoryCredentials repCred = remoteRepository.getRepository().getLoginCredentials();
  291. if (repCred != null && repCred instanceof PasswordCredentials) {
  292. PasswordCredentials pwdCred = (PasswordCredentials) repCred;
  293. username = pwdCred.getUsername();
  294. password = pwdCred.getPassword() == null ? "" : new String(pwdCred.getPassword());
  295. }
  296. if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password)) {
  297. log.debug("Using username {} to connect to remote repository {}", username, remoteRepository.getURL());
  298. authInfo = new AuthenticationInfo();
  299. authInfo.setUserName(username);
  300. authInfo.setPassword(password);
  301. }
  302. // Convert seconds to milliseconds
  303. long timeoutInMilliseconds = remoteRepository.getRepository().getTimeout().toMillis();
  304. // Set timeout read and connect
  305. // FIXME olamy having 2 config values
  306. wagon.setReadTimeout((int) timeoutInMilliseconds);
  307. wagon.setTimeout((int) timeoutInMilliseconds);
  308. try {
  309. Repository wagonRepository =
  310. new Repository(remoteRepository.getId(), remoteRepository.getURL().toString());
  311. wagon.connect(wagonRepository, authInfo, networkProxy);
  312. connected = true;
  313. } catch (ConnectionException | AuthenticationException e) {
  314. log.warn("Could not connect to {}: {}", remoteRepository.getRepository().getName(), e.getMessage());
  315. connected = false;
  316. }
  317. return connected;
  318. }
  319. public WagonFactory getWagonFactory() {
  320. return wagonFactory;
  321. }
  322. public void setWagonFactory(WagonFactory wagonFactory) {
  323. this.wagonFactory = wagonFactory;
  324. }
  325. @Override
  326. public List<RepositoryType> supports() {
  327. return REPOSITORY_TYPES;
  328. }
  329. }