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.IOException;
25  import java.io.OutputStream;
26  import java.net.URI;
27  import java.nio.charset.StandardCharsets;
28  import java.nio.file.Files;
29  import java.nio.file.StandardOpenOption;
30  import java.util.Collections;
31  import java.util.HashMap;
32  import java.util.Map;
33  
34  /**
35   * A task to download a resource from the remote repository.
36   *
37   * @see Transporter#get(GetTask)
38   */
39  public final class GetTask
40      extends TransportTask
41  {
42  
43      private File dataFile;
44  
45      private boolean resume;
46  
47      private ByteArrayOutputStream dataBytes;
48  
49      private Map<String, String> checksums;
50  
51      /**
52       * Creates a new task for the specified remote resource.
53       * 
54       * @param location The relative location of the resource in the remote repository, must not be {@code null}.
55       */
56      public GetTask( URI location )
57      {
58          checksums = Collections.emptyMap();
59          setLocation( location );
60      }
61  
62      /**
63       * Opens an output stream to store the downloaded data. Depending on {@link #getDataFile()}, this stream writes
64       * either to a file on disk or a growable buffer in memory. It's the responsibility of the caller to close the
65       * provided stream.
66       * 
67       * @return The output stream for the data, never {@code null}. The stream is unbuffered.
68       * @throws IOException If the stream could not be opened.
69       */
70      public OutputStream newOutputStream()
71          throws IOException
72      {
73          return newOutputStream( false );
74      }
75  
76      /**
77       * Opens an output stream to store the downloaded data. Depending on {@link #getDataFile()}, this stream writes
78       * either to a file on disk or a growable buffer in memory. It's the responsibility of the caller to close the
79       * provided stream.
80       * 
81       * @param resume {@code true} if the download resumes from the byte offset given by {@link #getResumeOffset()},
82       *            {@code false} if the download starts at the first byte of the resource.
83       * @return The output stream for the data, never {@code null}. The stream is unbuffered.
84       * @throws IOException If the stream could not be opened.
85       */
86      public OutputStream newOutputStream( boolean resume )
87          throws IOException
88      {
89          if ( dataFile != null )
90          {
91              if ( this.resume && resume )
92              {
93                  return Files.newOutputStream( dataFile.toPath(),
94                          StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND );
95              }
96              else
97              {
98                  return Files.newOutputStream( dataFile.toPath(),
99                          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 }