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.ByteArrayOutputStream;
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.OutputStream;
25 import java.io.UncheckedIOException;
26 import java.net.URI;
27 import java.nio.charset.StandardCharsets;
28 import java.nio.file.Files;
29 import java.nio.file.Path;
30 import java.nio.file.StandardOpenOption;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.Map;
34
35 /**
36 * A task to download a resource from the remote repository.
37 *
38 * @see Transporter#get(GetTask)
39 */
40 public final class GetTask extends TransportTask {
41
42 private Path dataPath;
43
44 private boolean resume;
45
46 private ByteArrayOutputStream dataBytes;
47
48 private Map<String, String> checksums;
49
50 /**
51 * Creates a new task for the specified remote resource.
52 *
53 * @param location The relative location of the resource in the remote repository, must not be {@code null}.
54 */
55 public GetTask(URI location) {
56 checksums = Collections.emptyMap();
57 setLocation(location);
58 }
59
60 /**
61 * Opens an output stream to store the downloaded data. Depending on {@link #getDataFile()}, this stream writes
62 * either to a file on disk or a growable buffer in memory. It's the responsibility of the caller to close the
63 * provided stream.
64 *
65 * @return The output stream for the data, never {@code null}. The stream is unbuffered.
66 * @throws IOException If the stream could not be opened.
67 */
68 public OutputStream newOutputStream() throws IOException {
69 return newOutputStream(false);
70 }
71
72 /**
73 * Opens an output stream to store the downloaded data. Depending on {@link #getDataFile()}, this stream writes
74 * either to a file on disk or a growable buffer in memory. It's the responsibility of the caller to close the
75 * provided stream.
76 *
77 * @param resume {@code true} if the download resumes from the byte offset given by {@link #getResumeOffset()},
78 * {@code false} if the download starts at the first byte of the resource.
79 * @return The output stream for the data, never {@code null}. The stream is unbuffered.
80 * @throws IOException If the stream could not be opened.
81 */
82 public OutputStream newOutputStream(boolean resume) throws IOException {
83 if (dataPath != null) {
84 if (this.resume && resume) {
85 return Files.newOutputStream(
86 dataPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND);
87 } else {
88 return Files.newOutputStream(
89 dataPath,
90 StandardOpenOption.CREATE,
91 StandardOpenOption.WRITE,
92 StandardOpenOption.TRUNCATE_EXISTING);
93 }
94 }
95 if (dataBytes == null) {
96 dataBytes = new ByteArrayOutputStream(1024);
97 } else if (!resume) {
98 dataBytes.reset();
99 }
100 return dataBytes;
101 }
102
103 /**
104 * Gets the file (if any) where the downloaded data should be stored. If the specified file already exists, it will
105 * be overwritten.
106 *
107 * @return The data file or {@code null} if the data will be buffered in memory.
108 * @deprecated Use {@link #getDataPath()} instead.
109 */
110 @Deprecated
111 public File getDataFile() {
112 return dataPath != null ? dataPath.toFile() : null;
113 }
114
115 /**
116 * Gets the file (if any) where the downloaded data should be stored. If the specified file already exists, it will
117 * be overwritten.
118 *
119 * @return The data file or {@code null} if the data will be buffered in memory.
120 * @since 2.0.0
121 */
122 public Path getDataPath() {
123 return dataPath;
124 }
125
126 /**
127 * Sets the file where the downloaded data should be stored. If the specified file already exists, it will be
128 * overwritten. Unless the caller can reasonably expect the resource to be small, use of a data file is strongly
129 * recommended to avoid exhausting heap memory during the download.
130 *
131 * @param dataFile The file to store the downloaded data, may be {@code null} to store the data in memory.
132 * @return This task for chaining, never {@code null}.
133 * @deprecated Use {@link #setDataPath(Path)} instead.
134 */
135 @Deprecated
136 public GetTask setDataFile(File dataFile) {
137 return setDataFile(dataFile, false);
138 }
139
140 /**
141 * Sets the file where the downloaded data should be stored. If the specified file already exists, it will be
142 * overwritten. Unless the caller can reasonably expect the resource to be small, use of a data file is strongly
143 * recommended to avoid exhausting heap memory during the download.
144 *
145 * @param dataPath The file to store the downloaded data, may be {@code null} to store the data in memory.
146 * @return This task for chaining, never {@code null}.
147 * @since 2.0.0
148 */
149 public GetTask setDataPath(Path dataPath) {
150 return setDataPath(dataPath, false);
151 }
152
153 /**
154 * Sets the file where the downloaded data should be stored. If the specified file already exists, it will be
155 * overwritten or appended to, depending on the {@code resume} argument and the capabilities of the transporter.
156 * Unless the caller can reasonably expect the resource to be small, use of a data file is strongly recommended to
157 * avoid exhausting heap memory during the download.
158 *
159 * @param dataFile The file to store the downloaded data, may be {@code null} to store the data in memory.
160 * @param resume {@code true} to request resuming a previous download attempt, starting from the current length of
161 * the data file, {@code false} to download the resource from its beginning.
162 * @return This task for chaining, never {@code null}.
163 * @deprecated Use {@link #setDataPath(Path, boolean)} instead.
164 */
165 @Deprecated
166 public GetTask setDataFile(File dataFile, boolean resume) {
167 return setDataPath(dataFile != null ? dataFile.toPath() : null, resume);
168 }
169
170 /**
171 * Sets the file where the downloaded data should be stored. If the specified file already exists, it will be
172 * overwritten or appended to, depending on the {@code resume} argument and the capabilities of the transporter.
173 * Unless the caller can reasonably expect the resource to be small, use of a data file is strongly recommended to
174 * avoid exhausting heap memory during the download.
175 *
176 * @param dataPath The file to store the downloaded data, may be {@code null} to store the data in memory.
177 * @param resume {@code true} to request resuming a previous download attempt, starting from the current length of
178 * the data file, {@code false} to download the resource from its beginning.
179 * @return This task for chaining, never {@code null}.
180 * @since 2.0.0
181 */
182 public GetTask setDataPath(Path dataPath, boolean resume) {
183 this.dataPath = dataPath;
184 this.resume = resume;
185 return this;
186 }
187
188 /**
189 * Gets the byte offset within the resource from which the download should resume if supported.
190 *
191 * @return The zero-based index of the first byte to download or {@code 0} for a full download from the start of the
192 * resource, never negative.
193 */
194 public long getResumeOffset() {
195 if (resume) {
196 if (dataPath != null) {
197 try {
198 return Files.size(dataPath);
199 } catch (IOException e) {
200 throw new UncheckedIOException(e);
201 }
202 }
203 if (dataBytes != null) {
204 return dataBytes.size();
205 }
206 }
207 return 0;
208 }
209
210 /**
211 * Gets the data that was downloaded into memory. <strong>Note:</strong> This method may only be called if
212 * {@link #getDataFile()} is {@code null} as otherwise the downloaded data has been written directly to disk.
213 *
214 * @return The possibly empty data bytes, never {@code null}.
215 */
216 public byte[] getDataBytes() {
217 if (dataPath != null || dataBytes == null) {
218 return EMPTY;
219 }
220 return dataBytes.toByteArray();
221 }
222
223 /**
224 * Gets the data that was downloaded into memory as a string. The downloaded data is assumed to be encoded using
225 * UTF-8. <strong>Note:</strong> This method may only be called if {@link #getDataFile()} is {@code null} as
226 * otherwise the downloaded data has been written directly to disk.
227 *
228 * @return The possibly empty data string, never {@code null}.
229 */
230 public String getDataString() {
231 if (dataPath != null || dataBytes == null) {
232 return "";
233 }
234 return new String(dataBytes.toByteArray(), StandardCharsets.UTF_8);
235 }
236
237 /**
238 * Sets the listener that is to be notified during the transfer.
239 *
240 * @param listener The listener to notify of progress, may be {@code null}.
241 * @return This task for chaining, never {@code null}.
242 */
243 public GetTask setListener(TransportListener listener) {
244 super.setListener(listener);
245 return this;
246 }
247
248 /**
249 * Gets the checksums which the remote repository advertises for the resource. The map is keyed by algorithm name
250 * and the values are hexadecimal representations of the corresponding value. <em>Note:</em> This is optional
251 * data that a transporter may return if the underlying transport protocol provides metadata (e.g. HTTP headers)
252 * along with the actual resource data. Checksums returned by this method have kind of
253 * {@link org.eclipse.aether.spi.connector.checksum.ChecksumPolicy.ChecksumKind#REMOTE_INCLUDED}.
254 *
255 * @return The (read-only) checksums advertised for the downloaded resource, possibly empty but never {@code null}.
256 */
257 public Map<String, String> getChecksums() {
258 return checksums;
259 }
260
261 /**
262 * Sets a checksum which the remote repository advertises for the resource. <em>Note:</em> Transporters should only
263 * use this method to record checksum information which is readily available while performing the actual download,
264 * they should not perform additional transfers to gather this data.
265 *
266 * @param algorithm The name of the checksum algorithm (e.g. {@code "SHA-1"}, may be {@code null}.
267 * @param value The hexadecimal representation of the checksum, may be {@code null}.
268 * @return This task for chaining, never {@code null}.
269 */
270 public GetTask setChecksum(String algorithm, String value) {
271 if (algorithm != null) {
272 if (checksums.isEmpty()) {
273 checksums = new HashMap<>();
274 }
275 if (value != null && !value.isEmpty()) {
276 checksums.put(algorithm, value);
277 } else {
278 checksums.remove(algorithm);
279 }
280 }
281 return this;
282 }
283
284 @Override
285 public String toString() {
286 return "<< " + getLocation();
287 }
288 }