1 package org.eclipse.aether.spi.connector.transport;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.io.ByteArrayOutputStream;
23 import java.io.File;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.OutputStream;
27 import java.io.UnsupportedEncodingException;
28 import java.net.URI;
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
39 extends TransportTask
40 {
41
42 private File dataFile;
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 {
57 checksums = Collections.emptyMap();
58 setLocation( location );
59 }
60
61 /**
62 * Opens an output stream to store the downloaded data. Depending on {@link #getDataFile()}, this stream writes
63 * either to a file on disk or a growable buffer in memory. It's the responsibility of the caller to close the
64 * provided stream.
65 *
66 * @return The output stream for the data, never {@code null}. The stream is unbuffered.
67 * @throws IOException If the stream could not be opened.
68 */
69 public OutputStream newOutputStream()
70 throws IOException
71 {
72 return newOutputStream( false );
73 }
74
75 /**
76 * Opens an output stream to store the downloaded data. Depending on {@link #getDataFile()}, this stream writes
77 * either to a file on disk or a growable buffer in memory. It's the responsibility of the caller to close the
78 * provided stream.
79 *
80 * @param resume {@code true} if the download resumes from the byte offset given by {@link #getResumeOffset()},
81 * {@code false} if the download starts at the first byte of the resource.
82 * @return The output stream for the data, never {@code null}. The stream is unbuffered.
83 * @throws IOException If the stream could not be opened.
84 */
85 public OutputStream newOutputStream( boolean resume )
86 throws IOException
87 {
88 if ( dataFile != null )
89 {
90 return new FileOutputStream( dataFile, this.resume && resume );
91 }
92 if ( dataBytes == null )
93 {
94 dataBytes = new ByteArrayOutputStream( 1024 );
95 }
96 else if ( !resume )
97 {
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 */
109 public File getDataFile()
110 {
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 {
124 return setDataFile( dataFile, false );
125 }
126
127 /**
128 * Sets the file where the downloaded data should be stored. If the specified file already exists, it will be
129 * overwritten or appended to, depending on the {@code resume} argument and the capabilities of the transporter.
130 * Unless the caller can reasonably expect the resource to be small, use of a data file is strongly recommended to
131 * avoid exhausting heap memory during the download.
132 *
133 * @param dataFile The file to store the downloaded data, may be {@code null} to store the data in memory.
134 * @param resume {@code true} to request resuming a previous download attempt, starting from the current length of
135 * the data file, {@code false} to download the resource from its beginning.
136 * @return This task for chaining, never {@code null}.
137 */
138 public GetTask setDataFile( File dataFile, boolean resume )
139 {
140 this.dataFile = dataFile;
141 this.resume = resume;
142 return this;
143 }
144
145 /**
146 * Gets the byte offset within the resource from which the download should resume if supported.
147 *
148 * @return The zero-based index of the first byte to download or {@code 0} for a full download from the start of the
149 * resource, never negative.
150 */
151 public long getResumeOffset()
152 {
153 if ( resume )
154 {
155 if ( dataFile != null )
156 {
157 return dataFile.length();
158 }
159 if ( dataBytes != null )
160 {
161 return dataBytes.size();
162 }
163 }
164 return 0;
165 }
166
167 /**
168 * Gets the data that was downloaded into memory. <strong>Note:</strong> This method may only be called if
169 * {@link #getDataFile()} is {@code null} as otherwise the downloaded data has been written directly to disk.
170 *
171 * @return The possibly empty data bytes, never {@code null}.
172 */
173 public byte[] getDataBytes()
174 {
175 if ( dataFile != null || dataBytes == null )
176 {
177 return EMPTY;
178 }
179 return dataBytes.toByteArray();
180 }
181
182 /**
183 * Gets the data that was downloaded into memory as a string. The downloaded data is assumed to be encoded using
184 * UTF-8. <strong>Note:</strong> This method may only be called if {@link #getDataFile()} is {@code null} as
185 * otherwise the downloaded data has been written directly to disk.
186 *
187 * @return The possibly empty data string, never {@code null}.
188 */
189 public String getDataString()
190 {
191 if ( dataFile != null || dataBytes == null )
192 {
193 return "";
194 }
195 try
196 {
197 return dataBytes.toString( "UTF-8" );
198 }
199 catch ( UnsupportedEncodingException e )
200 {
201 throw new IllegalStateException( e );
202 }
203 }
204
205 /**
206 * Sets the listener that is to be notified during the transfer.
207 *
208 * @param listener The listener to notify of progress, may be {@code null}.
209 * @return This task for chaining, never {@code null}.
210 */
211 public GetTask setListener( TransportListener listener )
212 {
213 super.setListener( listener );
214 return this;
215 }
216
217 /**
218 * Gets the checksums which the remote repository advertises for the resource. The map is keyed by algorithm name
219 * (cf. {@link java.security.MessageDigest#getInstance(String)}) and the values are hexadecimal representations of
220 * the corresponding value. <em>Note:</em> This is optional data that a transporter may return if the underlying
221 * transport protocol provides metadata (e.g. HTTP headers) along with the actual resource data.
222 *
223 * @return The (read-only) checksums advertised for the downloaded resource, possibly empty but never {@code null}.
224 */
225 public Map<String, String> getChecksums()
226 {
227 return checksums;
228 }
229
230 /**
231 * Sets a checksum which the remote repository advertises for the resource. <em>Note:</em> Transporters should only
232 * use this method to record checksum information which is readily available while performing the actual download,
233 * they should not perform additional transfers to gather this data.
234 *
235 * @param algorithm The name of the checksum algorithm (e.g. {@code "SHA-1"}, cf.
236 * {@link java.security.MessageDigest#getInstance(String)} ), may be {@code null}.
237 * @param value The hexadecimal representation of the checksum, may be {@code null}.
238 * @return This task for chaining, never {@code null}.
239 */
240 public GetTask setChecksum( String algorithm, String value )
241 {
242 if ( algorithm != null )
243 {
244 if ( checksums.isEmpty() )
245 {
246 checksums = new HashMap<String, String>();
247 }
248 if ( value != null && value.length() > 0 )
249 {
250 checksums.put( algorithm, value );
251 }
252 else
253 {
254 checksums.remove( algorithm );
255 }
256 }
257 return this;
258 }
259
260 @Override
261 public String toString()
262 {
263 return "<< " + getLocation();
264 }
265
266 }