001package org.eclipse.aether.spi.connector.transport;
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 java.io.IOException;
023import java.io.InputStream;
024import java.io.OutputStream;
025import java.nio.ByteBuffer;
026import java.util.concurrent.atomic.AtomicBoolean;
027
028import org.eclipse.aether.transfer.TransferCancelledException;
029
030/**
031 * A skeleton implementation for custom transporters.
032 */
033public abstract class AbstractTransporter
034    implements Transporter
035{
036
037    private final AtomicBoolean closed;
038
039    /**
040     * Enables subclassing.
041     */
042    protected AbstractTransporter()
043    {
044        closed = new AtomicBoolean();
045    }
046
047    public void peek( PeekTask task )
048        throws Exception
049    {
050        failIfClosed( task );
051        implPeek( task );
052    }
053
054    /**
055     * Implements {@link #peek(PeekTask)}, gets only called if the transporter has not been closed.
056     * 
057     * @param task The existence check to perform, must not be {@code null}.
058     * @throws Exception If the existence of the specified resource could not be confirmed.
059     */
060    protected abstract void implPeek( PeekTask task )
061        throws Exception;
062
063    public void get( GetTask task )
064        throws Exception
065    {
066        failIfClosed( task );
067        implGet( task );
068    }
069
070    /**
071     * Implements {@link #get(GetTask)}, gets only called if the transporter has not been closed.
072     * 
073     * @param task The download to perform, must not be {@code null}.
074     * @throws Exception If the transfer failed.
075     */
076    protected abstract void implGet( GetTask task )
077        throws Exception;
078
079    /**
080     * Performs stream-based I/O for the specified download task and notifies the configured transport listener.
081     * Subclasses might want to invoke this utility method from within their {@link #implGet(GetTask)} to avoid
082     * boilerplate I/O code.
083     * 
084     * @param task The download to perform, must not be {@code null}.
085     * @param is The input stream to download the data from, must not be {@code null}.
086     * @param close {@code true} if the supplied input stream should be automatically closed, {@code false} to leave the
087     *            stream open.
088     * @param length The size in bytes of the downloaded resource or {@code -1} if unknown, not to be confused with the
089     *            length of the supplied input stream which might be smaller if the download is resumed.
090     * @param resume {@code true} if the download resumes from {@link GetTask#getResumeOffset()}, {@code false} if the
091     *            download starts at the first byte of the resource.
092     * @throws IOException If the transfer encountered an I/O error.
093     * @throws TransferCancelledException If the transfer was cancelled.
094     */
095    protected void utilGet( GetTask task, InputStream is, boolean close, long length, boolean resume )
096        throws IOException, TransferCancelledException
097    {
098        OutputStream os = null;
099        try
100        {
101            os = task.newOutputStream( resume );
102            task.getListener().transportStarted( resume ? task.getResumeOffset() : 0L, length );
103            copy( os, is, task.getListener() );
104            os.close();
105            os = null;
106
107            if ( close )
108            {
109                is.close();
110                is = null;
111            }
112        }
113        finally
114        {
115            try
116            {
117                if ( os != null )
118                {
119                    os.close();
120                }
121            }
122            catch ( final IOException e )
123            {
124                // Suppressed due to an exception already thrown in the try block.
125            }
126            finally
127            {
128                try
129                {
130                    if ( close && is != null )
131                    {
132                        is.close();
133                    }
134                }
135                catch ( final IOException e )
136                {
137                    // Suppressed due to an exception already thrown in the try block.
138                }
139            }
140        }
141    }
142
143    public void put( PutTask task )
144        throws Exception
145    {
146        failIfClosed( task );
147        implPut( task );
148    }
149
150    /**
151     * Implements {@link #put(PutTask)}, gets only called if the transporter has not been closed.
152     * 
153     * @param task The upload to perform, must not be {@code null}.
154     * @throws Exception If the transfer failed.
155     */
156    protected abstract void implPut( PutTask task )
157        throws Exception;
158
159    /**
160     * Performs stream-based I/O for the specified upload task and notifies the configured transport listener.
161     * Subclasses might want to invoke this utility method from within their {@link #implPut(PutTask)} to avoid
162     * boilerplate I/O code.
163     * 
164     * @param task The upload to perform, must not be {@code null}.
165     * @param os The output stream to upload the data to, must not be {@code null}.
166     * @param close {@code true} if the supplied output stream should be automatically closed, {@code false} to leave
167     *            the stream open.
168     * @throws IOException If the transfer encountered an I/O error.
169     * @throws TransferCancelledException If the transfer was cancelled.
170     */
171    protected void utilPut( PutTask task, OutputStream os, boolean close )
172        throws IOException, TransferCancelledException
173    {
174        InputStream is = null;
175        try
176        {
177            task.getListener().transportStarted( 0, task.getDataLength() );
178            is = task.newInputStream();
179            copy( os, is, task.getListener() );
180
181            if ( close )
182            {
183                os.close();
184            }
185            else
186            {
187                os.flush();
188            }
189
190            os = null;
191
192            is.close();
193            is = null;
194        }
195        finally
196        {
197            try
198            {
199                if ( close && os != null )
200                {
201                    os.close();
202                }
203            }
204            catch ( final IOException e )
205            {
206                // Suppressed due to an exception already thrown in the try block.
207            }
208            finally
209            {
210                try
211                {
212                    if ( is != null )
213                    {
214                        is.close();
215                    }
216                }
217                catch ( final IOException e )
218                {
219                    // Suppressed due to an exception already thrown in the try block.
220                }
221            }
222        }
223    }
224
225    public void close()
226    {
227        if ( closed.compareAndSet( false, true ) )
228        {
229            implClose();
230        }
231    }
232
233    /**
234     * Implements {@link #close()}, gets only called if the transporter has not already been closed.
235     */
236    protected abstract void implClose();
237
238    private void failIfClosed( TransportTask task )
239    {
240        if ( closed.get() )
241        {
242            throw new IllegalStateException( "transporter closed, cannot execute task " + task );
243        }
244    }
245
246    private static void copy( OutputStream os, InputStream is, TransportListener listener )
247        throws IOException, TransferCancelledException
248    {
249        ByteBuffer buffer = ByteBuffer.allocate( 1024 * 32 );
250        byte[] array = buffer.array();
251        for ( int read = is.read( array ); read >= 0; read = is.read( array ) )
252        {
253            os.write( array, 0, read );
254            buffer.rewind();
255            buffer.limit( read );
256            listener.transportProgressed( buffer );
257        }
258    }
259
260}