Browse Source

Based on a patch submitted by Neeme Praks, allow support for a retry count on FTP transfers. Some

servers are unreliable for unknown - this allows for a retry count to be specified to accomodate work on such
flaky servers.


git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@278374 13f79535-47bb-0310-9956-ffa450edef68
master
Steven M. Cohen 20 years ago
parent
commit
d0cb590844
6 changed files with 293 additions and 23 deletions
  1. +8
    -0
      docs/manual/OptionalTasks/ftp.html
  2. +13
    -0
      src/etc/testcases/taskdefs/optional/net/ftp.xml
  3. +82
    -23
      src/main/org/apache/tools/ant/taskdefs/optional/net/FTP.java
  4. +72
    -0
      src/main/org/apache/tools/ant/util/RetryHandler.java
  5. +32
    -0
      src/main/org/apache/tools/ant/util/Retryable.java
  6. +86
    -0
      src/testcases/org/apache/tools/ant/taskdefs/optional/net/FTPTest.java

+ 8
- 0
docs/manual/OptionalTasks/ftp.html View File

@@ -192,6 +192,14 @@ coming from your ftp server (ls -l on the ftp prompt).
(<em>Note</em>: Ignored on Java 1.1)</td> (<em>Note</em>: Ignored on Java 1.1)</td>
<td valign="top" align="center">No; defaults to false.</td> <td valign="top" align="center">No; defaults to false.</td>
</tr> </tr>
<tr>
<td valign="top">retriesAllowed</td>
<td valign="top">Set the number of retries allowed on an file-transfer operation.
If a number > 0 specified, each file transfer can fail up to that
many times before the operation is failed. If -1 or "forever" specified, the
operation will keep trying until it succeeds.</td>
<td valign="top" align="center">No; defaults to 0</td>
</tr>


<tr> <tr>
<td colspan="3"> <td colspan="3">


+ 13
- 0
src/etc/testcases/taskdefs/optional/net/ftp.xml View File

@@ -15,6 +15,7 @@
<property name="server.timestamp.granularity.millis" value="60000"/> <property name="server.timestamp.granularity.millis" value="60000"/>
<property name="ftp.server.timezone" value="GMT"/> <property name="ftp.server.timezone" value="GMT"/>
<property name="ftp.listing.file" value="/dev/null"/> <property name="ftp.listing.file" value="/dev/null"/>
<property name="ftp.retries" value="2"/>


<fileset dir="${tmp.get.dir}" id="fileset-destination-with-selector"> <fileset dir="${tmp.get.dir}" id="fileset-destination-with-selector">
<include name="alpha/**"/> <include name="alpha/**"/>
@@ -272,5 +273,17 @@
<fileset dir="${tmp.local}"/> <fileset dir="${tmp.local}"/>
</ftp> </ftp>
</target> </target>
<target name="ftp-get-with-selector-retryable">
<ftp action="get"
server="${ftp.host}"
userid="${ftp.user}"
password="${ftp.password}"
separator="${ftp.filesep}"
remotedir="${tmp.dir}"
retriesAllowed="${ftp.retries}"
>
<fileset refid="fileset-destination-with-selector"/>
</ftp>
</target>


</project> </project>

+ 82
- 23
src/main/org/apache/tools/ant/taskdefs/optional/net/FTP.java View File

@@ -51,6 +51,8 @@ import org.apache.tools.ant.types.EnumeratedAttribute;
import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.selectors.SelectorUtils; import org.apache.tools.ant.types.selectors.SelectorUtils;
import org.apache.tools.ant.util.FileUtils; import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.RetryHandler;
import org.apache.tools.ant.util.Retryable;


/** /**
* Basic FTP client. Performs the following actions: * Basic FTP client. Performs the following actions:
@@ -126,6 +128,7 @@ public class FTP
private String shortMonthNamesConfig = null; private String shortMonthNamesConfig = null;
private Granularity timestampGranularity = Granularity.getDefault(); private Granularity timestampGranularity = Granularity.getDefault();
private boolean isConfigurationSet = false; private boolean isConfigurationSet = false;
private int retriesAllowed = 0;


protected static final String[] ACTION_STRS = { protected static final String[] ACTION_STRS = {
"sending", "sending",
@@ -1360,6 +1363,37 @@ public class FTP
} }





/**
* How many times to retry executing FTP command before giving up?
* Default is 0 - try once and if failure then give up.
*
* @param retriesAllowed number of retries to allow. -1 means
* keep trying forever. "forever" may also be specified as a
* synonym for -1.
*/
public void setRetriesAllowed(String retriesAllowed) {
if ("FOREVER".equalsIgnoreCase(retriesAllowed)) {
this.retriesAllowed = Retryable.RETRY_FOREVER;
} else {
try {
int retries = Integer.parseInt(retriesAllowed);
if (retries < Retryable.RETRY_FOREVER) {
throw new BuildException(
"Invalid value for retriesAllowed attribute: "
+ retriesAllowed);

}
this.retriesAllowed = retries;
} catch (NumberFormatException px) {
throw new BuildException(
"Invalid value for retriesAllowed attribute: "
+ retriesAllowed);
}
}
}
/** /**
* @return Returns the systemTypeKey. * @return Returns the systemTypeKey.
*/ */
@@ -1451,6 +1485,12 @@ public class FTP
} }
} }
} }
protected void executeRetryable(RetryHandler h, Retryable r, String filename)
throws IOException
{
h.execute(r, filename);
}




/** /**
@@ -1465,7 +1505,7 @@ public class FTP
* @throws IOException if there is a problem reading a file * @throws IOException if there is a problem reading a file
* @throws BuildException if there is a problem in the configuration. * @throws BuildException if there is a problem in the configuration.
*/ */
protected int transferFiles(FTPClient ftp, FileSet fs)
protected int transferFiles(final FTPClient ftp, FileSet fs)
throws IOException, BuildException { throws IOException, BuildException {
DirectoryScanner ds; DirectoryScanner ds;
if (action == SEND_FILES) { if (action == SEND_FILES) {
@@ -1512,38 +1552,51 @@ public class FTP
} }
bw = new BufferedWriter(new FileWriter(listing)); bw = new BufferedWriter(new FileWriter(listing));
} }
RetryHandler h = new RetryHandler(this.retriesAllowed, this);
if (action == RM_DIR) { if (action == RM_DIR) {
// to remove directories, start by the end of the list // to remove directories, start by the end of the list
// the trunk does not let itself be removed before the leaves // the trunk does not let itself be removed before the leaves
for (int i = dsfiles.length - 1; i >= 0; i--) { for (int i = dsfiles.length - 1; i >= 0; i--) {
rmDir(ftp, dsfiles[i]);
final String dsfile = dsfiles[i];
executeRetryable(h, new Retryable() {
public void execute() throws IOException {
rmDir(ftp, dsfile);
}
}, dsfile);
} }
} else { } else {
final BufferedWriter fbw = bw;
final String fdir = dir;
if (this.newerOnly) { if (this.newerOnly) {
this.granularityMillis = this.granularityMillis =
this.timestampGranularity.getMilliseconds(action); this.timestampGranularity.getMilliseconds(action);
} }
for (int i = 0; i < dsfiles.length; i++) { for (int i = 0; i < dsfiles.length; i++) {
switch (action) {
case SEND_FILES:
sendFile(ftp, dir, dsfiles[i]);
break;
case GET_FILES:
getFile(ftp, dir, dsfiles[i]);
break;
case DEL_FILES:
delFile(ftp, dsfiles[i]);
break;
case LIST_FILES:
listFile(ftp, bw, dsfiles[i]);
break;
case CHMOD:
doSiteCommand(ftp, "chmod " + chmod + " " + resolveFile(dsfiles[i]));
transferred++;
break;
default:
throw new BuildException("unknown ftp action " + action);
}
final String dsfile = dsfiles[i];
executeRetryable(h, new Retryable() {
public void execute() throws IOException {
switch (action) {
case SEND_FILES:
sendFile(ftp, fdir, dsfile);
break;
case GET_FILES:
getFile(ftp, fdir, dsfile);
break;
case DEL_FILES:
delFile(ftp, dsfile);
break;
case LIST_FILES:
listFile(ftp, fbw, dsfile);
break;
case CHMOD:
doSiteCommand(ftp, "chmod " + chmod + " " + resolveFile(dsfile));
transferred++;
break;
default:
throw new BuildException("unknown ftp action " + action);
}
}
}, dsfile);
} }
} }
} finally { } finally {
@@ -2198,7 +2251,13 @@ public class FTP
// directory is the directory to create. // directory is the directory to create.


if (action == MK_DIR) { if (action == MK_DIR) {
makeRemoteDir(ftp, remotedir);
RetryHandler h = new RetryHandler(this.retriesAllowed, this);
final FTPClient lftp = ftp;
executeRetryable(h, new Retryable() {
public void execute() throws IOException {
makeRemoteDir(lftp, remotedir);
}
}, remotedir);
} else { } else {
if (remotedir != null) { if (remotedir != null) {
log("changing the remote directory", Project.MSG_VERBOSE); log("changing the remote directory", Project.MSG_VERBOSE);


+ 72
- 0
src/main/org/apache/tools/ant/util/RetryHandler.java View File

@@ -0,0 +1,72 @@
/*
* Copyright 2005 The Apache Software Foundation
*
* Licensed 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.util;

import java.io.IOException;

import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;

/**
* A simple utility class to take a piece of code (that implements
* <code>Retryable</code> interface) and executes that with possibility to
* retry the execution in case of IOException.
*/
public class RetryHandler {

private int retriesAllowed = 0;
private Task task;

/**
* Create a new RetryingHandler.
*
* @param retriesAllowed how many times to retry
* @param task the Ant task that is is executed from, used for logging only
*/
public RetryHandler(int retriesAllowed, Task task) {
this.retriesAllowed = retriesAllowed;
this.task = task;
}

/**
* Execute the <code>Retryable</code> code with specified number of retries.
*
* @param exe the code to execute
* @param desc some descriptive text for this piece of code, used for logging
* @throws IOException if the number of retries has exceeded the allowed limit
*/
public void execute(Retryable exe, String desc) throws IOException {
int retries = 0;
while (true) {
try {
exe.execute();
break;
} catch (IOException e) {
retries++;
if (retries > this.retriesAllowed && this.retriesAllowed > -1) {
task.log("try #" + retries + ": IO error ("
+ desc + "), number of maximum retries reached ("
+ this.retriesAllowed + "), giving up", Project.MSG_WARN);
throw e;
} else {
task.log("try #" + retries + ": IO error (" + desc + "), retrying", Project.MSG_WARN);
}
}
}
}

}

+ 32
- 0
src/main/org/apache/tools/ant/util/Retryable.java View File

@@ -0,0 +1,32 @@
/*
* Copyright 2005 The Apache Software Foundation
*
* Licensed 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.util;

import java.io.IOException;


/**
* Simple interface for executing a piece of code. Used for writing anonymous inner
* classes in FTP task for retry-on-IOException behaviour.
*
* @see RetryHandler
*/
public interface Retryable {
public static final int RETRY_FOREVER = -1;
void execute() throws IOException;

}

+ 86
- 0
src/testcases/org/apache/tools/ant/taskdefs/optional/net/FTPTest.java View File

@@ -21,17 +21,21 @@ import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Random;
import java.util.Vector; import java.util.Vector;


import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPClient;
import org.apache.tools.ant.BuildEvent; import org.apache.tools.ant.BuildEvent;
import org.apache.tools.ant.BuildException; import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.BuildFileTest; import org.apache.tools.ant.BuildFileTest;
import org.apache.tools.ant.ComponentHelper;
import org.apache.tools.ant.DefaultLogger; import org.apache.tools.ant.DefaultLogger;
import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project; import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.condition.Os; import org.apache.tools.ant.taskdefs.condition.Os;
import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.util.RetryHandler;
import org.apache.tools.ant.util.Retryable;
import org.apache.tools.ant.util.regexp.RegexpMatcher; import org.apache.tools.ant.util.regexp.RegexpMatcher;
import org.apache.tools.ant.util.regexp.RegexpMatcherFactory; import org.apache.tools.ant.util.regexp.RegexpMatcherFactory;


@@ -779,6 +783,88 @@ public class FTPTest extends BuildFileTest{
public String resolveFile(String file) { public String resolveFile(String file) {
return super.resolveFile(file); return super.resolveFile(file);
} }
}


public abstract static class myRetryableFTP extends FTP {
private final int numberOfFailuresToSimulate;
private int simulatedFailuresLeft;
protected myRetryableFTP(int numberOfFailuresToSimulate) {
this.numberOfFailuresToSimulate = numberOfFailuresToSimulate;
this.simulatedFailuresLeft = numberOfFailuresToSimulate;
}


protected void getFile(FTPClient ftp, String dir, String filename)
throws IOException, BuildException
{
if (this.simulatedFailuresLeft > 0) {
this.simulatedFailuresLeft--;
throw new IOException("Simulated failure for testing");
}
super.getFile(ftp, dir, filename);
}
protected void executeRetryable(RetryHandler h, Retryable r,
String filename) throws IOException
{
this.simulatedFailuresLeft = this.numberOfFailuresToSimulate;
super.executeRetryable(h, r, filename);
}
} }
public static class oneFailureFTP extends myRetryableFTP {
public oneFailureFTP() {
super(1);
}
}
public static class twoFailureFTP extends myRetryableFTP {
public twoFailureFTP() {
super(2);
}
}
public static class threeFailureFTP extends myRetryableFTP {
public threeFailureFTP() {
super(3);
}
}
public static class randomFailureFTP extends myRetryableFTP {
public randomFailureFTP() {
super(new Random(30000).nextInt());
}
}
public void testGetWithSelectorRetryable1() {
getProject().addTaskDefinition("ftp", oneFailureFTP.class);
try {
getProject().executeTarget("ftp-get-with-selector-retryable");
} catch (BuildException bx) {
fail("Two retries expected, failed after one.");
}
}
public void testGetWithSelectorRetryable2() {
getProject().addTaskDefinition("ftp", twoFailureFTP.class);
try {
getProject().executeTarget("ftp-get-with-selector-retryable");
} catch (BuildException bx) {
fail("Two retries expected, failed after two.");
}
}
public void testGetWithSelectorRetryable3() {
getProject().addTaskDefinition("ftp", threeFailureFTP.class);
try {
getProject().executeTarget("ftp-get-with-selector-retryable");
fail("Two retries expected, continued after two.");
} catch (BuildException bx) {
}
}
public void testGetWithSelectorRetryableRandom() {
getProject().addTaskDefinition("ftp", threeFailureFTP.class);
try {
getProject().setProperty("ftp.retries", "forever");
getProject().executeTarget("ftp-get-with-selector-retryable");
} catch (BuildException bx) {
fail("Retry forever specified, but failed.");
}
}

} }

Loading…
Cancel
Save