1 package org.eclipse.aether.spi.connector.transport;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.nio.Buffer;
26 import java.nio.ByteBuffer;
27 import java.util.Objects;
28 import java.util.concurrent.atomic.AtomicBoolean;
29
30 import org.eclipse.aether.transfer.TransferCancelledException;
31
32 /**
33 * A skeleton implementation for custom transporters.
34 */
35 public abstract class AbstractTransporter
36 implements Transporter
37 {
38
39 private final AtomicBoolean closed;
40
41 /**
42 * Enables subclassing.
43 */
44 protected AbstractTransporter()
45 {
46 closed = new AtomicBoolean();
47 }
48
49 public void peek( PeekTask task )
50 throws Exception
51 {
52 Objects.requireNonNull( "task", "task cannot be null" );
53
54 failIfClosed( task );
55 implPeek( task );
56 }
57
58 /**
59 * Implements {@link #peek(PeekTask)}, gets only called if the transporter has not been closed.
60 *
61 * @param task The existence check to perform, must not be {@code null}.
62 * @throws Exception If the existence of the specified resource could not be confirmed.
63 */
64 protected abstract void implPeek( PeekTask task )
65 throws Exception;
66
67 public void get( GetTask task )
68 throws Exception
69 {
70 Objects.requireNonNull( "task", "task cannot be null" );
71
72 failIfClosed( task );
73 implGet( task );
74 }
75
76 /**
77 * Implements {@link #get(GetTask)}, gets only called if the transporter has not been closed.
78 *
79 * @param task The download to perform, must not be {@code null}.
80 * @throws Exception If the transfer failed.
81 */
82 protected abstract void implGet( GetTask task )
83 throws Exception;
84
85 /**
86 * Performs stream-based I/O for the specified download task and notifies the configured transport listener.
87 * Subclasses might want to invoke this utility method from within their {@link #implGet(GetTask)} to avoid
88 * boilerplate I/O code.
89 *
90 * @param task The download to perform, must not be {@code null}.
91 * @param is The input stream to download the data from, must not be {@code null}.
92 * @param close {@code true} if the supplied input stream should be automatically closed, {@code false} to leave the
93 * stream open.
94 * @param length The size in bytes of the downloaded resource or {@code -1} if unknown, not to be confused with the
95 * length of the supplied input stream which might be smaller if the download is resumed.
96 * @param resume {@code true} if the download resumes from {@link GetTask#getResumeOffset()}, {@code false} if the
97 * download starts at the first byte of the resource.
98 * @throws IOException If the transfer encountered an I/O error.
99 * @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 }