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