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