diff options
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Get.java')
-rw-r--r-- | framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Get.java | 883 |
1 files changed, 883 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Get.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Get.java new file mode 100644 index 00000000..c636ab54 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/taskdefs/Get.java @@ -0,0 +1,883 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.tools.ant.taskdefs; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.Date; +import java.util.zip.GZIPInputStream; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.MagicNames; +import org.apache.tools.ant.Main; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Mapper; +import org.apache.tools.ant.types.Resource; +import org.apache.tools.ant.types.ResourceCollection; +import org.apache.tools.ant.types.resources.Resources; +import org.apache.tools.ant.types.resources.URLProvider; +import org.apache.tools.ant.types.resources.URLResource; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.FileUtils; + +/** + * Gets a particular file from a URL source. + * Options include verbose reporting, timestamp based fetches and controlling + * actions on failures. NB: access through a firewall only works if the whole + * Java runtime is correctly configured. + * + * @since Ant 1.1 + * + * @ant.task category="network" + */ +public class Get extends Task { + private static final int NUMBER_RETRIES = 3; + private static final int DOTS_PER_LINE = 50; + private static final int BIG_BUFFER_SIZE = 100 * 1024; + private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); + private static final int REDIRECT_LIMIT = 25; + // HttpURLConnection doesn't have a constant for this in Java5 and + // what it calls HTTP_MOVED_TEMP would better be FOUND + private static final int HTTP_MOVED_TEMP = 307; + + private static final String HTTP = "http"; + private static final String HTTPS = "https"; + + private static final String DEFAULT_AGENT_PREFIX = "Apache Ant"; + private static final String GZIP_CONTENT_ENCODING = "gzip"; + + private final Resources sources = new Resources(); + private File destination; // required + private boolean verbose = false; + private boolean quiet = false; + private boolean useTimestamp = false; //off by default + private boolean ignoreErrors = false; + private String uname = null; + private String pword = null; + private long maxTime = 0; + private int numberRetries = NUMBER_RETRIES; + private boolean skipExisting = false; + private boolean httpUseCaches = true; // on by default + private boolean tryGzipEncoding = false; + private Mapper mapperElement = null; + private String userAgent = + System.getProperty(MagicNames.HTTP_AGENT_PROPERTY, + DEFAULT_AGENT_PREFIX + "/" + + Main.getShortAntVersion()); + + /** + * Does the work. + * + * @exception BuildException Thrown in unrecoverable error. + */ + @Override + public void execute() throws BuildException { + checkAttributes(); + + for (final Resource r : sources) { + final URLProvider up = r.as(URLProvider.class); + final URL source = up.getURL(); + + File dest = destination; + if (destination.isDirectory()) { + if (mapperElement == null) { + String path = source.getPath(); + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + final int slash = path.lastIndexOf("/"); + if (slash > -1) { + path = path.substring(slash + 1); + } + dest = new File(destination, path); + } else { + final FileNameMapper mapper = mapperElement.getImplementation(); + final String[] d = mapper.mapFileName(source.toString()); + if (d == null) { + log("skipping " + r + " - mapper can't handle it", + Project.MSG_WARN); + continue; + } else if (d.length == 0) { + log("skipping " + r + " - mapper returns no file name", + Project.MSG_WARN); + continue; + } else if (d.length > 1) { + log("skipping " + r + " - mapper returns multiple file" + + " names", Project.MSG_WARN); + continue; + } + dest = new File(destination, d[0]); + } + } + + //set up logging + final int logLevel = Project.MSG_INFO; + DownloadProgress progress = null; + if (verbose) { + progress = new VerboseProgress(System.out); + } + + //execute the get + try { + doGet(source, dest, logLevel, progress); + } catch (final IOException ioe) { + log("Error getting " + source + " to " + dest); + if (!ignoreErrors) { + throw new BuildException(ioe, getLocation()); + } + } + } + } + + /** + * make a get request, with the supplied progress and logging info. + * All the other config parameters are set at the task level, + * source, dest, ignoreErrors, etc. + * @param logLevel level to log at, see {@link Project#log(String, int)} + * @param progress progress callback; null for no-callbacks + * @return true for a successful download, false otherwise. + * The return value is only relevant when {@link #ignoreErrors} is true, as + * when false all failures raise BuildExceptions. + * @throws IOException for network trouble + * @throws BuildException for argument errors, or other trouble when ignoreErrors + * is false. + * @deprecated only gets the first configured resource + */ + @Deprecated + public boolean doGet(final int logLevel, final DownloadProgress progress) + throws IOException { + checkAttributes(); + for (final Resource r : sources) { + final URLProvider up = r.as(URLProvider.class); + final URL source = up.getURL(); + return doGet(source, destination, logLevel, progress); + } + /*NOTREACHED*/ + return false; + } + + /** + * make a get request, with the supplied progress and logging info. + * + * All the other config parameters like ignoreErrors are set at + * the task level. + * @param source the URL to get + * @param dest the target file + * @param logLevel level to log at, see {@link Project#log(String, int)} + * @param progress progress callback; null for no-callbacks + * @return true for a successful download, false otherwise. + * The return value is only relevant when {@link #ignoreErrors} is true, as + * when false all failures raise BuildExceptions. + * @throws IOException for network trouble + * @throws BuildException for argument errors, or other trouble when ignoreErrors + * is false. + * @since Ant 1.8.0 + */ + public boolean doGet(final URL source, final File dest, final int logLevel, + DownloadProgress progress) + throws IOException { + + if (dest.exists() && skipExisting) { + log("Destination already exists (skipping): " + + dest.getAbsolutePath(), logLevel); + return true; + } + + //dont do any progress, unless asked + if (progress == null) { + progress = new NullProgress(); + } + log("Getting: " + source, logLevel); + log("To: " + dest.getAbsolutePath(), logLevel); + + //set the timestamp to the file date. + long timestamp = 0; + + boolean hasTimestamp = false; + if (useTimestamp && dest.exists()) { + timestamp = dest.lastModified(); + if (verbose) { + final Date t = new Date(timestamp); + log("local file date : " + t.toString(), logLevel); + } + hasTimestamp = true; + } + + final GetThread getThread = new GetThread(source, dest, + hasTimestamp, timestamp, progress, + logLevel, userAgent); + getThread.setDaemon(true); + getProject().registerThreadTask(getThread, this); + getThread.start(); + try { + getThread.join(maxTime * 1000); + } catch (final InterruptedException ie) { + log("interrupted waiting for GET to finish", + Project.MSG_VERBOSE); + } + + if (getThread.isAlive()) { + final String msg = "The GET operation took longer than " + maxTime + + " seconds, stopping it."; + if (ignoreErrors) { + log(msg); + } + getThread.closeStreams(); + if (!ignoreErrors) { + throw new BuildException(msg); + } + return false; + } + + return getThread.wasSuccessful(); + } + + @Override + public void log(final String msg, final int msgLevel) { + if (!quiet || msgLevel >= Project.MSG_ERR) { + super.log(msg, msgLevel); + } + } + + /** + * Check the attributes. + */ + private void checkAttributes() { + + if (userAgent == null || userAgent.trim().length() == 0) { + throw new BuildException("userAgent may not be null or empty"); + } + + if (sources.size() == 0) { + throw new BuildException("at least one source is required", + getLocation()); + } + for (final Resource r : sources) { + final URLProvider up = r.as(URLProvider.class); + if (up == null) { + throw new BuildException("Only URLProvider resources are" + + " supported", getLocation()); + } + } + + if (destination == null) { + throw new BuildException("dest attribute is required", getLocation()); + } + + if (destination.exists() && sources.size() > 1 + && !destination.isDirectory()) { + throw new BuildException("The specified destination is not a" + + " directory", + getLocation()); + } + + if (destination.exists() && !destination.canWrite()) { + throw new BuildException("Can't write to " + + destination.getAbsolutePath(), + getLocation()); + } + + if (sources.size() > 1 && !destination.exists()) { + destination.mkdirs(); + } + } + + /** + * Set an URL to get. + * + * @param u URL for the file. + */ + public void setSrc(final URL u) { + add(new URLResource(u)); + } + + /** + * Adds URLs to get. + * @since Ant 1.8.0 + */ + public void add(final ResourceCollection rc) { + sources.add(rc); + } + + /** + * Where to copy the source file. + * + * @param dest Path to file. + */ + public void setDest(final File dest) { + this.destination = dest; + } + + /** + * If true, show verbose progress information. + * + * @param v if "true" then be verbose + */ + public void setVerbose(final boolean v) { + verbose = v; + } + + /** + * If true, set default log level to Project.MSG_ERR. + * + * @param v if "true" then be quiet + * @since Ant 1.9.4 + */ + public void setQuiet(final boolean v){ + this.quiet = v; + } + + /** + * If true, log errors but do not treat as fatal. + * + * @param v if "true" then don't report download errors up to ant + */ + public void setIgnoreErrors(final boolean v) { + ignoreErrors = v; + } + + /** + * If true, conditionally download a file based on the timestamp + * of the local copy. + * + * <p>In this situation, the if-modified-since header is set so + * that the file is only fetched if it is newer than the local + * file (or there is no local file) This flag is only valid on + * HTTP connections, it is ignored in other cases. When the flag + * is set, the local copy of the downloaded file will also have + * its timestamp set to the remote file time.</p> + * + * <p>Note that remote files of date 1/1/1970 (GMT) are treated as + * 'no timestamp', and web servers often serve files with a + * timestamp in the future by replacing their timestamp with that + * of the current time. Also, inter-computer clock differences can + * cause no end of grief.</p> + * @param v "true" to enable file time fetching + */ + public void setUseTimestamp(final boolean v) { + useTimestamp = v; + } + + + /** + * Username for basic auth. + * + * @param u username for authentication + */ + public void setUsername(final String u) { + this.uname = u; + } + + /** + * password for the basic authentication. + * + * @param p password for authentication + */ + public void setPassword(final String p) { + this.pword = p; + } + + /** + * The time in seconds the download is allowed to take before + * being terminated. + * + * @since Ant 1.8.0 + */ + public void setMaxTime(final long maxTime) { + this.maxTime = maxTime; + } + + /** + * The number of retries to attempt upon error, defaults to 3. + * + * @param r retry count + * + * @since Ant 1.8.0 + */ + public void setRetries(final int r) { + this.numberRetries = r; + } + + /** + * Skip files that already exist locally. + * + * @param s "true" to skip existing destination files + * + * @since Ant 1.8.0 + */ + public void setSkipExisting(final boolean s) { + this.skipExisting = s; + } + + /** + * HTTP connections only - set the user-agent to be used + * when communicating with remote server. if null, then + * the value is considered unset and the behaviour falls + * back to the default of the http API. + * + * @since Ant 1.9.3 + */ + public void setUserAgent(final String userAgent) { + this.userAgent = userAgent; + } + + /** + * HTTP connections only - control caching on the + * HttpUrlConnection: httpConnection.setUseCaches(); if false, do + * not allow caching on the HttpUrlConnection. + * + * <p>Defaults to true (allow caching, which is also the + * HttpUrlConnection default value.</p> + * + * @since Ant 1.8.0 + */ + public void setHttpUseCaches(final boolean httpUseCache) { + this.httpUseCaches = httpUseCache; + } + + /** + * Whether to transparently try to reduce bandwidth by telling the + * server ant would support gzip encoding. + * + * <p>Setting this to true also means Ant will uncompress + * <code>.tar.gz</code> and similar files automatically.</p> + * + * @since Ant 1.9.5 + */ + public void setTryGzipEncoding(boolean b) { + tryGzipEncoding = b; + } + + /** + * Define the mapper to map source to destination files. + * @return a mapper to be configured. + * @exception BuildException if more than one mapper is defined. + * @since Ant 1.8.0 + */ + public Mapper createMapper() throws BuildException { + if (mapperElement != null) { + throw new BuildException("Cannot define more than one mapper", + getLocation()); + } + mapperElement = new Mapper(getProject()); + return mapperElement; + } + + /** + * Add a nested filenamemapper. + * @param fileNameMapper the mapper to add. + * @since Ant 1.8.0 + */ + public void add(final FileNameMapper fileNameMapper) { + createMapper().add(fileNameMapper); + } + + /** + * Provide this for Backward Compatibility. + */ + protected static class Base64Converter + extends org.apache.tools.ant.util.Base64Converter { + } + + /** + * Interface implemented for reporting + * progress of downloading. + */ + public interface DownloadProgress { + /** + * begin a download + */ + void beginDownload(); + + /** + * tick handler + * + */ + void onTick(); + + /** + * end a download + */ + void endDownload(); + } + + /** + * do nothing with progress info + */ + public static class NullProgress implements DownloadProgress { + + /** + * begin a download + */ + public void beginDownload() { + } + + /** + * tick handler + * + */ + public void onTick() { + } + + /** + * end a download + */ + public void endDownload() { + } + } + + /** + * verbose progress system prints to some output stream + */ + public static class VerboseProgress implements DownloadProgress { + private int dots = 0; + // CheckStyle:VisibilityModifier OFF - bc + PrintStream out; + // CheckStyle:VisibilityModifier ON + + /** + * Construct a verbose progress reporter. + * @param out the output stream. + */ + public VerboseProgress(final PrintStream out) { + this.out = out; + } + + /** + * begin a download + */ + public void beginDownload() { + dots = 0; + } + + /** + * tick handler + * + */ + public void onTick() { + out.print("."); + if (dots++ > DOTS_PER_LINE) { + out.flush(); + dots = 0; + } + } + + /** + * end a download + */ + public void endDownload() { + out.println(); + out.flush(); + } + } + + private class GetThread extends Thread { + + private final URL source; + private final File dest; + private final boolean hasTimestamp; + private final long timestamp; + private final DownloadProgress progress; + private final int logLevel; + + private boolean success = false; + private IOException ioexception = null; + private BuildException exception = null; + private InputStream is = null; + private OutputStream os = null; + private URLConnection connection; + private int redirections = 0; + private String userAgent = null; + + GetThread(final URL source, final File dest, + final boolean h, final long t, final DownloadProgress p, final int l, final String userAgent) { + this.source = source; + this.dest = dest; + hasTimestamp = h; + timestamp = t; + progress = p; + logLevel = l; + this.userAgent = userAgent; + } + + @Override + public void run() { + try { + success = get(); + } catch (final IOException ioex) { + ioexception = ioex; + } catch (final BuildException bex) { + exception = bex; + } + } + + private boolean get() throws IOException, BuildException { + + connection = openConnection(source); + + if (connection == null) { + return false; + } + + final boolean downloadSucceeded = downloadFile(); + + //if (and only if) the use file time option is set, then + //the saved file now has its timestamp set to that of the + //downloaded file + if (downloadSucceeded && useTimestamp) { + updateTimeStamp(); + } + + return downloadSucceeded; + } + + + private boolean redirectionAllowed(final URL aSource, final URL aDest) { + if (!(aSource.getProtocol().equals(aDest.getProtocol()) || (HTTP + .equals(aSource.getProtocol()) && HTTPS.equals(aDest + .getProtocol())))) { + final String message = "Redirection detected from " + + aSource.getProtocol() + " to " + aDest.getProtocol() + + ". Protocol switch unsafe, not allowed."; + if (ignoreErrors) { + log(message, logLevel); + return false; + } else { + throw new BuildException(message); + } + } + + redirections++; + if (redirections > REDIRECT_LIMIT) { + final String message = "More than " + REDIRECT_LIMIT + + " times redirected, giving up"; + if (ignoreErrors) { + log(message, logLevel); + return false; + } else { + throw new BuildException(message); + } + } + + + return true; + } + + private URLConnection openConnection(final URL aSource) throws IOException { + + // set up the URL connection + final URLConnection connection = aSource.openConnection(); + // modify the headers + // NB: things like user authentication could go in here too. + if (hasTimestamp) { + connection.setIfModifiedSince(timestamp); + } + // Set the user agent + connection.addRequestProperty("User-Agent", this.userAgent); + + // prepare Java 1.1 style credentials + if (uname != null || pword != null) { + final String up = uname + ":" + pword; + String encoding; + // we do not use the sun impl for portability, + // and always use our own implementation for consistent + // testing + final Base64Converter encoder = new Base64Converter(); + encoding = encoder.encode(up.getBytes()); + connection.setRequestProperty("Authorization", "Basic " + + encoding); + } + + if (tryGzipEncoding) { + connection.setRequestProperty("Accept-Encoding", GZIP_CONTENT_ENCODING); + } + + if (connection instanceof HttpURLConnection) { + ((HttpURLConnection) connection) + .setInstanceFollowRedirects(false); + ((HttpURLConnection) connection) + .setUseCaches(httpUseCaches); + } + // connect to the remote site (may take some time) + try { + connection.connect(); + } catch (final NullPointerException e) { + //bad URLs can trigger NPEs in some JVMs + throw new BuildException("Failed to parse " + source.toString(), e); + } + + // First check on a 301 / 302 (moved) response (HTTP only) + if (connection instanceof HttpURLConnection) { + final HttpURLConnection httpConnection = (HttpURLConnection) connection; + final int responseCode = httpConnection.getResponseCode(); + if (isMoved(responseCode)) { + final String newLocation = httpConnection.getHeaderField("Location"); + final String message = aSource + + (responseCode == HttpURLConnection.HTTP_MOVED_PERM ? " permanently" + : "") + " moved to " + newLocation; + log(message, logLevel); + final URL newURL = new URL(aSource, newLocation); + if (!redirectionAllowed(aSource, newURL)) { + return null; + } + return openConnection(newURL); + } + // next test for a 304 result (HTTP only) + final long lastModified = httpConnection.getLastModified(); + if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED + || (lastModified != 0 && hasTimestamp && timestamp >= lastModified)) { + // not modified so no file download. just return + // instead and trace out something so the user + // doesn't think that the download happened when it + // didn't + log("Not modified - so not downloaded", logLevel); + return null; + } + // test for 401 result (HTTP only) + if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) { + final String message = "HTTP Authorization failure"; + if (ignoreErrors) { + log(message, logLevel); + return null; + } else { + throw new BuildException(message); + } + } + } + + //REVISIT: at this point even non HTTP connections may + //support the if-modified-since behaviour -we just check + //the date of the content and skip the write if it is not + //newer. Some protocols (FTP) don't include dates, of + //course. + return connection; + } + + private boolean isMoved(final int responseCode) { + return responseCode == HttpURLConnection.HTTP_MOVED_PERM || + responseCode == HttpURLConnection.HTTP_MOVED_TEMP || + responseCode == HttpURLConnection.HTTP_SEE_OTHER || + responseCode == HTTP_MOVED_TEMP; + } + + private boolean downloadFile() + throws FileNotFoundException, IOException { + for (int i = 0; i < numberRetries; i++) { + // this three attempt trick is to get round quirks in different + // Java implementations. Some of them take a few goes to bind + // properly; we ignore the first couple of such failures. + try { + is = connection.getInputStream(); + break; + } catch (final IOException ex) { + log("Error opening connection " + ex, logLevel); + } + } + if (is == null) { + log("Can't get " + source + " to " + dest, logLevel); + if (ignoreErrors) { + return false; + } + throw new BuildException("Can't get " + source + " to " + dest, + getLocation()); + } + + if (tryGzipEncoding + && GZIP_CONTENT_ENCODING.equals(connection.getContentEncoding())) { + is = new GZIPInputStream(is); + } + + os = new FileOutputStream(dest); + progress.beginDownload(); + boolean finished = false; + try { + final byte[] buffer = new byte[BIG_BUFFER_SIZE]; + int length; + while (!isInterrupted() && (length = is.read(buffer)) >= 0) { + os.write(buffer, 0, length); + progress.onTick(); + } + finished = !isInterrupted(); + } finally { + FileUtils.close(os); + FileUtils.close(is); + + // we have started to (over)write dest, but failed. + // Try to delete the garbage we'd otherwise leave + // behind. + if (!finished) { + dest.delete(); + } + } + progress.endDownload(); + return true; + } + + private void updateTimeStamp() { + final long remoteTimestamp = connection.getLastModified(); + if (verbose) { + final Date t = new Date(remoteTimestamp); + log("last modified = " + t.toString() + + ((remoteTimestamp == 0) + ? " - using current time instead" + : ""), logLevel); + } + if (remoteTimestamp != 0) { + FILE_UTILS.setFileLastModified(dest, remoteTimestamp); + } + } + + /** + * Has the download completed successfully? + * + * <p>Re-throws any exception caught during executaion.</p> + */ + boolean wasSuccessful() throws IOException, BuildException { + if (ioexception != null) { + throw ioexception; + } + if (exception != null) { + throw exception; + } + return success; + } + + /** + * Closes streams, interrupts the download, may delete the + * output file. + */ + void closeStreams() { + interrupt(); + FileUtils.close(os); + FileUtils.close(is); + if (!success && dest.exists()) { + dest.delete(); + } + } + } +} |