001package org.eclipse.aether.connector.basic;
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.junit.Assert.*;
023import static org.junit.Assume.*;
024
025import java.io.Closeable;
026import java.io.File;
027import java.io.FileOutputStream;
028import java.io.IOException;
029import java.io.OutputStream;
030import java.io.RandomAccessFile;
031import java.nio.channels.FileLock;
032import java.util.ArrayList;
033import java.util.List;
034import java.util.Locale;
035import java.util.concurrent.CountDownLatch;
036
037import org.eclipse.aether.internal.test.util.TestFileUtils;
038import org.eclipse.aether.internal.test.util.TestLoggerFactory;
039import org.junit.After;
040import org.junit.Before;
041import org.junit.Test;
042
043public class PartialFileTest
044{
045
046    private static class StubRemoteAccessChecker
047        implements PartialFile.RemoteAccessChecker
048    {
049
050        Exception exception;
051
052        int invocations;
053
054        public void checkRemoteAccess()
055            throws Exception
056        {
057            invocations++;
058            if ( exception != null )
059            {
060                throw exception;
061            }
062        }
063
064    }
065
066    private static class ConcurrentWriter
067        extends Thread
068    {
069
070        private final File dstFile;
071
072        private final File partFile;
073
074        private final File lockFile;
075
076        private final CountDownLatch locked;
077
078        private final int sleep;
079
080        volatile int length;
081
082        Exception error;
083
084        public ConcurrentWriter( File dstFile, int sleep, int length )
085            throws InterruptedException
086        {
087            super( "ConcurrentWriter-" + dstFile.getAbsolutePath() );
088            this.dstFile = dstFile;
089            partFile = new File( dstFile.getPath() + PartialFile.EXT_PART );
090            lockFile = new File( partFile.getPath() + PartialFile.EXT_LOCK );
091            this.sleep = sleep;
092            this.length = length;
093            locked = new CountDownLatch( 1 );
094            start();
095            locked.await();
096        }
097
098        @Override
099        public void run()
100        {
101            RandomAccessFile raf = null;
102            FileLock lock = null;
103            OutputStream out = null;
104            try
105            {
106                raf = new RandomAccessFile( lockFile, "rw" );
107                lock = raf.getChannel().lock( 0, 1, false );
108                locked.countDown();
109                out = new FileOutputStream( partFile );
110                for ( int i = 0, n = Math.abs( length ); i < n; i++ )
111                {
112                    for ( long start = System.currentTimeMillis(); System.currentTimeMillis() - start < sleep; )
113                    {
114                        Thread.sleep( 10 );
115                    }
116                    out.write( 65 );
117                    out.flush();
118                    System.out.println( "  " + System.currentTimeMillis() + " Wrote byte " + ( i + 1 ) + "/"
119                                            + n );
120                }
121                if ( length >= 0 && !dstFile.setLastModified( System.currentTimeMillis() ) )
122                {
123                    throw new IOException( "Could not update destination file" );
124                }
125
126                out.close();
127                out = null;
128                lock.release();
129                lock = null;
130                raf.close();
131                raf = null;
132            }
133            catch ( Exception e )
134            {
135                error = e;
136            }
137            finally
138            {
139                try
140                {
141                    if ( out != null )
142                    {
143                        out.close();
144                    }
145                }
146                catch ( final IOException e )
147                {
148                    // Suppressed due to an exception already thrown in the try block.
149                }
150                finally
151                {
152                    try
153                    {
154                        if ( lock != null )
155                        {
156                            lock.release();
157                        }
158                    }
159                    catch ( final IOException e )
160                    {
161                        // Suppressed due to an exception already thrown in the try block.
162                    }
163                    finally
164                    {
165                        try
166                        {
167                            if ( raf != null )
168                            {
169                                raf.close();
170                            }
171                        }
172                        catch ( final IOException e )
173                        {
174                            // Suppressed due to an exception already thrown in the try block.
175                        }
176                        finally
177                        {
178                            if ( !lockFile.delete() )
179                            {
180                                lockFile.deleteOnExit();
181                            }
182                        }
183                    }
184                }
185            }
186        }
187
188    }
189
190    private static final boolean PROPER_LOCK_SUPPORT;
191
192    static
193    {
194        String javaVersion = System.getProperty( "java.version" ).trim();
195        boolean notJava5 = !javaVersion.startsWith( "1.5." );
196        String osName = System.getProperty( "os.name" ).toLowerCase( Locale.ENGLISH );
197        boolean windows = osName.contains( "windows" );
198        PROPER_LOCK_SUPPORT = notJava5 || windows;
199    }
200
201    private StubRemoteAccessChecker remoteAccessChecker;
202
203    private File dstFile;
204
205    private File partFile;
206
207    private File lockFile;
208
209    private List<Closeable> closeables;
210
211    private PartialFile newPartialFile( long resumeThreshold, int requestTimeout )
212        throws Exception
213    {
214        PartialFile.Factory factory =
215            new PartialFile.Factory( resumeThreshold >= 0L, resumeThreshold, requestTimeout,
216                                     new TestLoggerFactory().getLogger( "" ) );
217        PartialFile partFile = factory.newInstance( dstFile, remoteAccessChecker );
218        if ( partFile != null )
219        {
220            closeables.add( partFile );
221        }
222        return partFile;
223    }
224
225    @Before
226    public void init()
227        throws Exception
228    {
229        closeables = new ArrayList<Closeable>();
230        remoteAccessChecker = new StubRemoteAccessChecker();
231        dstFile = TestFileUtils.createTempFile( "Hello World!" );
232        partFile = new File( dstFile.getPath() + PartialFile.EXT_PART );
233        lockFile = new File( partFile.getPath() + PartialFile.EXT_LOCK );
234    }
235
236    @After
237    public void exit()
238    {
239        for ( Closeable closeable : closeables )
240        {
241            try
242            {
243                closeable.close();
244            }
245            catch ( Exception e )
246            {
247                e.printStackTrace();
248            }
249        }
250    }
251
252    @Test
253    public void testCloseNonResumableFile()
254        throws Exception
255    {
256        PartialFile partialFile = newPartialFile( -1, 100 );
257        assertNotNull( partialFile );
258        assertNotNull( partialFile.getFile() );
259        assertTrue( partialFile.getFile().getAbsolutePath(), partialFile.getFile().isFile() );
260        partialFile.close();
261        assertFalse( partialFile.getFile().getAbsolutePath(), partialFile.getFile().exists() );
262    }
263
264    @Test
265    public void testCloseResumableFile()
266        throws Exception
267    {
268        PartialFile partialFile = newPartialFile( 0, 100 );
269        assertNotNull( partialFile );
270        assertNotNull( partialFile.getFile() );
271        assertTrue( partialFile.getFile().getAbsolutePath(), partialFile.getFile().isFile() );
272        assertEquals( partFile, partialFile.getFile() );
273        assertTrue( lockFile.getAbsolutePath(), lockFile.isFile() );
274        partialFile.close();
275        assertTrue( partialFile.getFile().getAbsolutePath(), partialFile.getFile().isFile() );
276        assertFalse( lockFile.getAbsolutePath(), lockFile.exists() );
277    }
278
279    @Test
280    public void testResumableFileCreationError()
281        throws Exception
282    {
283        assertTrue( partFile.getAbsolutePath(), partFile.mkdirs() );
284        PartialFile partialFile = newPartialFile( 0, 100 );
285        assertNotNull( partialFile );
286        assertFalse( partialFile.isResume() );
287        assertFalse( lockFile.getAbsolutePath(), lockFile.exists() );
288    }
289
290    @Test
291    public void testResumeThreshold()
292        throws Exception
293    {
294        PartialFile partialFile = newPartialFile( 0, 100 );
295        assertNotNull( partialFile );
296        assertTrue( partialFile.isResume() );
297        partialFile.close();
298        partialFile = newPartialFile( 1, 100 );
299        assertNotNull( partialFile );
300        assertFalse( partialFile.isResume() );
301        partialFile.close();
302    }
303
304    @Test( timeout = 10000L )
305    public void testResumeConcurrently_RequestTimeout()
306        throws Exception
307    {
308        assumeTrue( PROPER_LOCK_SUPPORT );
309        ConcurrentWriter writer = new ConcurrentWriter( dstFile, 5 * 1000, 1 );
310        try
311        {
312            newPartialFile( 0, 1000 );
313            fail( "expected exception" );
314        }
315        catch ( Exception e )
316        {
317            assertTrue( e.getMessage().contains( "Timeout" ) );
318        }
319        writer.interrupt();
320        writer.join();
321    }
322
323    @Test( timeout = 10000L )
324    public void testResumeConcurrently_AwaitCompletion_ConcurrentWriterSucceeds()
325        throws Exception
326    {
327        assumeTrue( PROPER_LOCK_SUPPORT );
328        assertTrue( dstFile.setLastModified( System.currentTimeMillis() - 60L * 1000L ) );
329        ConcurrentWriter writer = new ConcurrentWriter( dstFile, 100, 10 );
330        assertNull( newPartialFile( 0, 500 ) );
331        writer.join();
332        assertNull( writer.error );
333        assertEquals( 1, remoteAccessChecker.invocations );
334    }
335
336    @Test( timeout = 10000L )
337    public void testResumeConcurrently_AwaitCompletion_ConcurrentWriterFails()
338        throws Exception
339    {
340        assumeTrue( PROPER_LOCK_SUPPORT );
341        assertTrue( dstFile.setLastModified( System.currentTimeMillis() - 60L * 1000L ) );
342        ConcurrentWriter writer = new ConcurrentWriter( dstFile, 100, -10 );
343        PartialFile partialFile = newPartialFile( 0, 500 );
344        assertNotNull( partialFile );
345        assertTrue( partialFile.isResume() );
346        writer.join();
347        assertNull( writer.error );
348        assertEquals( 1, remoteAccessChecker.invocations );
349    }
350
351    @Test( timeout = 10000L )
352    public void testResumeConcurrently_CheckRemoteAccess()
353        throws Exception
354    {
355        assumeTrue( PROPER_LOCK_SUPPORT );
356        remoteAccessChecker.exception = new IOException( "missing" );
357        ConcurrentWriter writer = new ConcurrentWriter( dstFile, 1000, 1 );
358        try
359        {
360            newPartialFile( 0, 1000 );
361            fail( "expected exception" );
362        }
363        catch ( Exception e )
364        {
365            assertSame( remoteAccessChecker.exception, e );
366        }
367        writer.interrupt();
368        writer.join();
369    }
370
371}