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