1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.eclipse.aether.spi.connector.transport;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.nio.ByteBuffer;
25 import java.util.concurrent.atomic.AtomicBoolean;
26
27 import org.eclipse.aether.transfer.TransferCancelledException;
28
29 import static java.util.Objects.requireNonNull;
30
31 /**
32 * A skeleton implementation for custom transporters.
33 */
34 public abstract class AbstractTransporter implements Transporter {
35
36 private final AtomicBoolean closed;
37
38 /**
39 * Enables subclassing.
40 */
41 protected AbstractTransporter() {
42 closed = new AtomicBoolean();
43 }
44
45 public void peek(PeekTask task) throws Exception {
46 requireNonNull(task, "task cannot be null");
47
48 failIfClosed(task);
49 implPeek(task);
50 }
51
52 /**
53 * Implements {@link #peek(PeekTask)}, gets only called if the transporter has not been closed.
54 *
55 * @param task The existence check to perform, must not be {@code null}.
56 * @throws Exception If the existence of the specified resource could not be confirmed.
57 */
58 protected abstract void implPeek(PeekTask task) throws Exception;
59
60 public void get(GetTask task) throws Exception {
61 requireNonNull(task, "task cannot be null");
62
63 failIfClosed(task);
64 implGet(task);
65 }
66
67 /**
68 * Implements {@link #get(GetTask)}, gets only called if the transporter has not been closed.
69 *
70 * @param task The download to perform, must not be {@code null}.
71 * @throws Exception If the transfer failed.
72 */
73 protected abstract void implGet(GetTask task) throws Exception;
74
75 /**
76 * Performs stream-based I/O for the specified download task and notifies the configured transport listener.
77 * Subclasses might want to invoke this utility method from within their {@link #implGet(GetTask)} to avoid
78 * boilerplate I/O code.
79 *
80 * @param task The download to perform, must not be {@code null}.
81 * @param is The input stream to download the data from, must not be {@code null}.
82 * @param close {@code true} if the supplied input stream should be automatically closed, {@code false} to leave the
83 * stream open.
84 * @param length The size in bytes of the downloaded resource or {@code -1} if unknown, not to be confused with the
85 * length of the supplied input stream which might be smaller if the download is resumed.
86 * @param resume {@code true} if the download resumes from {@link GetTask#getResumeOffset()}, {@code false} if the
87 * download starts at the first byte of the resource.
88 * @throws IOException If the transfer encountered an I/O error.
89 * @throws TransferCancelledException If the transfer was cancelled.
90 */
91 protected void utilGet(GetTask task, InputStream is, boolean close, long length, boolean resume)
92 throws IOException, TransferCancelledException {
93 if (close) {
94 try (InputStream input = is;
95 OutputStream os = task.newOutputStream(resume)) {
96 task.getListener().transportStarted(resume ? task.getResumeOffset() : 0L, length);
97 copy(os, input, task.getListener());
98 }
99 } else {
100 try (OutputStream os = task.newOutputStream(resume)) {
101 task.getListener().transportStarted(resume ? task.getResumeOffset() : 0L, length);
102 copy(os, is, task.getListener());
103 }
104 }
105 }
106
107 public void put(PutTask task) throws Exception {
108 requireNonNull(task, "task cannot be null");
109
110 failIfClosed(task);
111 implPut(task);
112 }
113
114 /**
115 * Implements {@link #put(PutTask)}, gets only called if the transporter has not been closed.
116 *
117 * @param task The upload to perform, must not be {@code null}.
118 * @throws Exception If the transfer failed.
119 */
120 protected abstract void implPut(PutTask task) throws Exception;
121
122 /**
123 * Performs stream-based I/O for the specified upload task and notifies the configured transport listener.
124 * Subclasses might want to invoke this utility method from within their {@link #implPut(PutTask)} to avoid
125 * boilerplate I/O code.
126 *
127 * @param task The upload to perform, must not be {@code null}.
128 * @param os The output stream to upload the data to, must not be {@code null}.
129 * @param close {@code true} if the supplied output stream should be automatically closed, {@code false} to leave
130 * the stream open.
131 * @throws IOException If the transfer encountered an I/O error.
132 * @throws TransferCancelledException If the transfer was cancelled.
133 */
134 protected void utilPut(PutTask task, OutputStream os, boolean close)
135 throws IOException, TransferCancelledException {
136 if (close) {
137 try (OutputStream output = os;
138 InputStream is = task.newInputStream()) {
139 task.getListener().transportStarted(0, task.getDataLength());
140 copy(output, is, task.getListener());
141 }
142 } else {
143 try (InputStream is = task.newInputStream()) {
144 task.getListener().transportStarted(0, task.getDataLength());
145 copy(os, is, task.getListener());
146 } finally {
147 os.flush();
148 }
149 }
150 }
151
152 public void close() {
153 if (closed.compareAndSet(false, true)) {
154 implClose();
155 }
156 }
157
158 /**
159 * Implements {@link #close()}, gets only called if the transporter has not already been closed.
160 */
161 protected abstract void implClose();
162
163 private void failIfClosed(TransportTask task) {
164 if (closed.get()) {
165 throw new IllegalStateException("transporter closed, cannot execute task " + task);
166 }
167 }
168
169 private static void copy(OutputStream os, InputStream is, TransportListener listener)
170 throws IOException, TransferCancelledException {
171 byte[] buffer = new byte[1024 * 32];
172 for (int read = is.read(buffer); read >= 0; read = is.read(buffer)) {
173 os.write(buffer, 0, read);
174 listener.transportProgressed(ByteBuffer.wrap(buffer, 0, read));
175 }
176 }
177 }