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.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.isEmpty()) {
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 }