View Javadoc
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 }