001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs;
020:
021: import org.apache.tools.ant.BuildException;
022: import org.apache.tools.ant.Project;
023: import org.apache.tools.ant.Task;
024: import org.apache.tools.ant.util.FileUtils;
025:
026: import java.io.File;
027: import java.io.FileOutputStream;
028: import java.io.IOException;
029: import java.io.InputStream;
030: import java.io.PrintStream;
031: import java.net.HttpURLConnection;
032: import java.net.URL;
033: import java.net.URLConnection;
034: import java.util.Date;
035:
036: /**
037: * Gets a particular file from a URL source.
038: * Options include verbose reporting, timestamp based fetches and controlling
039: * actions on failures. NB: access through a firewall only works if the whole
040: * Java runtime is correctly configured.
041: *
042: * @since Ant 1.1
043: *
044: * @ant.task category="network"
045: */
046: public class Get extends Task {
047:
048: private static final FileUtils FILE_UTILS = FileUtils
049: .getFileUtils();
050:
051: private URL source; // required
052: private File dest; // required
053: private boolean verbose = false;
054: private boolean useTimestamp = false; //off by default
055: private boolean ignoreErrors = false;
056: private String uname = null;
057: private String pword = null;
058:
059: /**
060: * Does the work.
061: *
062: * @exception BuildException Thrown in unrecoverable error.
063: */
064: public void execute() throws BuildException {
065:
066: //set up logging
067: int logLevel = Project.MSG_INFO;
068: DownloadProgress progress = null;
069: if (verbose) {
070: progress = new VerboseProgress(System.out);
071: }
072:
073: //execute the get
074: try {
075: doGet(logLevel, progress);
076: } catch (IOException ioe) {
077: log("Error getting " + source + " to " + dest);
078: if (!ignoreErrors) {
079: throw new BuildException(ioe, getLocation());
080: }
081: }
082: }
083:
084: /**
085: * make a get request, with the supplied progress and logging info.
086: * All the other config parameters are set at the task level,
087: * source, dest, ignoreErrors, etc.
088: * @param logLevel level to log at, see {@link Project#log(String, int)}
089: * @param progress progress callback; null for no-callbacks
090: * @return true for a successful download, false otherwise.
091: * The return value is only relevant when {@link #ignoreErrors} is true, as
092: * when false all failures raise BuildExceptions.
093: * @throws IOException for network trouble
094: * @throws BuildException for argument errors, or other trouble when ignoreErrors
095: * is false.
096: */
097: public boolean doGet(int logLevel, DownloadProgress progress)
098: throws IOException {
099: if (source == null) {
100: throw new BuildException("src attribute is required",
101: getLocation());
102: }
103:
104: if (dest == null) {
105: throw new BuildException("dest attribute is required",
106: getLocation());
107: }
108:
109: if (dest.exists() && dest.isDirectory()) {
110: throw new BuildException(
111: "The specified destination is a directory",
112: getLocation());
113: }
114:
115: if (dest.exists() && !dest.canWrite()) {
116: throw new BuildException("Can't write to "
117: + dest.getAbsolutePath(), getLocation());
118: }
119: //dont do any progress, unless asked
120: if (progress == null) {
121: progress = new NullProgress();
122: }
123: log("Getting: " + source, logLevel);
124: log("To: " + dest.getAbsolutePath(), logLevel);
125:
126: //set the timestamp to the file date.
127: long timestamp = 0;
128:
129: boolean hasTimestamp = false;
130: if (useTimestamp && dest.exists()) {
131: timestamp = dest.lastModified();
132: if (verbose) {
133: Date t = new Date(timestamp);
134: log("local file date : " + t.toString(), logLevel);
135: }
136: hasTimestamp = true;
137: }
138:
139: //set up the URL connection
140: URLConnection connection = source.openConnection();
141: //modify the headers
142: //NB: things like user authentication could go in here too.
143: if (hasTimestamp) {
144: connection.setIfModifiedSince(timestamp);
145: }
146: // prepare Java 1.1 style credentials
147: if (uname != null || pword != null) {
148: String up = uname + ":" + pword;
149: String encoding;
150: //we do not use the sun impl for portability,
151: //and always use our own implementation for consistent
152: //testing
153: Base64Converter encoder = new Base64Converter();
154: encoding = encoder.encode(up.getBytes());
155: connection.setRequestProperty("Authorization", "Basic "
156: + encoding);
157: }
158:
159: //connect to the remote site (may take some time)
160: connection.connect();
161: //next test for a 304 result (HTTP only)
162: if (connection instanceof HttpURLConnection) {
163: HttpURLConnection httpConnection = (HttpURLConnection) connection;
164: long lastModified = httpConnection.getLastModified();
165: if (httpConnection.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED
166: || (lastModified != 0 && hasTimestamp && timestamp >= lastModified)) {
167: //not modified so no file download. just return
168: //instead and trace out something so the user
169: //doesn't think that the download happened when it
170: //didn't
171: log("Not modified - so not downloaded", logLevel);
172: return false;
173: }
174: // test for 401 result (HTTP only)
175: if (httpConnection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
176: String message = "HTTP Authorization failure";
177: if (ignoreErrors) {
178: log(message, logLevel);
179: return false;
180: } else {
181: throw new BuildException(message);
182: }
183: }
184:
185: }
186:
187: //REVISIT: at this point even non HTTP connections may
188: //support the if-modified-since behaviour -we just check
189: //the date of the content and skip the write if it is not
190: //newer. Some protocols (FTP) don't include dates, of
191: //course.
192:
193: InputStream is = null;
194: for (int i = 0; i < 3; i++) {
195: //this three attempt trick is to get round quirks in different
196: //Java implementations. Some of them take a few goes to bind
197: //property; we ignore the first couple of such failures.
198: try {
199: is = connection.getInputStream();
200: break;
201: } catch (IOException ex) {
202: log("Error opening connection " + ex, logLevel);
203: }
204: }
205: if (is == null) {
206: log("Can't get " + source + " to " + dest, logLevel);
207: if (ignoreErrors) {
208: return false;
209: }
210: throw new BuildException("Can't get " + source + " to "
211: + dest, getLocation());
212: }
213:
214: FileOutputStream fos = new FileOutputStream(dest);
215: progress.beginDownload();
216: boolean finished = false;
217: try {
218: byte[] buffer = new byte[100 * 1024];
219: int length;
220: while ((length = is.read(buffer)) >= 0) {
221: fos.write(buffer, 0, length);
222: progress.onTick();
223: }
224: finished = true;
225: } finally {
226: FileUtils.close(fos);
227: FileUtils.close(is);
228:
229: // we have started to (over)write dest, but failed.
230: // Try to delete the garbage we'd otherwise leave
231: // behind.
232: if (!finished) {
233: dest.delete();
234: }
235: }
236: progress.endDownload();
237:
238: //if (and only if) the use file time option is set, then
239: //the saved file now has its timestamp set to that of the
240: //downloaded file
241: if (useTimestamp) {
242: long remoteTimestamp = connection.getLastModified();
243: if (verbose) {
244: Date t = new Date(remoteTimestamp);
245: log(
246: "last modified = "
247: + t.toString()
248: + ((remoteTimestamp == 0) ? " - using current time instead"
249: : ""), logLevel);
250: }
251: if (remoteTimestamp != 0) {
252: FILE_UTILS.setFileLastModified(dest, remoteTimestamp);
253: }
254: }
255:
256: //successful download
257: return true;
258: }
259:
260: /**
261: * Set the URL to get.
262: *
263: * @param u URL for the file.
264: */
265: public void setSrc(URL u) {
266: this .source = u;
267: }
268:
269: /**
270: * Where to copy the source file.
271: *
272: * @param dest Path to file.
273: */
274: public void setDest(File dest) {
275: this .dest = dest;
276: }
277:
278: /**
279: * If true, show verbose progress information.
280: *
281: * @param v if "true" then be verbose
282: */
283: public void setVerbose(boolean v) {
284: verbose = v;
285: }
286:
287: /**
288: * If true, log errors but do not treat as fatal.
289: *
290: * @param v if "true" then don't report download errors up to ant
291: */
292: public void setIgnoreErrors(boolean v) {
293: ignoreErrors = v;
294: }
295:
296: /**
297: * If true, conditionally download a file based on the timestamp
298: * of the local copy.
299: *
300: * <p>In this situation, the if-modified-since header is set so
301: * that the file is only fetched if it is newer than the local
302: * file (or there is no local file) This flag is only valid on
303: * HTTP connections, it is ignored in other cases. When the flag
304: * is set, the local copy of the downloaded file will also have
305: * its timestamp set to the remote file time.</p>
306: *
307: * <p>Note that remote files of date 1/1/1970 (GMT) are treated as
308: * 'no timestamp', and web servers often serve files with a
309: * timestamp in the future by replacing their timestamp with that
310: * of the current time. Also, inter-computer clock differences can
311: * cause no end of grief.</p>
312: * @param v "true" to enable file time fetching
313: */
314: public void setUseTimestamp(boolean v) {
315: useTimestamp = v;
316: }
317:
318: /**
319: * Username for basic auth.
320: *
321: * @param u username for authentication
322: */
323: public void setUsername(String u) {
324: this .uname = u;
325: }
326:
327: /**
328: * password for the basic authentication.
329: *
330: * @param p password for authentication
331: */
332: public void setPassword(String p) {
333: this .pword = p;
334: }
335:
336: /**
337: * Provide this for Backward Compatibility.
338: */
339: protected static class Base64Converter extends
340: org.apache.tools.ant.util.Base64Converter {
341: }
342:
343: /**
344: * Interface implemented for reporting
345: * progess of downloading.
346: */
347: public interface DownloadProgress {
348: /**
349: * begin a download
350: */
351: void beginDownload();
352:
353: /**
354: * tick handler
355: *
356: */
357: void onTick();
358:
359: /**
360: * end a download
361: */
362: void endDownload();
363: }
364:
365: /**
366: * do nothing with progress info
367: */
368: public static class NullProgress implements DownloadProgress {
369:
370: /**
371: * begin a download
372: */
373: public void beginDownload() {
374:
375: }
376:
377: /**
378: * tick handler
379: *
380: */
381: public void onTick() {
382: }
383:
384: /**
385: * end a download
386: */
387: public void endDownload() {
388:
389: }
390: }
391:
392: /**
393: * verbose progress system prints to some output stream
394: */
395: public static class VerboseProgress implements DownloadProgress {
396: private int dots = 0;
397: // CheckStyle:VisibilityModifier OFF - bc
398: PrintStream out;
399:
400: // CheckStyle:VisibilityModifier ON
401:
402: /**
403: * Construct a verbose progress reporter.
404: * @param out the output stream.
405: */
406: public VerboseProgress(PrintStream out) {
407: this .out = out;
408: }
409:
410: /**
411: * begin a download
412: */
413: public void beginDownload() {
414: dots = 0;
415: }
416:
417: /**
418: * tick handler
419: *
420: */
421: public void onTick() {
422: out.print(".");
423: if (dots++ > 50) {
424: out.flush();
425: dots = 0;
426: }
427: }
428:
429: /**
430: * end a download
431: */
432: public void endDownload() {
433: out.println();
434: out.flush();
435: }
436: }
437:
438: }
|