001package org.apache.maven.wagon.providers.http;
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 static org.hamcrest.CoreMatchers.instanceOf;
023import static org.junit.Assert.assertEquals;
024import static org.junit.Assert.assertFalse;
025import static org.junit.Assert.assertNotNull;
026import static org.junit.Assert.assertThat;
027import static org.junit.Assert.assertTrue;
028import static org.junit.Assert.fail;
029
030import java.io.File;
031import java.io.IOException;
032import java.io.InterruptedIOException;
033import java.lang.reflect.Field;
034import java.net.ConnectException;
035import java.net.MalformedURLException;
036import java.net.URL;
037import java.net.URLClassLoader;
038import java.net.UnknownHostException;
039import java.util.ArrayList;
040import java.util.Collection;
041import java.util.Set;
042
043import javax.net.ssl.SSLException;
044
045import org.apache.http.client.HttpRequestRetryHandler;
046import org.apache.http.impl.client.CloseableHttpClient;
047import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
048import org.apache.http.impl.execchain.RedirectExec;
049import org.apache.http.impl.execchain.RetryExec;
050import org.apache.maven.wagon.InputData;
051import org.apache.maven.wagon.repository.Repository;
052import org.apache.maven.wagon.resource.Resource;
053import org.apache.maven.wagon.shared.http.AbstractHttpClientWagon;
054import org.junit.Ignore;
055import org.junit.Test;
056
057public class AbstractHttpClientWagonTest
058{
059    @Ignore("This test is validating nothing and require internet connection which we should avoid so ignore it")
060    public void test()
061        throws Exception
062    {
063        AbstractHttpClientWagon wagon = new AbstractHttpClientWagon()
064        {
065        };
066
067        Repository repository = new Repository( "central", "http://repo.maven.apache.org/maven2" );
068
069        wagon.connect( repository );
070
071        Resource resource = new Resource();
072
073        resource.setName( "junit/junit/maven-metadata.xml" );
074
075        InputData inputData = new InputData();
076
077        inputData.setResource( resource );
078
079        wagon.fillInputData( inputData );
080
081        wagon.disconnect();
082    }
083
084    @Test
085    public void retryableConfigurationDefaultTest() throws Exception
086    {
087        doTestHttpClient( new Runnable()
088        {
089            @Override
090            public void run()
091            {
092                final HttpRequestRetryHandler handler = getCurrentHandler();
093                assertNotNull( handler );
094                assertThat( handler, instanceOf( DefaultHttpRequestRetryHandler.class ) );
095                final DefaultHttpRequestRetryHandler impl = DefaultHttpRequestRetryHandler.class.cast(handler);
096                assertEquals( 3, impl.getRetryCount() );
097                assertFalse( impl.isRequestSentRetryEnabled() );
098            }
099        });
100    }
101
102    @Test
103    public void retryableConfigurationCountTest() throws Exception
104    {
105        doTestHttpClient( new Runnable()
106        {
107            @Override
108            public void run()
109            {
110                System.setProperty( "maven.wagon.http.retryHandler.class", "default" );
111                System.setProperty( "maven.wagon.http.retryHandler.count", "5" );
112
113                final HttpRequestRetryHandler handler = getCurrentHandler();
114                assertNotNull( handler );
115                assertThat( handler, instanceOf( DefaultHttpRequestRetryHandler.class ) );
116                final DefaultHttpRequestRetryHandler impl = DefaultHttpRequestRetryHandler.class.cast(handler);
117                assertEquals( 5, impl.getRetryCount() );
118                assertFalse( impl.isRequestSentRetryEnabled() );
119            }
120        });
121    }
122
123    @Test
124    public void retryableConfigurationSentTest() throws Exception
125    {
126        doTestHttpClient( new Runnable()
127        {
128            @Override
129            public void run()
130            {
131                System.setProperty( "maven.wagon.http.retryHandler.class", "default" );
132                System.setProperty( "maven.wagon.http.retryHandler.requestSentEnabled", "true" );
133
134                final HttpRequestRetryHandler handler = getCurrentHandler();
135                assertNotNull( handler );
136                assertThat( handler, instanceOf( DefaultHttpRequestRetryHandler.class ) );
137                final DefaultHttpRequestRetryHandler impl = DefaultHttpRequestRetryHandler.class.cast(handler);
138                assertEquals( 3, impl.getRetryCount() );
139                assertTrue( impl.isRequestSentRetryEnabled() );
140            }
141        });
142    }
143
144    @Test
145    public void retryableConfigurationExceptionsTest() throws Exception
146    {
147        doTestHttpClient( new Runnable()
148        {
149            @Override
150            public void run()
151            {
152                System.setProperty( "maven.wagon.http.retryHandler.class", "default" );
153                System.setProperty( "maven.wagon.http.retryHandler.nonRetryableClasses", IOException.class.getName() );
154
155                final HttpRequestRetryHandler handler = getCurrentHandler();
156                assertNotNull( handler );
157                assertThat( handler, instanceOf( DefaultHttpRequestRetryHandler.class ) );
158                final DefaultHttpRequestRetryHandler impl = DefaultHttpRequestRetryHandler.class.cast(handler);
159                assertEquals( 3, impl.getRetryCount() );
160                assertFalse( impl.isRequestSentRetryEnabled() );
161
162                try
163                {
164                    final Field nonRetriableClasses = handler.getClass().getSuperclass()
165                            .getDeclaredField( "nonRetriableClasses" );
166                    if ( !nonRetriableClasses.isAccessible() )
167                    {
168                        nonRetriableClasses.setAccessible(true);
169                    }
170                    final Set<?> exceptions = Set.class.cast( nonRetriableClasses.get(handler) );
171                    assertEquals( 1, exceptions.size() );
172                    assertTrue( exceptions.contains( IOException.class ) );
173                }
174                catch ( final Exception e )
175                {
176                    fail( e.getMessage() );
177                }
178            }
179        });
180    }
181
182    private HttpRequestRetryHandler getCurrentHandler()
183    {
184        try
185        {
186            final Class<?> impl = Thread.currentThread().getContextClassLoader().loadClass(
187                        "org.apache.maven.wagon.shared.http.AbstractHttpClientWagon" );
188
189            final CloseableHttpClient httpClient = CloseableHttpClient.class.cast(
190                    impl.getMethod("getHttpClient").invoke(null) );
191
192            final Field redirectExec = httpClient.getClass().getDeclaredField( "execChain" );
193            if ( !redirectExec.isAccessible() )
194            {
195                redirectExec.setAccessible( true );
196            }
197            final RedirectExec redirectExecInstance = RedirectExec.class.cast(
198                    redirectExec.get( httpClient ) );
199
200            final Field requestExecutor = redirectExecInstance.getClass().getDeclaredField( "requestExecutor" );
201            if ( !requestExecutor.isAccessible() )
202            {
203                requestExecutor.setAccessible( true );
204            }
205            final RetryExec requestExecutorInstance = RetryExec.class.cast(
206                    requestExecutor.get( redirectExecInstance ) );
207
208            final Field retryHandler = requestExecutorInstance.getClass().getDeclaredField( "retryHandler" );
209            if ( !retryHandler.isAccessible() )
210            {
211                retryHandler.setAccessible( true );
212            }
213            return HttpRequestRetryHandler.class.cast( retryHandler.get( requestExecutorInstance ) );
214        }
215        catch ( final Exception e )
216        {
217            throw new IllegalStateException(e);
218        }
219    }
220
221    private void doTestHttpClient( final Runnable test ) throws Exception
222    {
223        final String classpath = System.getProperty( "java.class.path" );
224        final String[] paths = classpath.split( File.pathSeparator );
225        final Collection<URL> urls = new ArrayList<>( paths.length );
226        for ( final String path : paths )
227        {
228            try
229            {
230                urls.add( new File( path ).toURI().toURL() );
231            }
232            catch ( final MalformedURLException e )
233            {
234                fail( e.getMessage() );
235            }
236        }
237        final URLClassLoader loader = new URLClassLoader( urls.toArray( new URL[ paths.length ] ) , new ClassLoader()
238        {
239            @Override
240            protected Class<?> loadClass( final String name, final boolean resolve ) throws ClassNotFoundException
241            {
242                if ( name.startsWith( "org.apache.maven.wagon.shared.http" ) )
243                {
244                    throw new ClassNotFoundException( name );
245                }
246                return super.loadClass( name, resolve );
247            }
248        });
249        final Thread thread = Thread.currentThread();
250        final ClassLoader contextClassLoader = thread.getContextClassLoader();
251        thread.setContextClassLoader( loader );
252
253        final String originalClass = System.getProperty( "maven.wagon.http.retryHandler.class", "default" );
254        final String originalSentEnabled = System.getProperty(
255                "maven.wagon.http.retryHandler.requestSentEnabled", "false" );
256        final String originalCount = System.getProperty( "maven.wagon.http.retryHandler.count", "3" );
257        final String originalExceptions = System.getProperty( "maven.wagon.http.retryHandler.nonRetryableClasses",
258                InterruptedIOException.class.getName() + ","
259                    + UnknownHostException.class.getName() + ","
260                    + ConnectException.class.getName() + ","
261                    + SSLException.class.getName());
262        try
263        {
264            test.run();
265        }
266        finally
267        {
268            loader.close();
269            thread.setContextClassLoader( contextClassLoader );
270            System.setProperty(  "maven.wagon.http.retryHandler.class", originalClass );
271            System.setProperty(  "maven.wagon.http.retryHandler.requestSentEnabled", originalSentEnabled );
272            System.setProperty(  "maven.wagon.http.retryHandler.count", originalCount );
273            System.setProperty(  "maven.wagon.http.retryHandler.nonRetryableClasses", originalExceptions );
274        }
275    }
276}