]> source.dussan.org Git - archiva.git/blob
714ea5bb1bcbc4bd50bcb62bbe60479d739d185c
[archiva.git] /
1 package org.apache.archiva.maven.proxy;
2
3 /*
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
11  *
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
18  * under the License.
19  */
20
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;
49
50 import javax.inject.Inject;
51 import java.io.IOException;
52 import java.net.URI;
53 import java.nio.file.Files;
54 import java.nio.file.Path;
55 import java.util.ArrayList;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.concurrent.ConcurrentHashMap;
59 import java.util.concurrent.ConcurrentMap;
60
61 /**
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
65  */
66 @Service( "repositoryProxyHandler#maven" )
67 public class MavenRepositoryProxyHandler extends DefaultRepositoryProxyHandler {
68
69     private static final Logger log = LoggerFactory.getLogger( MavenRepositoryProxyHandler.class );
70
71     private static final List<RepositoryType> REPOSITORY_TYPES = new ArrayList<>();
72
73     static {
74         REPOSITORY_TYPES.add(RepositoryType.MAVEN);
75     }
76
77     @Inject
78     private WagonFactory wagonFactory;
79
80     private ConcurrentMap<String, ProxyInfo> networkProxyMap = new ConcurrentHashMap<>();
81
82     @Override
83     public void initialize() {
84         super.initialize();
85     }
86
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();
92
93             ProxyInfo proxy = new ProxyInfo();
94
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()));
100
101             this.networkProxyMap.put(key, proxy);
102         }
103     }
104
105     @Override
106     public void setNetworkProxies(Map<String, NetworkProxy> networkProxies ) {
107         super.setNetworkProxies( networkProxies );
108         updateWagonProxyInfo( networkProxies );
109     }
110
111     /**
112      * @param connector
113      * @param remoteRepository
114      * @param tmpResource
115      * @param checksumFiles
116      * @param url
117      * @param remotePath
118      * @param resource
119      * @param workingDirectory
120      * @param repository
121      * @throws ProxyException
122      * @throws NotModifiedException
123      */
124     @Override
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 {
129         Wagon wagon = null;
130         try {
131             URI repoUrl = remoteRepository.getLocation( );
132             String protocol = repoUrl.getScheme( );
133             NetworkProxy networkProxy = null;
134             String proxyId = connector.getProxyId();
135             if (StringUtils.isNotBlank(proxyId)) {
136
137                 networkProxy = getNetworkProxy(proxyId);
138             }
139             WagonFactoryRequest wagonFactoryRequest = new WagonFactoryRequest("wagon#" + protocol,
140                     remoteRepository.getExtraHeaders());
141             if (networkProxy == null) {
142
143                 log.warn("No network proxy with id {} found for connector {}->{}", proxyId,
144                         connector.getSourceRepository().getId(), connector.getTargetRepository().getId());
145             } else {
146                 wagonFactoryRequest = wagonFactoryRequest.networkProxy(networkProxy);
147             }
148             wagon = wagonFactory.getWagon(wagonFactoryRequest);
149             if (wagon == null) {
150                 throw new ProxyException("Unsupported target repository protocol: " + protocol);
151             }
152
153             boolean connected = connectToRepository(connector, wagon, remoteRepository);
154             if (connected) {
155                 transferArtifact(wagon, remoteRepository, remotePath, resource.getFilePath(),
156                     tmpResource);
157
158                 // TODO: these should be used to validate the download based on the policies, not always downloaded
159                 // to
160                 // save on connections since md5 is rarely used
161                 for ( StorageAsset checksumFile : checksumFiles )
162                 {
163                     String ext = "." + StringUtils.substringAfterLast( checksumFile.getName( ), "." );
164                     transferChecksum( wagon, remoteRepository, remotePath, resource.getFilePath( ), ext,
165                         checksumFile.getFilePath( ) );
166                 }
167             }
168         }
169         catch (NotModifiedException e) {
170             // Do not cache url here.
171             throw e;
172         }
173         catch ( ProxyException e) {
174             urlFailureCache.cacheFailure(url);
175             throw e;
176         }
177         catch ( WagonFactoryException e) {
178             throw new ProxyException(e.getMessage(), e);
179         } finally {
180             if (wagon != null) {
181                 try {
182                     wagon.disconnect();
183                 } catch (ConnectionException e) {
184                     log.warn("Unable to disconnect wagon.", e);
185                 }
186             }
187         }
188     }
189
190     protected void transferArtifact( Wagon wagon, RemoteRepository remoteRepository, String remotePath,
191                                      Path resource,
192                                      StorageAsset destFile )
193             throws ProxyException {
194         transferSimpleFile(wagon, remoteRepository, remotePath, resource, destFile.getFilePath());
195     }
196
197     /**
198      * <p>
199      * Quietly transfer the checksum file from the remote repository to the local file.
200      * </p>
201      *
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.
208      */
209     protected void transferChecksum( Wagon wagon, RemoteRepository remoteRepository, String remotePath,
210                                      Path resource, String ext,
211                                      Path destFile )
212             throws ProxyException {
213         String url = remoteRepository.getLocation().toString() + remotePath + ext;
214
215         // Transfer checksum does not use the policy.
216         if (urlFailureCache.hasFailedBefore(url)) {
217             return;
218         }
219
220         try {
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.
234             throw e;
235         }
236     }
237
238     /**
239      * Perform the transfer of the remote file to the local file specified.
240      *
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.
246      */
247     protected void transferSimpleFile( Wagon wagon, RemoteRepository remoteRepository, String remotePath,
248                                        Path origFile, Path destFile )
249             throws ProxyException {
250         assert (remotePath != null);
251
252         // Transfer the file.
253         try {
254
255
256             if (!Files.exists(origFile)) {
257                 log.debug("Retrieving {} from {}", remotePath, remoteRepository.getId());
258                 wagon.get(addParameters(remotePath, remoteRepository), destFile.toFile());
259
260                 // You wouldn't get here on failure, a WagonException would have been thrown.
261                 log.debug("Downloaded successfully.");
262             } else {
263                 boolean success;
264                 log.debug("Retrieving {} from {} if updated", remotePath, remoteRepository.getId());
265                 try {
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());
270                 }
271                 if (!success) {
272                     throw new NotModifiedException(
273                             "Not downloaded, as local file is newer than remote side: " + origFile.toAbsolutePath());
274                 }
275
276                 if (Files.exists(destFile)) {
277                     log.debug("Downloaded successfully.");
278                 }
279             }
280         } catch (ResourceDoesNotExistException e) {
281             throw new NotFoundException(
282                     "Resource [" + remoteRepository.getLocation() + "/" + remotePath + "] does not exist: " + e.getMessage(),
283                     e);
284         } catch (WagonException e) {
285             // TODO: shouldn't have to drill into the cause, but TransferFailedException is often not descriptive enough
286
287             String msg =
288                     "Download failure on resource [" + remoteRepository.getLocation() + "/" + remotePath + "]:" + e.getMessage();
289             if (e.getCause() != null) {
290                 msg += " (cause: " + e.getCause() + ")";
291             }
292             throw new ProxyException(msg, e);
293         }
294     }
295
296     /**
297      * Using wagon, connect to the remote repository.
298      *
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.
303      */
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());
308
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();
316                 }
317                 if (StringUtils.isNotBlank(networkProxy.getUserName())) {
318                     msg += "; as user: " + networkProxy.getUserName();
319                 }
320                 log.debug(msg);
321             }
322         }
323
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());
332         }
333
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);
339         }
340
341         // Convert seconds to milliseconds
342
343         long timeoutInMilliseconds = remoteRepository.getTimeout().toMillis();
344
345         // Set timeout  read and connect
346         // FIXME olamy having 2 config values
347         wagon.setReadTimeout((int) timeoutInMilliseconds);
348         wagon.setTimeout((int) timeoutInMilliseconds);
349
350         try {
351             Repository wagonRepository =
352                     new Repository(remoteRepository.getId(), remoteRepository.getLocation().toString());
353             wagon.connect(wagonRepository, authInfo, networkProxy);
354             return true;
355         } catch (ConnectionException | AuthenticationException e) {
356             log.warn("Could not connect to {}: {}", remoteRepository.getId(), e.getMessage());
357             return false;
358         }
359
360     }
361
362
363     public WagonFactory getWagonFactory() {
364         return wagonFactory;
365     }
366
367     public void setWagonFactory(WagonFactory wagonFactory) {
368         this.wagonFactory = wagonFactory;
369     }
370
371     @Override
372     public List<RepositoryType> supports() {
373         return REPOSITORY_TYPES;
374     }
375
376     @Override
377     public void addNetworkproxy( String id, NetworkProxy networkProxy )
378     {
379
380     }
381
382     @Override
383     public <T extends RepositoryProxyHandler> T getHandler( Class<T> clazz ) throws IllegalArgumentException
384     {
385         if (clazz.isAssignableFrom( this.getClass() )) {
386             return (T)this;
387         } else {
388             throw new IllegalArgumentException( "This Proxy Handler is no subclass of " + clazz );
389         }
390     }
391 }