View Javadoc
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 }