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