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