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