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