Broken: repository references (you'll see in the test results) not tested: authenticated access to the repository. Already had a feature request for multiple repository support; would be nice. That and better diagnostics on failure. Note that we dont currently probe ibiblio for availaibility; that was taking longer than the fetches themselves. git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@276957 13f79535-47bb-0310-9956-ffa450edef68master
@@ -68,6 +68,8 @@ Other changes: | |||
* Added a new "failall" value for the onerror attribute of <typedef>. | |||
Bugzilla report 31685. | |||
* New task <getlibraries> can retrieve library files from a maven repository. | |||
Changes from Ant 1.6.2 to current Ant 1.6 CVS version | |||
===================================================== | |||
@@ -328,6 +328,11 @@ | |||
<filename name="${ant.package}/launch/**/*"/> | |||
</selector> | |||
<selector id="needs.commons.httpclient"> | |||
<filename name="${optional.package}/repository/**/*"/> | |||
</selector> | |||
<patternset id="onlinetests"> | |||
<exclude name="**/GetTest.java" if="offline"/> | |||
<exclude name="**/SignJarTest.java" if="offline"/> | |||
@@ -561,6 +566,17 @@ | |||
classpathref="classpath"/> | |||
</or> | |||
</condition> | |||
<!-- http client needs commons logging --> | |||
<condition property="commons.httpclient.present"> | |||
<and> | |||
<available | |||
classname="org.apache.commons.httpclient.HttpClient" | |||
classpathref="classpath"/> | |||
<isset property="commons.logging.present"/> | |||
</and> | |||
</condition> | |||
<condition property="wsdl.found"> | |||
<or> | |||
<available file="wsdl" filepath="${env.PATH}"/> | |||
@@ -679,6 +695,7 @@ | |||
<selector refid="needs.jdepend" unless="jdepend.present"/> | |||
<selector refid="needs.swing" unless="swing.present"/> | |||
<selector refid="needs.jsch" unless="jsch.present"/> | |||
<selector refid="needs.commons.httpclient" unless="commons.httpclient.present"/> | |||
</or> | |||
</not> | |||
</selector> | |||
@@ -853,6 +870,7 @@ | |||
<selector refid="needs.jdepend"/> | |||
<selector refid="needs.swing"/> | |||
<selector refid="needs.jsch"/> | |||
<selector refid="needs.commons.httpclient"/> | |||
</or> | |||
</not> | |||
</and> | |||
@@ -897,6 +915,7 @@ | |||
<optional-jar dep="swing"/> | |||
<optional-jar dep="jsch"/> | |||
<optional-jar dep="jdepend"/> | |||
<optional-jar dep="commons.httpclient"/> | |||
<jar destfile="${build.lib}/${optional.jars.prefix}-weblogic.jar" | |||
basedir="${build.classes}" | |||
@@ -1471,6 +1490,7 @@ | |||
<sysproperty key="ant.home" value="${ant.home}"/> | |||
<sysproperty key="build.tests" file="${build.tests}"/> | |||
<sysproperty key="offline" value="${offline}"/> | |||
<sysproperty key="build.tests.value" value="${build.tests.value}"/> | |||
<sysproperty key="tests-classpath.value" | |||
value="${tests-classpath.value}"/> | |||
@@ -1624,6 +1644,7 @@ | |||
<sysproperty key="ant.home" value="${ant.home}"/> | |||
<sysproperty key="build.tests" file="${build.tests}"/> | |||
<sysproperty key="build.tests.value" value="${build.tests.value}"/> | |||
<sysproperty key="offline" value="${offline}"/> | |||
<sysproperty key="tests-classpath.value" | |||
value="${tests-classpath.value}"/> | |||
<classpath refid="tests-classpath"/> | |||
@@ -0,0 +1,112 @@ | |||
<?xml version="1.0"?> | |||
<project name="getlibraries" basedir="." default="all"> | |||
<!-- use the normal one at ibiblio--> | |||
<mavenrepository id="maven"/> | |||
<target name="init"> | |||
<property name="lib.dir" value="getlib"/> | |||
<mkdir dir="${lib.dir}"/> | |||
<property name="commons.logging" value="commons-logging-1.0.1.jar"/> | |||
<presetdef name="getlib"> | |||
<getlibraries destDir="${lib.dir}"> | |||
<library archive="commons-logging" project="commons-logging" version="1.0.1"/> | |||
</getlibraries> | |||
</presetdef> | |||
<macrodef name="assert-downloaded"> | |||
<attribute name="library" default="${commons.logging}"/> | |||
<sequential> | |||
<available property="@{library}.exists" | |||
file="${lib.dir}/@{library}"/> | |||
<fail unless="@{library}.exists"> | |||
Not found: ${lib.dir}@{library} | |||
</fail> | |||
</sequential> | |||
</macrodef> | |||
</target> | |||
<target name="cleanup"> | |||
<delete dir="${lib.dir}"/> | |||
</target> | |||
<target name="testEmpty" depends="init"> | |||
<getlibraries/> | |||
</target> | |||
<target name="testEmpty2" depends="init"> | |||
<getlibraries destDir="${lib.dir}"> | |||
</getlibraries> | |||
</target> | |||
<target name="testEmpty3" depends="init"> | |||
<getlibraries destDir="${lib.dir}"> | |||
<repository/> | |||
</getlibraries> | |||
</target> | |||
<target name="testNoRepo" depends="init"> | |||
<getlib/> | |||
</target> | |||
<target name="testUnknownReference" depends="init"> | |||
<getlib> | |||
<repository refid="unknown"/> | |||
</getlib> | |||
</target> | |||
<target name="testFunctionalInline" depends="init"> | |||
<getlib repositoryref="maven"> | |||
</getlib> | |||
<assert-downloaded/> | |||
</target> | |||
<target name="testMavenInline" depends="init"> | |||
<getlib> | |||
<mavenrepository/> | |||
</getlib> | |||
<assert-downloaded/> | |||
</target> | |||
<target name="testTwoRepositories" depends="init"> | |||
<getlib> | |||
<mavenrepository/> | |||
<mavenrepository/> | |||
</getlib> | |||
</target> | |||
<target name="testMavenInlineBadURL" depends="init"> | |||
<getlib> | |||
</getlib> | |||
</target> | |||
<target name="testRenaming" depends="init"> | |||
<getlib> | |||
<mavenrepository/> | |||
<library archive="commons-logging" project="commons-logging" version="1.0.1" | |||
destinationName="renamed.jar" | |||
/> | |||
</getlib> | |||
<assert-downloaded/> | |||
<assert-downloaded library="renamed.jar"/> | |||
</target> | |||
<target name="testOverwrite" depends="init"> | |||
<getlib> | |||
<mavenrepository/> | |||
</getlib> | |||
<assert-downloaded/> | |||
<getlib> | |||
<mavenrepository/> | |||
</getlib> | |||
</target> | |||
</project> | |||
@@ -203,6 +203,7 @@ rexec=org.apache.tools.ant.taskdefs.optional.net.RExecTask | |||
scriptdef=org.apache.tools.ant.taskdefs.optional.script.ScriptDef | |||
ildasm=org.apache.tools.ant.taskdefs.optional.dotnet.Ildasm | |||
apt=org.apache.tools.ant.taskdefs.Apt | |||
getlibraries=org.apache.tools.ant.taskdefs.optional.repository.GetLibraries | |||
# deprecated ant tasks (kept for back compatibility) | |||
starteam=org.apache.tools.ant.taskdefs.optional.scm.AntStarTeamCheckOut | |||
@@ -0,0 +1,315 @@ | |||
/* | |||
* Copyright 2004 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.taskdefs.optional.repository; | |||
import org.apache.tools.ant.BuildException; | |||
import org.apache.tools.ant.Project; | |||
import org.apache.tools.ant.Task; | |||
import org.apache.tools.ant.types.Reference; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.util.Iterator; | |||
import java.util.LinkedList; | |||
import java.util.List; | |||
/** | |||
* This task will retrieve one or more libraries from a repository. <ol> | |||
* <li>Users must declare a repository, either inline or by reference</li> | |||
* <li>Dependency checking is used (timestamps) unless forceDownload=true</li> | |||
* <li>It is an error if, at the end of the task, a library is missing. | |||
* | |||
* @ant.task | |||
* @since Ant 1.7 | |||
*/ | |||
public class GetLibraries extends Task { | |||
/** | |||
* destination | |||
*/ | |||
private File destDir; | |||
/** | |||
* flag to force a download | |||
*/ | |||
private boolean forceDownload = false; | |||
/** | |||
* flag to force offline | |||
*/ | |||
private boolean offline = false; | |||
/** | |||
* list of libraries | |||
*/ | |||
private List libraries = new LinkedList(); | |||
/** | |||
* repository for retrieval | |||
*/ | |||
private Repository repository; | |||
public static final String ERROR_ONE_REPOSITORY_ONLY = "Only one repository is allowed"; | |||
public static final String ERROR_NO_DEST_DIR = "No destination directory"; | |||
public static final String ERROR_NO_REPOSITORY = "No repository defined"; | |||
public static final String ERROR_NO_LIBRARIES = "No libraries to load"; | |||
public static final String ERROR_REPO_PROBE_FAILED = "repository probe failed with "; | |||
public static final String ERROR_LIBRARY_FETCH_FAILED = "failed to retrieve "; | |||
public static final String ERROR_FORCED_DOWNLOAD_FAILED = "Failed to download every file on a forced download"; | |||
public static final String ERROR_INCOMPLETE_RETRIEVAL = "Not all libraries could be retrieved"; | |||
/** | |||
* add a repository. Only one is (currently) supported | |||
* | |||
* @param repo | |||
*/ | |||
public void add(Repository repo) { | |||
if (repository != null) { | |||
throw new BuildException(ERROR_ONE_REPOSITORY_ONLY); | |||
} | |||
repository = repo; | |||
} | |||
/** | |||
* add a repository. Unless there is explicit support for a subclass | |||
* | |||
* @param repo | |||
*/ | |||
public void addRepository(RepositoryRef repo) { | |||
add(repo); | |||
} | |||
/** | |||
* add a maven repository. | |||
*/ | |||
/* | |||
public void addMavenRepository(MavenRepository repo) { | |||
add(repo); | |||
} | |||
*/ | |||
/** | |||
* bind to a repository. | |||
*/ | |||
public void setRepositoryRef(final Reference ref) { | |||
//create a special repository that can only | |||
//resolve references. | |||
Repository r = new RepositoryRef(getProject(), ref); | |||
add(r); | |||
} | |||
/** | |||
* add a library for retrieval | |||
* | |||
* @param lib | |||
*/ | |||
public void addLibrary(Library lib) { | |||
libraries.add(lib); | |||
} | |||
/** | |||
* destination directory for all library files | |||
* | |||
* @param destDir | |||
*/ | |||
public void setDestDir(File destDir) { | |||
this.destDir = destDir; | |||
} | |||
/** | |||
* flag to force a download even if the clock indicates it aint needed. | |||
* | |||
* @param forceDownload | |||
*/ | |||
public void setForceDownload(boolean forceDownload) { | |||
this.forceDownload = forceDownload; | |||
} | |||
/** | |||
* test for being offline | |||
* | |||
* @return true if the offline flag is set | |||
*/ | |||
public boolean isOffline() { | |||
return offline; | |||
} | |||
/** | |||
* declare the system offline. This disables any attempt to retrieve files. | |||
* In this mode, only the presence of files is verified. If forceDownload is | |||
* true, or there is a missing library, then an error will be raised. | |||
* | |||
* @param offline | |||
*/ | |||
public void setOffline(boolean offline) { | |||
this.offline = offline; | |||
} | |||
public File getDestDir() { | |||
return destDir; | |||
} | |||
public boolean isForceDownload() { | |||
return forceDownload; | |||
} | |||
public List getLibraries() { | |||
return libraries; | |||
} | |||
public Repository getRepository() { | |||
return repository; | |||
} | |||
/** | |||
* validate ourselves | |||
* | |||
* @throws BuildException | |||
*/ | |||
public void validate() { | |||
if (destDir == null || !destDir.exists() || !destDir.isDirectory()) { | |||
throw new BuildException(ERROR_NO_DEST_DIR); | |||
} | |||
if (repository == null) { | |||
throw new BuildException(ERROR_NO_REPOSITORY); | |||
} | |||
Iterator it = libraries.iterator(); | |||
while (it.hasNext()) { | |||
Library library = (Library) it.next(); | |||
library.validate(); | |||
} | |||
} | |||
/** | |||
* Called by the project to let the task do its work. | |||
* | |||
* @throws org.apache.tools.ant.BuildException | |||
* if something goes wrong with the build | |||
*/ | |||
public void execute() throws BuildException { | |||
validate(); | |||
Repository repo = repository.resolve(); | |||
repo.validate(); | |||
int toFetch = libraries.size(); | |||
if (toFetch == 0) { | |||
throw new BuildException(ERROR_NO_LIBRARIES); | |||
} | |||
int fetched = 0; | |||
log("Getting libraries from " + repo.toString(), Project.MSG_VERBOSE); | |||
log("Saving libraries to " + destDir.toString(), Project.MSG_VERBOSE); | |||
bindAllLibraries(); | |||
if (isOffline()) { | |||
log("No retrieval, task is \"offline\""); | |||
verifyAllLibrariesPresent(); | |||
return; | |||
} | |||
//connect the repository | |||
repo.connect(this); | |||
try { | |||
//check for reachability. | |||
//it is up to each repository to decide that. | |||
boolean reachable; | |||
try { | |||
log("Checking repository for reachability", Project.MSG_DEBUG); | |||
reachable = repo.checkRepositoryReachable(); | |||
} catch (IOException e) { | |||
log(ERROR_REPO_PROBE_FAILED + e.getMessage(), | |||
Project.MSG_VERBOSE); | |||
reachable = false; | |||
} | |||
if (!reachable) { | |||
if (forceDownload) { | |||
throw new BuildException(repo.toString() | |||
+ " is unreachable and forceDownload is set"); | |||
} | |||
} else { | |||
log("Repository is live", Project.MSG_DEBUG); | |||
} | |||
//iterate through the libs we have | |||
Iterator it = libraries.iterator(); | |||
while (it.hasNext()) { | |||
Library library = (Library) it.next(); | |||
library.bind(destDir); | |||
try { | |||
if (repo.fetch(library)) { | |||
fetched++; | |||
} | |||
} catch (IOException e) { | |||
//failures to fetch are logged at verbose level | |||
log(ERROR_LIBRARY_FETCH_FAILED + library); | |||
log(e.getMessage(), Project.MSG_VERBOSE); | |||
} | |||
} | |||
} finally { | |||
repo.disconnect(); | |||
} | |||
//at this point downloads have finished. | |||
//we do still need to verify that everything worked. | |||
if ((fetched < toFetch && forceDownload)) { | |||
throw new BuildException(ERROR_FORCED_DOWNLOAD_FAILED); | |||
} | |||
verifyAllLibrariesPresent(); | |||
} | |||
/** | |||
* bind all libraries to our destination | |||
*/ | |||
protected void bindAllLibraries() { | |||
Iterator it = libraries.iterator(); | |||
while (it.hasNext()) { | |||
Library library = (Library) it.next(); | |||
library.bind(destDir); | |||
} | |||
} | |||
/** | |||
* verify that all libraries are present | |||
*/ | |||
protected void verifyAllLibrariesPresent() { | |||
//iterate through the libs we have | |||
boolean missing = false; | |||
Iterator it = libraries.iterator(); | |||
while (it.hasNext()) { | |||
Library library = (Library) it.next(); | |||
//check for the library existing | |||
if (!library.exists()) { | |||
//and log if one is missing | |||
log("Missing: " + library.toString(), | |||
Project.MSG_ERR); | |||
missing = true; | |||
} | |||
} | |||
if (missing) { | |||
throw new BuildException(ERROR_INCOMPLETE_RETRIEVAL); | |||
} | |||
} | |||
} |
@@ -0,0 +1,447 @@ | |||
/* | |||
* Copyright 2004 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.taskdefs.optional.repository; | |||
import org.apache.commons.httpclient.*; | |||
import org.apache.commons.httpclient.cookie.CookiePolicy; | |||
import org.apache.commons.httpclient.methods.GetMethod; | |||
import org.apache.tools.ant.BuildException; | |||
import org.apache.tools.ant.Project; | |||
import org.apache.tools.ant.util.FileUtils; | |||
import java.io.File; | |||
import java.io.FileOutputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.net.MalformedURLException; | |||
import java.net.URL; | |||
import java.util.Date; | |||
/** | |||
* This is a base class for repositories that are built on URLs. Although you | |||
* can share this datatype, it is *not* thread safe; you can only use it in one | |||
* thread at at time | |||
* | |||
* @since Ant1.7 | |||
*/ | |||
public abstract class HttpRepository extends Repository { | |||
/** | |||
* repositoryURL of repository | |||
*/ | |||
private String url; | |||
/** | |||
* username | |||
*/ | |||
private String username; | |||
/** | |||
* password | |||
*/ | |||
private String password; | |||
/** | |||
* auth realm; can be null | |||
*/ | |||
private String realm; | |||
/** | |||
* this is our http client | |||
*/ | |||
private HttpClient client; | |||
/** | |||
* number of times to retry fetches | |||
*/ | |||
private int retries = 1; | |||
/** | |||
* no repository URL | |||
*/ | |||
public static final String ERROR_NO_REPOSITORY_URL = "No repository URL"; | |||
/** | |||
* owner class | |||
*/ | |||
private GetLibraries owner; | |||
/** | |||
* retry logic | |||
*/ | |||
private DefaultMethodRetryHandler retryhandler; | |||
public static final String ERROR_REENTRANT_USE = "Repository is already in use"; | |||
private static final String IF_MODIFIED_SINCE = "If-Modified-Since"; | |||
private static final int BLOCKSIZE = 8192; | |||
/** | |||
* get the base URL of the repository | |||
* | |||
* @return | |||
*/ | |||
public String getUrl() { | |||
return url; | |||
} | |||
/** | |||
* Set the base URL of the repository | |||
* | |||
* @param url | |||
*/ | |||
public void setUrl(String url) { | |||
this.url = url; | |||
} | |||
public String getUsername() { | |||
return username; | |||
} | |||
/** | |||
* set the username for the remote repository | |||
* | |||
* @param username | |||
*/ | |||
public void setUsername(String username) { | |||
this.username = username; | |||
} | |||
public String getPassword() { | |||
return password; | |||
} | |||
/** | |||
* set the password for the remote repository | |||
* | |||
* @param password | |||
*/ | |||
public void setPassword(String password) { | |||
this.password = password; | |||
} | |||
public String getRealm() { | |||
return realm; | |||
} | |||
/** | |||
* set the realm for authentication; empty string is equivalent to "any | |||
* realm" (the default) | |||
* | |||
* @param realm | |||
*/ | |||
public void setRealm(String realm) { | |||
if (realm != null) { | |||
this.realm = realm; | |||
} else { | |||
this.realm = null; | |||
} | |||
} | |||
/** | |||
* @return number of times to retry fetches | |||
*/ | |||
public int getRetries() { | |||
return retries; | |||
} | |||
/** | |||
* number of times to retry fetches | |||
* | |||
* @param retries | |||
*/ | |||
public void setRetries(int retries) { | |||
this.retries = retries; | |||
} | |||
/** | |||
* get the client | |||
* | |||
* @return | |||
*/ | |||
public HttpClient getClient() { | |||
return client; | |||
} | |||
public GetLibraries getOwner() { | |||
return owner; | |||
} | |||
/** | |||
* validate yourself | |||
* | |||
* @throws org.apache.tools.ant.BuildException | |||
* if unhappy | |||
*/ | |||
public void validate() { | |||
super.validate(); | |||
checkChildrenAllowed(); | |||
checkAttributesAllowed(); | |||
if (url == null || url.length() == 0) { | |||
throw new BuildException(ERROR_NO_REPOSITORY_URL); | |||
} | |||
} | |||
/** | |||
* override point: connection is called at the start of the retrieval | |||
* process | |||
* | |||
* @param newOwner | |||
* | |||
* @throws org.apache.tools.ant.BuildException | |||
* | |||
*/ | |||
public void connect(GetLibraries newOwner) { | |||
this.owner = newOwner; | |||
if (client != null) { | |||
throw new BuildException(ERROR_REENTRANT_USE); | |||
} | |||
if (!url.endsWith("/")) { | |||
url = url + '/'; | |||
} | |||
client = new HttpClient(); | |||
//retry handler | |||
retryhandler = new DefaultMethodRetryHandler(); | |||
retryhandler.setRequestSentRetryEnabled(false); | |||
retryhandler.setRetryCount(retries); | |||
//validate the URL | |||
URL repository; | |||
try { | |||
repository = new URL(url); | |||
} catch (MalformedURLException e) { | |||
throw new BuildException(e); | |||
} | |||
//authentication | |||
if (username != null) { | |||
Credentials defaultcreds = | |||
new UsernamePasswordCredentials(username, password); | |||
client.getState().setCredentials(realm, | |||
repository.getHost(), | |||
defaultcreds); | |||
//turn auth on on first call | |||
client.getState().setAuthenticationPreemptive(true); | |||
} | |||
//cookies | |||
client.getState().setCookiePolicy(CookiePolicy.COMPATIBILITY); | |||
} | |||
/** | |||
* override point: connection is called at the start of the retrieval | |||
* process | |||
* | |||
* @throws org.apache.tools.ant.BuildException | |||
* | |||
*/ | |||
public void disconnect() { | |||
client = null; | |||
retryhandler = null; | |||
} | |||
/** | |||
* Test for a repository being reachable. This method is called after {@link | |||
* #connect(org.apache.tools.ant.taskdefs.optional.repository.GetLibraries)} | |||
* is called, before any files are to be retrieved. | |||
* <p/> | |||
* If it returns false the repository considers itself offline. Similarly, | |||
* any ioexception is interpreted as being offline. | |||
* <p/> | |||
* The Http implementation probes for the base URL being reachable, and | |||
* returning a 200 status code. | |||
* | |||
* @return true if the repository is online. | |||
* | |||
* @throws java.io.IOException | |||
*/ | |||
public boolean checkRepositoryReachable() throws IOException { | |||
//return pingBaseURL(); | |||
return true; | |||
} | |||
private boolean pingBaseURL() throws IOException { | |||
GetMethod get = createGet(getUrl()); | |||
client.executeMethod(get); | |||
return get.getStatusCode() == HttpStatus.SC_OK; | |||
} | |||
/** | |||
* create a new getMethod against any URI | |||
* | |||
* @param url | |||
* | |||
* @return | |||
*/ | |||
public GetMethod createGet(String url) { | |||
GetMethod method = new GetMethod(url); | |||
method.setMethodRetryHandler(retryhandler); | |||
method.setDoAuthentication(true); | |||
method.setFollowRedirects(true); | |||
return method; | |||
} | |||
/** | |||
* @param method | |||
* @param timestamp | |||
* | |||
* @link http://www.w3.org/Protocols/HTTP/HTRQ_Headers.html#if-modified-since | |||
* @link http://www.w3.org/Protocols/rfc850/rfc850.html#z10 | |||
*/ | |||
public void setIfModifiedSinceHeader(HttpMethod method, long timestamp) { | |||
Date date = new Date(timestamp); | |||
//ooh, naughty, deprecated. and like why is it deprecated? | |||
method.setRequestHeader(IF_MODIFIED_SINCE, date.toGMTString()); | |||
} | |||
/** | |||
* fetch a library from the repository | |||
* | |||
* @param library | |||
* | |||
* @return true if we retrieved | |||
* | |||
* @throws org.apache.tools.ant.BuildException | |||
* | |||
*/ | |||
public boolean fetch(Library library) throws IOException { | |||
String path = getRemoteLibraryURL(library); | |||
logVerbose("Library URL=" + path); | |||
logVerbose("destination =" + library.getAbsolutePath()); | |||
GetMethod get = createGet(path); | |||
boolean useTimestamps = !getOwner().isForceDownload() && | |||
!library.exists(); | |||
if (useTimestamps) { | |||
setIfModifiedSinceHeader(get, library.getLastModified()); | |||
} | |||
try { | |||
long start, finish; | |||
start = System.currentTimeMillis(); | |||
client.executeMethod(get); | |||
if (useTimestamps && get.getStatusCode() == HttpStatus.SC_NOT_MODIFIED) { | |||
logDebug("File is not modified"); | |||
//we get here if there is no change in timestamp | |||
//so no fetch | |||
return false; | |||
} | |||
if (get.getStatusCode() != HttpStatus.SC_OK) { | |||
String message = "Request Failed:" | |||
+ get.getStatusCode() | |||
+ " from " + get.getPath(); | |||
logVerbose(message); | |||
logVerbose(get.getStatusLine().toString()); | |||
logVerbose(get.getStatusText()); | |||
throw new BuildException(message); | |||
} | |||
saveStreamToLibrary(get, library); | |||
finish = System.currentTimeMillis(); | |||
long diff = finish - start; | |||
logVerbose("downloaded in " + diff / 1000 + " seconds"); | |||
} finally { | |||
// Release the connection. | |||
get.releaseConnection(); | |||
} | |||
return true; | |||
} | |||
/** | |||
* log something at the verbose level | |||
* | |||
* @param message text to log | |||
*/ | |||
protected void logVerbose(String message) { | |||
getOwner().log(message, | |||
Project.MSG_VERBOSE); | |||
} | |||
protected void logDebug(String message) { | |||
getOwner().log(message, | |||
Project.MSG_DEBUG); | |||
} | |||
/** | |||
* Get the path to a remote library. This is the full URL | |||
* | |||
* @param library | |||
* | |||
* @return URL to library | |||
*/ | |||
protected abstract String getRemoteLibraryURL(Library library); | |||
/** | |||
* save a stream from a connection to a library. prerequisite: connection | |||
* open and response=200. | |||
* | |||
* @param get | |||
* @param library | |||
* | |||
* @throws java.io.IOException on any trouble. | |||
*/ | |||
protected void saveStreamToLibrary(GetMethod get, Library library) | |||
throws IOException { | |||
//we only get here if we are happy | |||
//so save it to a temp file | |||
File tempDest = File.createTempFile("download", ".bin", getOwner() | |||
.getDestDir()); | |||
logDebug("Saving file to " + tempDest); | |||
FileOutputStream fos = new FileOutputStream(tempDest); | |||
InputStream is = get.getResponseBodyAsStream(); | |||
boolean finished = false; | |||
try { | |||
byte[] buffer = new byte[BLOCKSIZE]; | |||
int length; | |||
while ((length = is.read(buffer)) >= 0) { | |||
fos.write(buffer, 0, length); | |||
} | |||
finished = true; | |||
} finally { | |||
FileUtils.close(fos); | |||
FileUtils.close(is); | |||
// we have started to (over)write dest, but failed. | |||
// Try to delete the garbage we'd otherwise leave | |||
// behind. | |||
if (!finished) { | |||
logVerbose("Deleting temporary file after failed download"); | |||
tempDest.delete(); | |||
} | |||
} | |||
logDebug("download complete; renaming destination file"); | |||
//then copy over the file | |||
File libraryFile = library.getLibraryFile(); | |||
if (libraryFile.exists()) { | |||
libraryFile.delete(); | |||
} | |||
// set the dest file | |||
if (!tempDest.renameTo(libraryFile)) { | |||
tempDest.delete(); | |||
throw new IOException( | |||
"Could not rename temp file to destination file"); | |||
} | |||
} | |||
/** | |||
* Returns a string representation of the repository | |||
* | |||
* @return the base URL | |||
*/ | |||
public String toString() { | |||
return "Repository at " + getUrl(); | |||
} | |||
} |
@@ -0,0 +1,218 @@ | |||
/* | |||
* Copyright 2004 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.taskdefs.optional.repository; | |||
import org.apache.tools.ant.BuildException; | |||
import java.io.File; | |||
/** | |||
* How we represent libraries | |||
* | |||
* @since 20-Oct-2004 | |||
*/ | |||
public class Library { | |||
//project "ant" | |||
private String project; | |||
//version "1.5" | |||
private String version; | |||
//archive prefix "ant-optional" | |||
private String archive; | |||
/** | |||
* very optional attribute; name of the destination. Autocalculated if not | |||
* set. | |||
*/ | |||
private String destinationName; | |||
private File libraryFile; | |||
public static final String ERROR_NO_ARCHIVE = "No archive defined"; | |||
public static final String ERROR_NO_PROJECT = "No project defined"; | |||
public static final String ERROR_NO_VERSION = "No version defined"; | |||
public static final String ERROR_NO_SUFFIX = "No version defined"; | |||
/** | |||
* suffix | |||
*/ | |||
private String suffix = "jar"; | |||
public String getProject() { | |||
return project; | |||
} | |||
public void setProject(String project) { | |||
this.project = project; | |||
} | |||
public String getVersion() { | |||
return version; | |||
} | |||
public void setVersion(String version) { | |||
this.version = version; | |||
} | |||
public String getArchive() { | |||
return archive; | |||
} | |||
public void setArchive(String archive) { | |||
this.archive = archive; | |||
} | |||
public String getDestinationName() { | |||
return destinationName; | |||
} | |||
public void setDestinationName(String destinationName) { | |||
this.destinationName = destinationName; | |||
} | |||
/** | |||
* get the suffix for this file. | |||
* | |||
* @return | |||
*/ | |||
public String getSuffix() { | |||
return suffix; | |||
} | |||
public void setSuffix(String suffix) { | |||
this.suffix = suffix; | |||
} | |||
public File getLibraryFile() { | |||
return libraryFile; | |||
} | |||
/** | |||
* fault if the field is null or empty | |||
* | |||
* @param field | |||
* @param message text for fault | |||
* | |||
* @throws BuildException if the field is not set up | |||
*/ | |||
private void faultIfEmpty(String field, String message) { | |||
if (field == null || field.length() == 0) { | |||
throw new BuildException(message); | |||
} | |||
} | |||
/** | |||
* validate; | |||
* | |||
* @throws BuildException if invalid | |||
*/ | |||
public void validate() { | |||
faultIfEmpty(archive, ERROR_NO_ARCHIVE); | |||
faultIfEmpty(project, ERROR_NO_PROJECT); | |||
faultIfEmpty(version, ERROR_NO_VERSION); | |||
faultIfEmpty(version, ERROR_NO_SUFFIX); | |||
} | |||
public String toString() { | |||
return "Library " + getNormalFilename() | |||
+ " from project " + project | |||
+ " to " + getDestinationName(); | |||
} | |||
/** | |||
* calculare the destination file of a library | |||
* | |||
* @param baseDir | |||
* | |||
* @throws BuildException if invalid | |||
*/ | |||
public void bind(File baseDir) { | |||
validate(); | |||
if (destinationName == null) { | |||
destinationName = getNormalFilename(); | |||
} | |||
libraryFile = new File(baseDir, destinationName); | |||
} | |||
/** | |||
* a test that is only valid after binding | |||
* | |||
* @return | |||
*/ | |||
public boolean exists() { | |||
return libraryFile.exists(); | |||
} | |||
/** | |||
* get the last modified date | |||
* | |||
* @return | |||
*/ | |||
public long getLastModified() { | |||
return libraryFile.lastModified(); | |||
} | |||
/** | |||
* get the filename from the rule of archive+version+'.'+suffix. Clearly | |||
* only valid if all fields are defined. | |||
* | |||
* @return a string representing the expected name of the file at the | |||
* source | |||
*/ | |||
public String getNormalFilename() { | |||
return archive + "-" + version + "." + suffix; | |||
} | |||
/** | |||
* get the filename of the destination; no path. | |||
* | |||
* @return | |||
*/ | |||
public String getDestFilename() { | |||
if (destinationName == null) { | |||
return getNormalFilename(); | |||
} else { | |||
return destinationName; | |||
} | |||
} | |||
/** | |||
* get a maven path (project/filename) | |||
* | |||
* @param separator directory separator | |||
* | |||
* @return | |||
*/ | |||
public String getMavenPath(char separator) { | |||
return project + separator + "jars" + separator + getNormalFilename(); | |||
} | |||
/** | |||
* get the absolute path of this library | |||
* | |||
* @return | |||
*/ | |||
public String getAbsolutePath() { | |||
return libraryFile.getAbsolutePath(); | |||
} | |||
} |
@@ -0,0 +1,65 @@ | |||
/* | |||
* Copyright 2004 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.taskdefs.optional.repository; | |||
/** | |||
* A Maven repository knows about maven repository layout rules It also defaults | |||
* to http://www.ibiblio.org/maven/ | |||
* | |||
* @link http://maven.apache.org/reference/user-guide.html#Remote_Repository_Layout | |||
* @link | |||
* @since Ant1.7 | |||
*/ | |||
public class MavenRepository extends HttpRepository { | |||
public static final String MAVEN_URL = "http://www.ibiblio.org/maven/"; | |||
/** | |||
* bind to the main maven repository | |||
*/ | |||
public MavenRepository() { | |||
setUrl(MAVEN_URL); | |||
} | |||
/** | |||
* Get the path to a remote library. This is the full URL | |||
* | |||
* @param library | |||
* | |||
* @return URL to library | |||
*/ | |||
protected String getRemoteLibraryURL(Library library) { | |||
String base = getUrl(); | |||
if (!base.endsWith("/")) { | |||
base = base + '/'; | |||
} | |||
return base + library.getMavenPath('/'); | |||
} | |||
/** | |||
* Returns a string representation of the repository | |||
* | |||
* @return the base URL | |||
*/ | |||
public String toString() { | |||
return "Maven Repository at " + getUrl(); | |||
} | |||
} |
@@ -0,0 +1,109 @@ | |||
/* | |||
* Copyright 2004 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.taskdefs.optional.repository; | |||
import org.apache.tools.ant.BuildException; | |||
import org.apache.tools.ant.types.DataType; | |||
import java.io.IOException; | |||
/** | |||
* This type represents a repository; a place that stores libraries for | |||
* retrieval. To use this type, you must use a non-abstract class, either one | |||
* that ships with Ant, or one you implement and declare yourself. | |||
* <p/> | |||
* The <getlibraries> task lets you supply a repository by reference | |||
* inline {@link GetLibraries#add(Repository)} or on the command line {@link | |||
* GetLibraries#setRepositoryRef(org.apache.tools.ant.types.Reference)} | |||
* | |||
* @since Ant1.7 | |||
*/ | |||
public abstract class Repository extends DataType { | |||
/** | |||
* validate yourself | |||
* | |||
* @throws BuildException if unhappy | |||
*/ | |||
public void validate() { | |||
} | |||
/** | |||
* recursively resolve any references to get the real repository | |||
* | |||
* @return | |||
*/ | |||
public final Repository resolve() { | |||
if (getRefid() == null) { | |||
return this; | |||
} else { | |||
Repository repository = (Repository) getCheckedRef(this.getClass(), | |||
"Repository"); | |||
return repository; | |||
} | |||
} | |||
/** | |||
* override point: connection is called at the start of the retrieval | |||
* process | |||
* | |||
* @param owner owner of the libraries | |||
* | |||
* @throws BuildException | |||
*/ | |||
public void connect(GetLibraries owner) { | |||
} | |||
/** | |||
* override point: connection is called at the start of the retrieval | |||
* process | |||
* | |||
* @throws BuildException | |||
*/ | |||
public void disconnect() { | |||
} | |||
/** | |||
* Test for a repository being reachable. This method is called after {@link | |||
* #connect(GetLibraries)} is called, before any files are to be retrieved. | |||
* <p/> | |||
* If it returns false the repository considers itself offline. Similarly, | |||
* any ioexception is interpreted as being offline. | |||
* | |||
* @return true if the repository is online. | |||
* | |||
* @throws IOException | |||
*/ | |||
public abstract boolean checkRepositoryReachable() throws IOException; | |||
/** | |||
* fetch a library from the repository | |||
* | |||
* @param library | |||
* | |||
* @return | |||
*/ | |||
public abstract boolean fetch(Library library) throws IOException; | |||
} |
@@ -0,0 +1,76 @@ | |||
/* | |||
* Copyright 2004 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.taskdefs.optional.repository; | |||
import org.apache.tools.ant.BuildException; | |||
import org.apache.tools.ant.Project; | |||
import org.apache.tools.ant.types.Reference; | |||
import java.io.IOException; | |||
/** | |||
* not a real repository; one to paste a reference into the chain for | |||
* resolution. | |||
* | |||
* @since Ant1.7 | |||
*/ | |||
public final class RepositoryRef extends Repository { | |||
/** | |||
* create a repository reference | |||
* | |||
* @param reference | |||
*/ | |||
public RepositoryRef(Project project, Reference reference) { | |||
setRefid(reference); | |||
setProject(project); | |||
} | |||
/** | |||
* empty constructor | |||
*/ | |||
public RepositoryRef() { | |||
} | |||
/** | |||
* Test for a repository being reachable. This method is called after {@link | |||
* #connect(GetLibraries)} is called, before any files are to be retrieved. | |||
* <p/> | |||
* If it returns false the repository considers itself offline. Similarly, | |||
* any ioexception is interpreted as being offline. | |||
* | |||
* @return true if the repository is online. | |||
* | |||
* @throws java.io.IOException | |||
*/ | |||
public boolean checkRepositoryReachable() throws IOException { | |||
return false; | |||
} | |||
/** | |||
* fetch a library from the repository | |||
* | |||
* @param library | |||
* | |||
* @return | |||
*/ | |||
public boolean fetch(Library library) throws IOException { | |||
throw new BuildException("Not Implemented"); | |||
} | |||
} |
@@ -33,4 +33,5 @@ scriptfilter=org.apache.tools.ant.types.optional.ScriptFilter | |||
propertyset=org.apache.tools.ant.types.PropertySet | |||
assertions=org.apache.tools.ant.types.Assertions | |||
concatfilter=org.apache.tools.ant.filters.ConcatFilter | |||
ispingable=org.apache.tools.ant.taskdefs.optional.condition.IsPingable | |||
ispingable=org.apache.tools.ant.taskdefs.optional.condition.IsPingable | |||
mavenrepository=org.apache.tools.ant.taskdefs.optional.repository.MavenRepository |
@@ -0,0 +1,109 @@ | |||
/* | |||
* Copyright 2004 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.taskdefs.optional; | |||
import org.apache.tools.ant.BuildFileTest; | |||
import org.apache.tools.ant.taskdefs.optional.repository.GetLibraries; | |||
/** | |||
* test the test libraries stuff. | |||
* skip all the tests if we are offline | |||
*/ | |||
public class GetLibrariesTest extends BuildFileTest { | |||
private final static String TASKDEFS_DIR = "src/etc/testcases/taskdefs/optional/"; | |||
public GetLibrariesTest(String name) { | |||
super(name); | |||
} | |||
public void setUp() { | |||
configureProject(TASKDEFS_DIR + "getlibraries.xml"); | |||
} | |||
protected boolean offline() { | |||
return "true".equals(System.getProperty("offline")); | |||
} | |||
public void tearDown() { | |||
executeTarget("cleanup"); | |||
} | |||
public void testEmpty() { | |||
expectBuildException("testEmpty",GetLibraries.ERROR_NO_DEST_DIR); | |||
} | |||
public void testEmpty2() { | |||
expectBuildException("testEmpty2", GetLibraries.ERROR_NO_REPOSITORY); | |||
} | |||
public void testEmpty3() { | |||
expectBuildException("testEmpty3", GetLibraries.ERROR_NO_LIBRARIES); | |||
} | |||
public void testNoRepo() { | |||
expectBuildException("testNoRepo", GetLibraries.ERROR_NO_REPOSITORY); | |||
} | |||
public void testUnknownReference() { | |||
expectBuildException("testUnknownReference", "Reference unknown not found."); | |||
} | |||
/** | |||
* refs are broken | |||
* */ | |||
public void testFunctionalInline() { | |||
if(offline()) { | |||
return; | |||
} | |||
executeTarget("testFunctionalInline"); | |||
} | |||
public void testMavenInline() { | |||
if (offline()) { | |||
return; | |||
} | |||
executeTarget("testMavenInline"); | |||
} | |||
public void testTwoRepositories() { | |||
expectBuildException("testTwoRepositories", GetLibraries.ERROR_ONE_REPOSITORY_ONLY); | |||
} | |||
public void testMavenInlineBadURL() { | |||
if (offline()) { | |||
return; | |||
} | |||
expectBuildException("testTwoRepositories", | |||
GetLibraries.ERROR_INCOMPLETE_RETRIEVAL); | |||
} | |||
public void testRenaming() { | |||
if (offline()) { | |||
return; | |||
} | |||
executeTarget("testRenaming"); | |||
} | |||
public void testOverwrite() { | |||
if (offline()) { | |||
return; | |||
} | |||
executeTarget("testOverwrite"); | |||
} | |||
} |