001package com.hfg.anttask; 002 003import java.io.File; 004import java.io.IOException; 005import java.util.Calendar; 006import java.util.Collections; 007import java.util.Iterator; 008import java.util.List; 009import java.util.regex.Matcher; 010import java.util.regex.Pattern; 011 012import org.apache.tools.ant.BuildException; 013import org.apache.tools.ant.Task; 014 015import com.hfg.datetime.DateSortDirection; 016import com.hfg.util.StringUtil; 017import com.hfg.util.io.HTTPRemoteFileLister; 018import com.hfg.util.io.RemoteFile; 019import com.hfg.util.io.RemoteFileLister; 020import com.hfg.util.io.RemoteFileListerFactory; 021import com.hfg.util.io.RemoteFileTimestampComparator; 022 023 024//------------------------------------------------------------------------------ 025/** 026 Ant task for retrieving a particular file from a wildcarded URL source. 027 In the case of multiple matches, the newest is the one returned. HTTP, FTP and file system URL's are supported. 028 <p> 029 Normally, this task is used with files whose names contain a variable portion, the 030 matching of which allows '*' wildcarding. 031 </p> 032 <p> 033 Requires the jakarta commons-net jar (for FTP). 034 </p> 035 If the task's name is set and multiple matching is not enabled, five project properties are set: 036 <ul> 037 <li>Property <code><taskname></code> is set to the destination file's full path.</li> 038 <li>Property <code><taskname>.name</code> is set to the destination file's name.</li> 039 <li>Property <code><taskname>.basename</code> is set to the destination file's name minus the last '.extenstion'.</li> 040 <li>Property <code><taskname>.baseURL</code> is set to the URL of the source file's parent directory.</li> 041 <li>Property <code><taskname>.wildcard</code> is set to the portion of the URL corresponding to the first '*'.</li> 042 </ul> 043 044 <h3>Parameters</h3> 045 <table border="1" cellpadding="2" cellspacing="0"> 046 <caption>Parameter descriptions</caption> 047 <tr> 048 <td valign="top"><b>Attribute</b></td> 049 <td valign="top"><b>Description</b></td> 050 <td align="center" valign="top"><b>Required</b></td> 051 </tr> 052 <tr> 053 <td valign="top">name</td> 054 <td valign="top">The name (handle) of the property.</td> 055 <td valign="top" align="center">No</td> 056 </tr> 057 <tr> 058 <td valign="top">src</td> 059 <td valign="top">The URL (http, ftp, or a file system path) of the remote file (may contain one or more '*' wildcards).</td> 060 <td valign="top" align="center">Yes</td> 061 </tr> 062 <tr> 063 <td valign="top">dest</td> 064 <td valign="top">The local file to copy into.</td> 065 <td valign="top" align="center" rowspan='2'>Yes</td> 066 </tr> 067 <tr> 068 <td valign="top">destdir</td> 069 <td valign="top">The local directory to copy into. 070 The file's remote name is maintained.</td> 071 </tr> 072 <tr> 073 <td valign="top">matchmultiple</td> 074 <td valign="top">Used with destdir, retrieves all matching files and no properties are set. 075 If false, only the most recent matching file is retrieved and properties are set. 076 (false by default)</td> 077 <td valign="top" align="center">No</td> 078 </tr> 079 <tr> 080 <td valign="top">ignoreerrors</td> 081 <td valign="top">Do not halt ant execution on failure. (false by default)</td> 082 <td valign="top" align="center">No</td> 083 </tr> 084 <tr> 085 <td valign="top">usetimestamp</td> 086 <td valign="top">Use the file's timestamp and size to determine if freshening the 087 local copy is necessary. (true by default)</td> 088 <td valign="top" align="center">No</td> 089 </tr> 090 <tr> 091 <td valign="top">if</td> 092 <td valign="top">Only execute if a property of the given name exists in the current project.</td> 093 <td valign="top" align="center">No</td> 094 </tr> 095 <tr> 096 <td valign="top">unless</td> 097 <td valign="top">Only execute if a property of the given name doesn't exist in the current project.</td> 098 <td valign="top" align="center">No</td> 099 </tr> 100 <tr> 101 <td valign="top">useragent</td> 102 <td valign="top">User-Agent HTTP header to send, Ant will specify a User-Agent header of "Apache Ant VERSION" unless overridden by this attribute.</td> 103 <td valign="top" align="center">No</td> 104 </tr> 105 </table> 106 <p> 107 The <code>if</code> and <code>unless</code> attributes make the 108 execution conditional -both probe for the named property being defined. 109 The <code>if</code> tests for the property being defined, the 110 <code>unless</code> for a property being undefined. 111 </p> 112 If both attributes are set, then the task executes only if both tests 113 are true. i.e. 114 <pre>execute := defined(ifProperty) && !defined(unlessProperty)</pre> 115 116 The following example downloads the most recent file matching the src pattern 117 to the specified local destination:<pre> 118 <getwild name='ij' 119 src='http://rsb.info.nih.gov/ij/download/zips/ij*.zip' 120 dest='${tmp.dir}/ij.zip' /> 121 </pre> 122 @author J. Alex Taylor, hairyfatguy.com 123 */ 124//------------------------------------------------------------------------------ 125// com.hfg XML/HTML Coding Library 126// 127// This library is free software; you can redistribute it and/or 128// modify it under the terms of the GNU Lesser General Public 129// License as published by the Free Software Foundation; either 130// version 2.1 of the License, or (at your option) any later version. 131// 132// This library is distributed in the hope that it will be useful, 133// but WITHOUT ANY WARRANTY; without even the implied warranty of 134// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 135// Lesser General Public License for more details. 136// 137// You should have received a copy of the GNU Lesser General Public 138// License along with this library; if not, write to the Free Software 139// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 140// 141// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 142// jataylor@hairyfatguy.com 143//------------------------------------------------------------------------------ 144 145// Wanted to just extend org.apache.tools.ant.taskdefs.Get but I didn't have 146// access to the variables. 147// - JAT 148 149public class GetWild extends Task 150{ 151 private String mName; 152 private String mSource; // required 153 private File mDest; 154 private File mDestDir; 155 private boolean mUseTimestamp = true; //on by default 156 private boolean mIgnoreErrors = false; 157 private boolean mMatchMultiple = false; 158 private String mIfCondition; 159 private String mUnlessCondition; 160 private String mUserAgentString; 161 162 //--------------------------------------------------------------------------- 163 /** 164 * Set the task's name. 165 * 166 * @param name name for the task 167 */ 168 public void setName(String name) 169 { 170 this.mName = name; 171 } 172 173 //--------------------------------------------------------------------------- 174 /** 175 * The remote file for retrieval. 176 * @param inURL the remote file for retrieval 177 */ 178 public void setSrc(String inURL) 179 { 180 this.mSource = inURL; 181 } 182 183 //--------------------------------------------------------------------------- 184 /** 185 * Specifies where to copy the source file. 186 * @param inValue the local file to copy the remote file to 187 */ 188 public void setDest(File inValue) 189 { 190 this.mDest = inValue; 191 } 192 193 //--------------------------------------------------------------------------- 194 /** 195 * Specifies the local directory to copy the remote file into 196 * (preserving the file's remote name). 197 * @param inValue the local directory to copy the remote file into 198 */ 199 public void setDestdir(File inValue) 200 { 201 this.mDestDir = inValue; 202 } 203 204 205 //--------------------------------------------------------------------------- 206 /** 207 * Only execute if a property of the given name exists in the current project. 208 * @param inPropertyName property name 209 */ 210 public void setIf(String inPropertyName) 211 { 212 mIfCondition = inPropertyName; 213 } 214 215 //--------------------------------------------------------------------------- 216 /** 217 * Only execute if a property of the given name does not exist in the current project. 218 * @param inPropertyName property name 219 */ 220 public void setUnless(String inPropertyName) 221 { 222 mUnlessCondition = inPropertyName; 223 } 224 225 //--------------------------------------------------------------------------- 226 /** 227 * Specifies whether or not to retrieve multiple matching files. 228 * @param inValue whether or not to retrieve multiple matching files 229 */ 230 public void setMatchMultiple(boolean inValue) 231 { 232 mMatchMultiple = inValue; 233 } 234 235 //--------------------------------------------------------------------------- 236 /** 237 * Specifies whether or not to ignore retrieval errors. 238 * @param inValue whether or not to ignore retrieval errors 239 */ 240 public void setIgnoreErrors(boolean inValue) 241 { 242 mIgnoreErrors = inValue; 243 } 244 245 //--------------------------------------------------------------------------- 246 /** 247 * If set to true, the remote file's timestamp will be compared 248 * to the local file (if present) before downloading. 249 * @param inValue whether or not to compare the remote timestamp to the local file 250 */ 251 public void setUseTimestamp(boolean inValue) 252 { 253 mUseTimestamp = inValue; 254 } 255 256 //--------------------------------------------------------------------------- 257 /** 258 * User-Agent HTTP header to send, Ant will specify a User-Agent header of "Apache Ant VERSION" unless overridden by this attribute. 259 * @param inValue the value to use for the User-Agent header field in HTTP requests 260 */ 261 public void setUserAgent(String inValue) 262 { 263 mUserAgentString = inValue; 264 } 265 266 //--------------------------------------------------------------------------- 267 /** 268 * Does the work. 269 * 270 * @exception BuildException Thrown in unrecoverable error. 271 */ 272 public void execute() 273 throws BuildException 274 { 275 if (TaskUtil.testIfCondition(mIfCondition, getProject()) 276 && TaskUtil.testUnlessCondition(mUnlessCondition, getProject())) 277 { 278 if (mSource == null) 279 { 280 throw new BuildException("'src' attribute is required", getLocation()); 281 } 282 283 if (mSource.startsWith("http")) 284 { 285 String antVersionString = getProject().getProperty("ant.version"); 286 mUserAgentString = "Apache Ant" + (StringUtil.isSet(antVersionString) ? " " + antVersionString : ""); 287 } 288 289 List remoteFiles = getRemoteFiles(); 290 File destination = getDestination(); 291 292 if (mMatchMultiple) 293 { 294 if (null == mDestDir) 295 { 296 throw new BuildException("'destdir' attribute is required when matchmultiple is true", 297 getLocation()); 298 } 299 300 Iterator iter = remoteFiles.iterator(); 301 while (iter.hasNext()) 302 { 303 RemoteFile remoteFile = (RemoteFile) iter.next(); 304 retrieveRemoteFile(remoteFile, destination); 305 } 306 } 307 else 308 { 309 retrieveRemoteFile((RemoteFile) remoteFiles.get(0), destination); 310 } 311 } 312 } 313 314 //--------------------------------------------------------------------------- 315 private File getDestination() 316 { 317 File destination = null; 318 319 if (mDest == null 320 && mDestDir == null) 321 { 322 throw new BuildException("Either a 'dest' or 'destdir' attribute is required", 323 getLocation()); 324 } 325 326 if (mDest != null) 327 { 328 if (mDest.exists() && mDest.isDirectory()) 329 { 330 throw new BuildException("The specified destination is a directory", 331 getLocation()); 332 } 333 334 if (mDest.exists() && !mDest.canWrite()) 335 { 336 throw new BuildException("Can't write to " + mDest.getAbsolutePath(), 337 getLocation()); 338 } 339 340 destination = mDest; 341 } 342 else if (mDestDir != null) 343 { 344 if (mDestDir.exists() && !mDestDir.isDirectory()) 345 { 346 throw new BuildException("The specified destdir is not a directory", 347 getLocation()); 348 } 349 350 if (!mDestDir.exists()) 351 { 352 mDestDir.mkdirs(); 353 } 354 355 destination = mDestDir; 356 } 357 358 return destination; 359 } 360 361 //--------------------------------------------------------------------------- 362 private List getRemoteFiles() 363 { 364 RemoteFileLister lister = RemoteFileListerFactory.getRemoteFileLister(mSource); 365 if (lister instanceof HTTPRemoteFileLister) 366 { 367 ((HTTPRemoteFileLister) lister).setUserAgentString(mUserAgentString); 368 } 369 370 List remoteFiles = lister.getUnfilteredRemoteFileList(); 371 if (0 == remoteFiles.size()) 372 { 373 throw new BuildException( "No file found matching '" + mSource + "'"); 374 } 375 376 Collections.sort(remoteFiles, new RemoteFileTimestampComparator(DateSortDirection.NEWER_TO_OLDER)); 377 378 return remoteFiles; 379 } 380 381 382 //--------------------------------------------------------------------------- 383 private void retrieveRemoteFile(RemoteFile inRemoteFile, File inLocalDest) 384 { 385 log("Getting: " + inRemoteFile.getPath()); 386 387 Calendar timestamp = inRemoteFile.getTimestamp(); // Makes a HEAD request and will update the url for redirections 388 389 File destFile; 390 if (inLocalDest.isDirectory()) 391 { 392 destFile = new File(inLocalDest, inRemoteFile.getName()); 393 } 394 else 395 { 396 destFile = inLocalDest; 397 } 398 log("Local File: " + destFile); 399 400 401 if (mUseTimestamp 402 && timestamp != null 403 && destFile.exists() 404 && inRemoteFile.getTimestamp().getTimeInMillis() <= destFile.lastModified() 405 && inRemoteFile.getSize() == destFile.length()) 406 { 407 // Already up-to-date. 408 log("Already up-to-date"); 409 } 410 else 411 { 412 try 413 { 414 inRemoteFile.writeToLocalFile(destFile); 415 } 416 catch (IOException e) 417 { 418 log("Error getting " + mSource + " to " + destFile + ": " + e.toString()); 419 if (!mIgnoreErrors) throw new BuildException(e, getLocation()); 420 } 421 } 422 423 if (mName != null 424 && !mMatchMultiple) 425 { 426 setProjectProperties(inRemoteFile, destFile); 427 } 428 } 429 430 //--------------------------------------------------------------------------- 431 private void setProjectProperties(RemoteFile inRemoteFile, File inDestinationFile) 432 { 433 getProject().setProperty(mName, inDestinationFile.getPath()); 434 getProject().setProperty(mName + ".name", inDestinationFile.getName()); 435 436 437 int dotIndex = inDestinationFile.getName().lastIndexOf("."); 438 String basename = (dotIndex > 0 ? inDestinationFile.getName().substring(0, dotIndex) : 439 inDestinationFile.getName()); 440 441 getProject().setProperty(mName + ".basename", basename); 442 443 444 int slashIndex = inRemoteFile.getPath().lastIndexOf("/"); 445 if (slashIndex >= 0) 446 { 447 getProject().setProperty(mName + ".baseURL", inRemoteFile.getURL().substring(0, slashIndex)); 448 } 449 450 int wildcardIndex = mSource.indexOf("*"); 451 if (wildcardIndex > 0) 452 { 453 int prevSlash = mSource.lastIndexOf("/", wildcardIndex); 454 int postSlash = mSource.indexOf("/", wildcardIndex); 455 if (postSlash < 0) 456 { 457 postSlash = mSource.length(); 458 } 459 460 Matcher m = Pattern.compile(mSource.substring(prevSlash, wildcardIndex) + "(.+)" + mSource.substring(wildcardIndex + 1, postSlash)).matcher(inRemoteFile.getURL()); 461 if (m.find()) 462 { 463 getProject().setProperty(mName + ".wildcard", m.group(1)); 464 } 465 } 466 } 467}