From 269970be0e87071f3e7b382202d8f6028ed12d37 Mon Sep 17 00:00:00 2001 From: LKitching Date: Mon, 15 Jul 2013 15:42:31 +0100 Subject: [PATCH] Allow URLs to be parameterised Allow URLs to contain parameters of the format ${parameterName}. Parameter values are not available at configuration time so any parameters containing URLs cannot be validated. --- .../hudson/plugins/URLSCM/URLParameter.java | 92 ++++++++++++++ .../java/hudson/plugins/URLSCM/URLSCM.java | 19 ++- .../plugins/URLSCM/URLParameterTests.java | 113 ++++++++++++++++++ 3 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 src/main/java/hudson/plugins/URLSCM/URLParameter.java create mode 100644 src/test/java/hudson/plugins/URLSCM/URLParameterTests.java diff --git a/src/main/java/hudson/plugins/URLSCM/URLParameter.java b/src/main/java/hudson/plugins/URLSCM/URLParameter.java new file mode 100644 index 0000000..c2019e1 --- /dev/null +++ b/src/main/java/hudson/plugins/URLSCM/URLParameter.java @@ -0,0 +1,92 @@ +package hudson.plugins.URLSCM; + +import java.util.*; +import java.util.regex.Pattern; +import java.util.regex.Matcher; + +/** + * Represents a parameter in a URL + */ +public class URLParameter { + + private static String PARAMETER_NAME_REGEX = "[\\w.-]+"; + private static Pattern PARAMETER_DEFINITION_PATTERN = Pattern.compile("\\$\\{(" + PARAMETER_NAME_REGEX + ")\\}"); + private final String parameterName; + + /** + * Creates a parameter with the given name. + * @param parameterName The name of the parameter. + * @exception IllegalArgumentException If parameterName contains any illegal characters + */ + public URLParameter(String parameterName) { + if(Pattern.matches("^" + PARAMETER_NAME_REGEX + "$", parameterName)) { + this.parameterName = parameterName; + } + else throw new IllegalArgumentException(parameterName + " contains invalid characters"); + } + + /** + * Substitutes this parameter into a parameterised url based on its build parameter value. + * @param url The url to substitute this parameter into + * @param buildParameters The current collection of build parameters + * @return The url with the current parameter substituted with the value specified in buildParameters. If + * the this parameter does not exist in the collection of build parameters, the url will not be modified. + */ + public String substitute(final String url, final Map buildParameters) { + String marker = "${" + this.parameterName + "}"; + String value = buildParameters.get(this.parameterName); + + return value == null ? url : url.replace(marker, value); + } + + /** + * Whether this instance is equal to another object. + * @param other The object to compare to this parameter. + * @return True if other is a URLParameter with the same name. + */ + @Override public boolean equals(Object other) { + return other instanceof URLParameter && ((URLParameter)other).parameterName.equals(this.parameterName); + } + + /** + * Gets the hash code for this parameter. + * @return + */ + @Override public int hashCode() { + return this.parameterName.hashCode(); + } + + /** + * Finds all parameters in a URL. + * @param url The URL to search. + * @return A collection of all the parameters found in the input string. + */ + public static Set getParameters(final String url) { + HashSet parameters = new HashSet(); + + if(url == null) return parameters; + + Matcher matcher = PARAMETER_DEFINITION_PATTERN.matcher(url); + while(matcher.find()) { + parameters.add(new URLParameter(matcher.group(1))); + } + + return parameters; + } + + /** + * Substitutes all parameters in a url based on their current values for the build. + * @param url The URL to substitute parameters into. + * @param buildParameters The current collection of build parameters. + * @return + */ + public static String substituteAll(String url, Map buildParameters) { + Collection parameters = getParameters(url); + + for(URLParameter param : parameters) { + url = param.substitute(url, buildParameters); + } + + return url; + } +} diff --git a/src/main/java/hudson/plugins/URLSCM/URLSCM.java b/src/main/java/hudson/plugins/URLSCM/URLSCM.java index 2cc53e7..73ceeff 100644 --- a/src/main/java/hudson/plugins/URLSCM/URLSCM.java +++ b/src/main/java/hudson/plugins/URLSCM/URLSCM.java @@ -26,6 +26,8 @@ import java.net.URLConnection; import java.util.ArrayList; import java.util.Date; +import java.util.Map; +import java.util.Set; import javax.servlet.ServletException; @@ -63,10 +65,12 @@ public boolean checkout(final AbstractBuild build, workspace.deleteContents(); } + Map buildParameters = build.getBuildVariables(); + final URLDateAction dates = new URLDateAction(build); for (final URLTuple tuple : urls) { - final String urlString = tuple.getUrl(); + final String urlString = expandUrl(tuple, buildParameters); InputStream is = null; OutputStream os = null; try { @@ -105,6 +109,11 @@ public boolean checkout(final AbstractBuild build, return true; } + private static String expandUrl(final URLTuple urlTuple, final Map buildParameters) { + final String rawUrl = urlTuple.getUrl(); + return URLParameter.substituteAll(rawUrl, buildParameters); + } + @Override public ChangeLogParser createChangeLogParser() { return new NullChangeLogParser(); @@ -201,11 +210,19 @@ public FormValidation doUrlCheck(@QueryParameter final String value) if (!Hudson.getInstance().hasPermission(Hudson.ADMINISTER)) { return FormValidation.ok(); } + return new FormValidation.URLCheck() { @Override protected FormValidation check() throws IOException, ServletException { final String url = fixEmpty(value); + + //parameters cannot be validated here, so allow + Set parameters = URLParameter.getParameters(url); + if(! parameters.isEmpty()) { + return FormValidation.ok("URL contains parameters"); + } + URL u = null; try { u = new URL(url); diff --git a/src/test/java/hudson/plugins/URLSCM/URLParameterTests.java b/src/test/java/hudson/plugins/URLSCM/URLParameterTests.java new file mode 100644 index 0000000..524980a --- /dev/null +++ b/src/test/java/hudson/plugins/URLSCM/URLParameterTests.java @@ -0,0 +1,113 @@ +package hudson.plugins.URLSCM; + +import java.util.HashMap; +import java.util.Set; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * Tests for {@link URLParameter} + */ +public class URLParameterTests { + + /** + * Tests an exception is thrown if the parameter name contains invalid characters + */ + @Test(expected = IllegalArgumentException.class) + public void shouldThrowIfParameterContainsInvalidCharacters() { + URLParameter p = new URLParameter("invalid!par ameter"); + } + + /** + * Tests a parameter substitutes itself in a parameterised string. + */ + @Test + public void shouldSubstituteParameter() { + URLParameter param = new URLParameter("host"); + + HashMap parameters = new HashMap(); + parameters.put("host", "example"); + + String result = param.substitute("http://${host}.com/path", parameters); + + assertEquals("http://example.com/path", result); + } + + /** + * Tests a parameter does not try to substitute itself if the corresponding build parameter does not exist. + */ + @Test + public void shouldNotSubstituteIfParameterNotFound() { + URLParameter param = new URLParameter("test"); + + String parameterised = "http://example.com/${test}"; + String result = param.substitute(parameterised, new HashMap()); + + assertEquals(parameterised, result); + } + + /** + * Tests build parameters are found in a parameterised string. + */ + @Test + public void shouldFindParameters() { + String url = "http://${host}.com/${testPath}"; + Set parameters = URLParameter.getParameters(url); + + assertEquals(2, parameters.size()); + } + + /** + * Tests no parameters are found in a string without any valid parameters. + */ + @Test + public void shouldNotFindAnyParameters() { + String str = "String ${with no parameters!}${}"; + Set parameters = URLParameter.getParameters(str); + + assertEquals(0, parameters.size()); + } + + /** + * Tests repeated parameters are only returned once. + */ + @Test + public void shouldNotDuplicateParameters() { + String parameterised = "${param} followed by ${param}"; + Set parameters = URLParameter.getParameters(parameterised); + + assertEquals(1, parameters.size()); + } + + /** + * Tests all parameters are substituted into a parameterised string. + */ + @Test + public void shouldSubstituteAll() { + String parameterised = "http://${host}.com/${path1}/${path2}"; + + HashMap buildParameters = new HashMap(); + buildParameters.put("host", "example"); + buildParameters.put("path1", "first"); + buildParameters.put("path2", "second"); + + String sub = URLParameter.substituteAll(parameterised, buildParameters); + + assertEquals("http://example.com/first/second", sub); + } + + /** + * Tests a parameterless string is not modified. + */ + @Test + public void shouldNotModifyUrlWithNoParameters() { + String unparameterised = "http://example.com/some/path/without/parameters"; + + HashMap buildParameters = new HashMap(); + buildParameters.put("param1", "first"); + buildParameters.put("param2", "second"); + + String sub = URLParameter.substituteAll(unparameterised, buildParameters); + assertEquals(unparameterised, sub); + } +}