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