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.FileOutputStream; 025import java.io.IOException; 026import java.io.OutputStream; 027import java.net.URI; 028import java.nio.charset.StandardCharsets; 029import java.util.Collections; 030import java.util.HashMap; 031import java.util.Map; 032 033/** 034 * A task to download a resource from the remote repository. 035 * 036 * @see Transporter#get(GetTask) 037 */ 038public final class GetTask 039 extends TransportTask 040{ 041 042 private File dataFile; 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 { 057 checksums = Collections.emptyMap(); 058 setLocation( location ); 059 } 060 061 /** 062 * Opens an output stream to store the downloaded data. Depending on {@link #getDataFile()}, this stream writes 063 * either to a file on disk or a growable buffer in memory. It's the responsibility of the caller to close the 064 * provided stream. 065 * 066 * @return The output stream for the data, never {@code null}. The stream is unbuffered. 067 * @throws IOException If the stream could not be opened. 068 */ 069 public OutputStream newOutputStream() 070 throws IOException 071 { 072 return newOutputStream( false ); 073 } 074 075 /** 076 * Opens an output stream to store the downloaded data. Depending on {@link #getDataFile()}, this stream writes 077 * either to a file on disk or a growable buffer in memory. It's the responsibility of the caller to close the 078 * provided stream. 079 * 080 * @param resume {@code true} if the download resumes from the byte offset given by {@link #getResumeOffset()}, 081 * {@code false} if the download starts at the first byte of the resource. 082 * @return The output stream for the data, never {@code null}. The stream is unbuffered. 083 * @throws IOException If the stream could not be opened. 084 */ 085 public OutputStream newOutputStream( boolean resume ) 086 throws IOException 087 { 088 if ( dataFile != null ) 089 { 090 return new FileOutputStream( dataFile, this.resume && resume ); 091 } 092 if ( dataBytes == null ) 093 { 094 dataBytes = new ByteArrayOutputStream( 1024 ); 095 } 096 else if ( !resume ) 097 { 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 */ 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}