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.net.URI; 28 import java.nio.charset.StandardCharsets; 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 return new String( dataBytes.toByteArray(), StandardCharsets.UTF_8 ); 196 } 197 198 /** 199 * Sets the listener that is to be notified during the transfer. 200 * 201 * @param listener The listener to notify of progress, may be {@code null}. 202 * @return This task for chaining, never {@code null}. 203 */ 204 public GetTask setListener( TransportListener listener ) 205 { 206 super.setListener( listener ); 207 return this; 208 } 209 210 /** 211 * Gets the checksums which the remote repository advertises for the resource. The map is keyed by algorithm name 212 * (cf. {@link java.security.MessageDigest#getInstance(String)}) and the values are hexadecimal representations of 213 * the corresponding value. <em>Note:</em> This is optional data that a transporter may return if the underlying 214 * transport protocol provides metadata (e.g. HTTP headers) along with the actual resource data. 215 * 216 * @return The (read-only) checksums advertised for the downloaded resource, possibly empty but never {@code null}. 217 */ 218 public Map<String, String> getChecksums() 219 { 220 return checksums; 221 } 222 223 /** 224 * Sets a checksum which the remote repository advertises for the resource. <em>Note:</em> Transporters should only 225 * use this method to record checksum information which is readily available while performing the actual download, 226 * they should not perform additional transfers to gather this data. 227 * 228 * @param algorithm The name of the checksum algorithm (e.g. {@code "SHA-1"}, cf. 229 * {@link java.security.MessageDigest#getInstance(String)} ), may be {@code null}. 230 * @param value The hexadecimal representation of the checksum, may be {@code null}. 231 * @return This task for chaining, never {@code null}. 232 */ 233 public GetTask setChecksum( String algorithm, String value ) 234 { 235 if ( algorithm != null ) 236 { 237 if ( checksums.isEmpty() ) 238 { 239 checksums = new HashMap<>(); 240 } 241 if ( value != null && value.length() > 0 ) 242 { 243 checksums.put( algorithm, value ); 244 } 245 else 246 { 247 checksums.remove( algorithm ); 248 } 249 } 250 return this; 251 } 252 253 @Override 254 public String toString() 255 { 256 return "<< " + getLocation(); 257 } 258 259 }