001package org.apache.archiva.proxy.maven; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import org.apache.archiva.model.RepositoryURL; 023import org.apache.archiva.proxy.DefaultRepositoryProxyHandler; 024import org.apache.archiva.proxy.NotFoundException; 025import org.apache.archiva.proxy.NotModifiedException; 026import org.apache.archiva.proxy.ProxyException; 027import org.apache.archiva.proxy.model.NetworkProxy; 028import org.apache.archiva.proxy.model.ProxyConnector; 029import org.apache.archiva.proxy.model.RepositoryProxyHandler; 030import org.apache.archiva.repository.*; 031import org.apache.archiva.repository.base.PasswordCredentials; 032import org.apache.archiva.repository.storage.StorageAsset; 033import org.apache.commons.lang3.StringUtils; 034import org.apache.maven.wagon.ConnectionException; 035import org.apache.maven.wagon.ResourceDoesNotExistException; 036import org.apache.maven.wagon.Wagon; 037import org.apache.maven.wagon.WagonException; 038import org.apache.maven.wagon.authentication.AuthenticationException; 039import org.apache.maven.wagon.authentication.AuthenticationInfo; 040import org.apache.maven.wagon.proxy.ProxyInfo; 041import org.apache.maven.wagon.repository.Repository; 042import org.springframework.stereotype.Service; 043 044import javax.inject.Inject; 045import java.io.IOException; 046import java.nio.file.Files; 047import java.nio.file.Path; 048import java.util.ArrayList; 049import java.util.List; 050import java.util.Map; 051import java.util.concurrent.ConcurrentHashMap; 052import java.util.concurrent.ConcurrentMap; 053 054/** 055 * DefaultRepositoryProxyHandler 056 * TODO exception handling needs work - "not modified" is not really an exceptional case, and it has more layers than 057 * your average brown onion 058 */ 059@Service( "repositoryProxyHandler#maven" ) 060public class MavenRepositoryProxyHandler extends DefaultRepositoryProxyHandler { 061 062 private static final List<RepositoryType> REPOSITORY_TYPES = new ArrayList<>(); 063 064 static { 065 REPOSITORY_TYPES.add(RepositoryType.MAVEN); 066 } 067 068 @Inject 069 private WagonFactory wagonFactory; 070 071 private ConcurrentMap<String, ProxyInfo> networkProxyMap = new ConcurrentHashMap<>(); 072 073 @Override 074 public void initialize() { 075 super.initialize(); 076 } 077 078 private void updateWagonProxyInfo(Map<String, NetworkProxy> proxyList) { 079 this.networkProxyMap.clear(); 080 for (Map.Entry<String, NetworkProxy> proxyEntry : proxyList.entrySet()) { 081 String key = proxyEntry.getKey(); 082 NetworkProxy networkProxyDef = proxyEntry.getValue(); 083 084 ProxyInfo proxy = new ProxyInfo(); 085 086 proxy.setType(networkProxyDef.getProtocol()); 087 proxy.setHost(networkProxyDef.getHost()); 088 proxy.setPort(networkProxyDef.getPort()); 089 proxy.setUserName(networkProxyDef.getUsername()); 090 proxy.setPassword(new String(networkProxyDef.getPassword())); 091 092 this.networkProxyMap.put(key, proxy); 093 } 094 } 095 096 @Override 097 public void setNetworkProxies(Map<String, NetworkProxy> networkProxies ) { 098 super.setNetworkProxies( networkProxies ); 099 updateWagonProxyInfo( networkProxies ); 100 } 101 102 /** 103 * @param connector 104 * @param remoteRepository 105 * @param tmpResource 106 * @param checksumFiles 107 * @param url 108 * @param remotePath 109 * @param resource 110 * @param workingDirectory 111 * @param repository 112 * @throws ProxyException 113 * @throws NotModifiedException 114 */ 115 protected void transferResources( ProxyConnector connector, RemoteRepository remoteRepository, 116 StorageAsset tmpResource, StorageAsset[] checksumFiles, String url, String remotePath, StorageAsset resource, 117 Path workingDirectory, ManagedRepository repository ) 118 throws ProxyException, NotModifiedException { 119 Wagon wagon = null; 120 try { 121 RepositoryURL repoUrl = remoteRepository.getContent().getURL(); 122 String protocol = repoUrl.getProtocol(); 123 NetworkProxy networkProxy = null; 124 String proxyId = connector.getProxyId(); 125 if (StringUtils.isNotBlank(proxyId)) { 126 127 networkProxy = getNetworkProxy(proxyId); 128 } 129 WagonFactoryRequest wagonFactoryRequest = new WagonFactoryRequest("wagon#" + protocol, 130 remoteRepository.getExtraHeaders()); 131 if (networkProxy == null) { 132 133 log.warn("No network proxy with id {} found for connector {}->{}", proxyId, 134 connector.getSourceRepository().getId(), connector.getTargetRepository().getId()); 135 } else { 136 wagonFactoryRequest = wagonFactoryRequest.networkProxy(networkProxy); 137 } 138 wagon = wagonFactory.getWagon(wagonFactoryRequest); 139 if (wagon == null) { 140 throw new ProxyException("Unsupported target repository protocol: " + protocol); 141 } 142 143 if (wagon == null) { 144 throw new ProxyException("Unsupported target repository protocol: " + protocol); 145 } 146 147 boolean connected = connectToRepository(connector, wagon, remoteRepository); 148 if (connected) { 149 transferArtifact(wagon, remoteRepository, remotePath, repository, resource.getFilePath(), workingDirectory, 150 tmpResource); 151 152 // TODO: these should be used to validate the download based on the policies, not always downloaded 153 // to 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].getName( ), "." ); 157 transferChecksum(wagon, remoteRepository, remotePath, repository, resource.getFilePath(), ext, 158 checksumFiles[i].getFilePath()); 159 } 160 } 161 } catch (NotFoundException e) { 162 urlFailureCache.cacheFailure(url); 163 throw e; 164 } catch (NotModifiedException e) { 165 // Do not cache url here. 166 throw e; 167 } catch (ProxyException e) { 168 urlFailureCache.cacheFailure(url); 169 throw e; 170 } catch (WagonFactoryException e) { 171 throw new ProxyException(e.getMessage(), e); 172 } finally { 173 if (wagon != null) { 174 try { 175 wagon.disconnect(); 176 } catch (ConnectionException e) { 177 log.warn("Unable to disconnect wagon.", e); 178 } 179 } 180 } 181 } 182 183 protected void transferArtifact(Wagon wagon, RemoteRepository remoteRepository, String remotePath, 184 ManagedRepository repository, Path resource, Path tmpDirectory, 185 StorageAsset destFile) 186 throws ProxyException { 187 transferSimpleFile(wagon, remoteRepository, remotePath, repository, resource, destFile.getFilePath()); 188 } 189 190 /** 191 * <p> 192 * Quietly transfer the checksum file from the remote repository to the local file. 193 * </p> 194 * 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. 202 */ 203 protected void transferChecksum( Wagon wagon, RemoteRepository remoteRepository, String remotePath, 204 ManagedRepository repository, Path resource, String ext, 205 Path destFile ) 206 throws ProxyException { 207 String url = remoteRepository.getLocation().toString() + remotePath + ext; 208 209 // Transfer checksum does not use the policy. 210 if (urlFailureCache.hasFailedBefore(url)) { 211 return; 212 } 213 214 try { 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. 228 throw e; 229 } 230 } 231 232 /** 233 * Perform the transfer of the remote file to the local file specified. 234 * 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. 241 */ 242 protected void transferSimpleFile(Wagon wagon, RemoteRepository remoteRepository, String remotePath, 243 ManagedRepository repository, Path origFile, Path destFile) 244 throws ProxyException { 245 assert (remotePath != null); 246 247 // Transfer the file. 248 try { 249 boolean success = false; 250 251 if (!Files.exists(origFile)) { 252 log.debug("Retrieving {} from {}", remotePath, remoteRepository.getId()); 253 wagon.get(addParameters(remotePath, remoteRepository), destFile.toFile()); 254 success = true; 255 256 // You wouldn't get here on failure, a WagonException would have been thrown. 257 log.debug("Downloaded successfully."); 258 } else { 259 log.debug("Retrieving {} from {} if updated", remotePath, remoteRepository.getId()); 260 try { 261 success = wagon.getIfNewer(addParameters(remotePath, remoteRepository), destFile.toFile(), 262 Files.getLastModifiedTime(origFile).toMillis()); 263 } catch (IOException e) { 264 throw new ProxyException("Failed to the modification time of " + origFile.toAbsolutePath()); 265 } 266 if (!success) { 267 throw new NotModifiedException( 268 "Not downloaded, as local file is newer than remote side: " + origFile.toAbsolutePath()); 269 } 270 271 if (Files.exists(destFile)) { 272 log.debug("Downloaded successfully."); 273 } 274 } 275 } catch (ResourceDoesNotExistException e) { 276 throw new NotFoundException( 277 "Resource [" + remoteRepository.getLocation() + "/" + remotePath + "] does not exist: " + e.getMessage(), 278 e); 279 } catch (WagonException e) { 280 // TODO: shouldn't have to drill into the cause, but TransferFailedException is often not descriptive enough 281 282 String msg = 283 "Download failure on resource [" + remoteRepository.getLocation() + "/" + remotePath + "]:" + e.getMessage(); 284 if (e.getCause() != null) { 285 msg += " (cause: " + e.getCause() + ")"; 286 } 287 throw new ProxyException(msg, e); 288 } 289 } 290 291 /** 292 * Using wagon, connect to the remote repository. 293 * 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. 298 */ 299 protected boolean connectToRepository(ProxyConnector connector, Wagon wagon, 300 RemoteRepository remoteRepository) { 301 boolean connected = false; 302 303 final ProxyInfo networkProxy = 304 connector.getProxyId() == null ? null : this.networkProxyMap.get(connector.getProxyId()); 305 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.getLocation(); 311 if (networkProxy.getNonProxyHosts() != null) { 312 msg += "; excluding hosts: " + networkProxy.getNonProxyHosts(); 313 } 314 if (StringUtils.isNotBlank(networkProxy.getUserName())) { 315 msg += "; as user: " + networkProxy.getUserName(); 316 } 317 log.debug(msg); 318 } 319 } 320 321 AuthenticationInfo authInfo = null; 322 String username = ""; 323 String password = ""; 324 RepositoryCredentials repCred = remoteRepository.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()); 329 } 330 331 if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password)) { 332 log.debug("Using username {} to connect to remote repository {}", username, remoteRepository.getLocation()); 333 authInfo = new AuthenticationInfo(); 334 authInfo.setUserName(username); 335 authInfo.setPassword(password); 336 } 337 338 // Convert seconds to milliseconds 339 340 long timeoutInMilliseconds = remoteRepository.getTimeout().toMillis(); 341 342 // Set timeout read and connect 343 // FIXME olamy having 2 config values 344 wagon.setReadTimeout((int) timeoutInMilliseconds); 345 wagon.setTimeout((int) timeoutInMilliseconds); 346 347 try { 348 Repository wagonRepository = 349 new Repository(remoteRepository.getId(), remoteRepository.getLocation().toString()); 350 wagon.connect(wagonRepository, authInfo, networkProxy); 351 connected = true; 352 } catch (ConnectionException | AuthenticationException e) { 353 log.warn("Could not connect to {}: {}", remoteRepository.getId(), e.getMessage()); 354 connected = false; 355 } 356 357 return connected; 358 } 359 360 361 public WagonFactory getWagonFactory() { 362 return wagonFactory; 363 } 364 365 public void setWagonFactory(WagonFactory wagonFactory) { 366 this.wagonFactory = wagonFactory; 367 } 368 369 @Override 370 public List<RepositoryType> supports() { 371 return REPOSITORY_TYPES; 372 } 373 374 @Override 375 public void addNetworkproxy( String id, NetworkProxy networkProxy ) 376 { 377 378 } 379 380 @Override 381 public <T extends RepositoryProxyHandler> T getHandler( Class<T> clazz ) throws IllegalArgumentException 382 { 383 if (clazz.isAssignableFrom( this.getClass() )) { 384 return (T)this; 385 } else { 386 throw new IllegalArgumentException( "This Proxy Handler is no subclass of " + clazz ); 387 } 388 } 389}