001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.eclipse.aether.spi.connector.transport; 020 021import java.io.ByteArrayOutputStream; 022import java.io.File; 023import java.io.IOException; 024import java.io.OutputStream; 025import java.io.UncheckedIOException; 026import java.net.URI; 027import java.nio.charset.StandardCharsets; 028import java.nio.file.Files; 029import java.nio.file.Path; 030import java.nio.file.StandardOpenOption; 031import java.util.Collections; 032import java.util.HashMap; 033import java.util.Map; 034 035/** 036 * A task to download a resource from the remote repository. 037 * 038 * @see Transporter#get(GetTask) 039 */ 040public final class GetTask extends TransportTask { 041 042 private Path dataPath; 043 044 private boolean resume; 045 046 private ByteArrayOutputStream dataBytes; 047 048 private Map<String, String> checksums; 049 050 /** 051 * Creates a new task for the specified remote resource. 052 * 053 * @param location The relative location of the resource in the remote repository, must not be {@code null}. 054 */ 055 public GetTask(URI location) { 056 checksums = Collections.emptyMap(); 057 setLocation(location); 058 } 059 060 /** 061 * Opens an output stream to store the downloaded data. Depending on {@link #getDataFile()}, this stream writes 062 * either to a file on disk or a growable buffer in memory. It's the responsibility of the caller to close the 063 * provided stream. 064 * 065 * @return The output stream for the data, never {@code null}. The stream is unbuffered. 066 * @throws IOException If the stream could not be opened. 067 */ 068 public OutputStream newOutputStream() throws IOException { 069 return newOutputStream(false); 070 } 071 072 /** 073 * Opens an output stream to store the downloaded data. Depending on {@link #getDataFile()}, this stream writes 074 * either to a file on disk or a growable buffer in memory. It's the responsibility of the caller to close the 075 * provided stream. 076 * 077 * @param resume {@code true} if the download resumes from the byte offset given by {@link #getResumeOffset()}, 078 * {@code false} if the download starts at the first byte of the resource. 079 * @return The output stream for the data, never {@code null}. The stream is unbuffered. 080 * @throws IOException If the stream could not be opened. 081 */ 082 public OutputStream newOutputStream(boolean resume) throws IOException { 083 if (dataPath != null) { 084 if (this.resume && resume) { 085 return Files.newOutputStream( 086 dataPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND); 087 } else { 088 return Files.newOutputStream( 089 dataPath, 090 StandardOpenOption.CREATE, 091 StandardOpenOption.WRITE, 092 StandardOpenOption.TRUNCATE_EXISTING); 093 } 094 } 095 if (dataBytes == null) { 096 dataBytes = new ByteArrayOutputStream(1024); 097 } else if (!resume) { 098 dataBytes.reset(); 099 } 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}