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.ByteBuffer;
26 import java.util.Objects;
27 import java.util.concurrent.atomic.AtomicBoolean;
28
29 import org.eclipse.aether.transfer.TransferCancelledException;
30
31 /**
32 * A skeleton implementation for custom transporters.
33 */
34 public abstract class AbstractTransporter
35 implements Transporter
36 {
37
38 private final AtomicBoolean closed;
39
40 /**
41 * Enables subclassing.
42 */
43 protected AbstractTransporter()
44 {
45 closed = new AtomicBoolean();
46 }
47
48 public void peek( PeekTask task )
49 throws Exception
50 {
51 Objects.requireNonNull( task, "task cannot be null" );
52
53 failIfClosed( task );
54 implPeek( task );
55 }
56
57 /**
58 * Implements {@link #peek(PeekTask)}, gets only called if the transporter has not been closed.
59 *
60 * @param task The existence check to perform, must not be {@code null}.
61 * @throws Exception If the existence of the specified resource could not be confirmed.
62 */
63 protected abstract void implPeek( PeekTask task )
64 throws Exception;
65
66 public void get( GetTask task )
67 throws Exception
68 {
69 Objects.requireNonNull( task, "task cannot be null" );
70
71 failIfClosed( task );
72 implGet( task );
73 }
74
75 /**
76 * Implements {@link #get(GetTask)}, gets only called if the transporter has not been closed.
77 *
78 * @param task The download to perform, must not be {@code null}.
79 * @throws Exception If the transfer failed.
80 */
81 protected abstract void implGet( GetTask task )
82 throws Exception;
83
84 /**
85 * Performs stream-based I/O for the specified download task and notifies the configured transport listener.
86 * Subclasses might want to invoke this utility method from within their {@link #implGet(GetTask)} to avoid
87 * boilerplate I/O code.
88 *
89 * @param task The download to perform, must not be {@code null}.
90 * @param is The input stream to download the data from, must not be {@code null}.
91 * @param close {@code true} if the supplied input stream should be automatically closed, {@code false} to leave the
92 * stream open.
93 * @param length The size in bytes of the downloaded resource or {@code -1} if unknown, not to be confused with the
94 * length of the supplied input stream which might be smaller if the download is resumed.
95 * @param resume {@code true} if the download resumes from {@link GetTask#getResumeOffset()}, {@code false} if the
96 * download starts at the first byte of the resource.
97 * @throws IOException If the transfer encountered an I/O error.
98 * @throws TransferCancelledException If the transfer was cancelled.
99 */
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 }