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