This project has retired. For details please refer to its Attic page.
Source code
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}