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.Closeable;
023import java.io.IOException;
024import java.io.InputStream;
025import java.io.OutputStream;
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        try
100        {
101            task.getListener().transportStarted( resume ? task.getResumeOffset() : 0, length );
102            OutputStream os = task.newOutputStream( resume );
103            try
104            {
105                copy( os, is, task.getListener() );
106                os.close();
107            }
108            finally
109            {
110                close( os );
111            }
112        }
113        finally
114        {
115            if ( close )
116            {
117                close( is );
118            }
119        }
120    }
121
122    public void put( PutTask task )
123        throws Exception
124    {
125        failIfClosed( task );
126        implPut( task );
127    }
128
129    /**
130     * Implements {@link #put(PutTask)}, gets only called if the transporter has not been closed.
131     * 
132     * @param task The upload to perform, must not be {@code null}.
133     * @throws Exception If the transfer failed.
134     */
135    protected abstract void implPut( PutTask task )
136        throws Exception;
137
138    /**
139     * Performs stream-based I/O for the specified upload task and notifies the configured transport listener.
140     * Subclasses might want to invoke this utility method from within their {@link #implPut(PutTask)} to avoid
141     * boilerplate I/O code.
142     * 
143     * @param task The upload to perform, must not be {@code null}.
144     * @param os The output stream to upload the data to, must not be {@code null}.
145     * @param close {@code true} if the supplied output stream should be automatically closed, {@code false} to leave
146     *            the stream open.
147     * @throws IOException If the transfer encountered an I/O error.
148     * @throws TransferCancelledException If the transfer was cancelled.
149     */
150    protected void utilPut( PutTask task, OutputStream os, boolean close )
151        throws IOException, TransferCancelledException
152    {
153        try
154        {
155            task.getListener().transportStarted( 0, task.getDataLength() );
156            InputStream is = task.newInputStream();
157            try
158            {
159                copy( os, is, task.getListener() );
160            }
161            finally
162            {
163                close( is );
164            }
165            if ( close )
166            {
167                os.close();
168            }
169            else
170            {
171                os.flush();
172            }
173        }
174        finally
175        {
176            if ( close )
177            {
178                close( os );
179            }
180        }
181    }
182
183    public void close()
184    {
185        if ( closed.compareAndSet( false, true ) )
186        {
187            implClose();
188        }
189    }
190
191    /**
192     * Implements {@link #close()}, gets only called if the transporter has not already been closed.
193     */
194    protected abstract void implClose();
195
196    private void failIfClosed( TransportTask task )
197    {
198        if ( closed.get() )
199        {
200            throw new IllegalStateException( "transporter closed, cannot execute task " + task );
201        }
202    }
203
204    private static void copy( OutputStream os, InputStream is, TransportListener listener )
205        throws IOException, TransferCancelledException
206    {
207        ByteBuffer buffer = ByteBuffer.allocate( 1024 * 32 );
208        byte[] array = buffer.array();
209        for ( int read = is.read( array ); read >= 0; read = is.read( array ) )
210        {
211            os.write( array, 0, read );
212            buffer.rewind();
213            buffer.limit( read );
214            listener.transportProgressed( buffer );
215        }
216    }
217
218    private static void close( Closeable file )
219    {
220        if ( file != null )
221        {
222            try
223            {
224                file.close();
225            }
226            catch ( IOException e )
227            {
228                // irrelevant
229            }
230        }
231    }
232
233}