From ce531cdc38b280abcc9b93930a228cbbc034d952 Mon Sep 17 00:00:00 2001 From: Pablo Fernandez Date: Wed, 14 Dec 2011 13:31:01 -0300 Subject: [PATCH] first step towards ParameterList refactoring --- .../extractors/BaseStringExtractorImpl.java | 12 +- src/main/java/org/scribe/model/Parameter.java | 50 ++++++++ .../java/org/scribe/model/ParameterList.java | 117 ++++++++++++++++++ src/main/java/org/scribe/model/Request.java | 41 +++--- src/main/java/org/scribe/utils/MapUtils.java | 22 ---- .../java/org/scribe/utils/OAuthEncoder.java | 57 +++++++++ src/main/java/org/scribe/utils/URLUtils.java | 23 ---- .../org/scribe/examples/TwitterExample.java | 5 +- .../org/scribe/model/ParameterListTest.java | 62 ++++++++++ .../java/org/scribe/model/RequestTest.java | 7 +- .../java/org/scribe/utils/URLUtilsTest.java | 45 ------- 11 files changed, 318 insertions(+), 123 deletions(-) create mode 100644 src/main/java/org/scribe/model/Parameter.java create mode 100644 src/main/java/org/scribe/model/ParameterList.java create mode 100644 src/main/java/org/scribe/utils/OAuthEncoder.java create mode 100644 src/test/java/org/scribe/model/ParameterListTest.java diff --git a/src/main/java/org/scribe/extractors/BaseStringExtractorImpl.java b/src/main/java/org/scribe/extractors/BaseStringExtractorImpl.java index 73442b9..dcf77e6 100644 --- a/src/main/java/org/scribe/extractors/BaseStringExtractorImpl.java +++ b/src/main/java/org/scribe/extractors/BaseStringExtractorImpl.java @@ -31,12 +31,12 @@ public class BaseStringExtractorImpl implements BaseStringExtractor private String getSortedAndEncodedParams(OAuthRequest request) { - Map params = new HashMap(); - MapUtils.decodeAndAppendEntries(request.getQueryStringParams(), params); - MapUtils.decodeAndAppendEntries(request.getBodyParams(), params); - MapUtils.decodeAndAppendEntries(request.getOauthParameters(), params); - params = MapUtils.sort(params); - return URLUtils.percentEncode(MapUtils.concatSortedPercentEncodedParams(params)); + ParameterList params = new ParameterList(); + params.addAll(request.getQueryStringParams()); + params.addAll(request.getBodyParams()); + params.addAll(new ParameterList(request.getOauthParameters())); + params.sort(); + return params.asOauthBaseString(); } private void checkPreconditions(OAuthRequest request) diff --git a/src/main/java/org/scribe/model/Parameter.java b/src/main/java/org/scribe/model/Parameter.java new file mode 100644 index 0000000..9fe610b --- /dev/null +++ b/src/main/java/org/scribe/model/Parameter.java @@ -0,0 +1,50 @@ +package org.scribe.model; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import org.scribe.exceptions.OAuthException; +import org.scribe.utils.OAuthEncoder; + +/** + * @author: Pablo Fernandez + */ +public class Parameter implements Comparable +{ + private static final String UTF = "UTF8"; + + private final String key; + private final String value; + + public Parameter(String key, String value) + { + this.key = key; + this.value = value; + } + + public String asUrlEncodedPair() + { + return OAuthEncoder.encode(key).concat("=").concat(OAuthEncoder.encode(value)); + } + + public boolean equals(Object other) + { + if(other == null) return false; + if(other == this) return true; + if(!(other instanceof Parameter)) return false; + + Parameter otherParam = (Parameter) other; + return otherParam.key.equals(key) && otherParam.value.equals(value); + } + + public int hashCode() + { + return key.hashCode() + value.hashCode(); + } + + public int compareTo(Parameter parameter) + { + int keyDiff = key.compareTo(parameter.key); + + return keyDiff != 0 ? keyDiff : value.compareTo(parameter.value); + } +} diff --git a/src/main/java/org/scribe/model/ParameterList.java b/src/main/java/org/scribe/model/ParameterList.java new file mode 100644 index 0000000..3c71991 --- /dev/null +++ b/src/main/java/org/scribe/model/ParameterList.java @@ -0,0 +1,117 @@ +package org.scribe.model; + +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.scribe.utils.Preconditions; +import org.scribe.utils.URLUtils; + +/** + * @author: Pablo Fernandez + */ +public class ParameterList +{ + private static final char QUERY_STRING_SEPARATOR = '?'; + private static final String PARAM_SEPARATOR = "&"; + private static final String PAIR_SEPARATOR = "="; + private static final String EMPTY_STRING = ""; + + private final List params; + + public ParameterList() + { + params = new ArrayList(); + } + + public ParameterList(Map map) + { + this(); + for(Map.Entry entry : map.entrySet()) + { + params.add(new Parameter(entry.getKey(), entry.getValue())); + } + } + + public void add(String key, String value) + { + params.add(new Parameter(key, value)); + } + + public String appendTo(String url) + { + Preconditions.checkNotNull(url, "Cannot append to null URL"); + String queryString = asFormUrlEncodedString(); + if (queryString.equals(EMPTY_STRING)) + { + return url; + } + else + { + url += url.indexOf(QUERY_STRING_SEPARATOR) != -1 ? PARAM_SEPARATOR : QUERY_STRING_SEPARATOR; + url += queryString; + return url; + } + } + + public String asOauthBaseString() + { + try + { + return URLEncoder.encode(asFormUrlEncodedString(), "UTF8"); + } + catch (UnsupportedEncodingException uee) + { + throw new RuntimeException(); + } + } + + public String asFormUrlEncodedString() + { + if (params.size() == 0) return EMPTY_STRING; + + StringBuilder builder = new StringBuilder(); + for(Parameter p : params) + { + builder.append('&').append(p.asUrlEncodedPair()); + } + return builder.toString().substring(1); + } + + public void addAll(ParameterList other) + { + params.addAll(other.params); + } + + public void addQuerystring(String queryString) + { + if (queryString != null && queryString.length() > 0) + { + for (String param : queryString.split(PARAM_SEPARATOR)) + { + String pair[] = param.split(PAIR_SEPARATOR); + String key = URLUtils.formURLDecode(pair[0]); + String value = pair.length > 1 ? URLUtils.formURLDecode(pair[1]) : EMPTY_STRING; + params.add(new Parameter(key, value)); + } + } + } + + public boolean contains(Parameter param) + { + return params.contains(param); + } + + public int size() + { + return params.size(); + } + + public void sort() + { + Collections.sort(params); + } +} diff --git a/src/main/java/org/scribe/model/Request.java b/src/main/java/org/scribe/model/Request.java index 4e78bcc..223b1d2 100644 --- a/src/main/java/org/scribe/model/Request.java +++ b/src/main/java/org/scribe/model/Request.java @@ -7,7 +7,6 @@ import java.util.*; import java.util.concurrent.TimeUnit; import org.scribe.exceptions.*; -import org.scribe.utils.*; /** * Represents an HTTP Request object @@ -22,8 +21,8 @@ class Request private String url; private Verb verb; - private Map querystringParams; - private Map bodyParams; + private ParameterList querystringParams; + private ParameterList bodyParams; private Map headers; private String payload = null; private HttpURLConnection connection; @@ -43,8 +42,8 @@ class Request { this.verb = verb; this.url = url; - this.querystringParams = new HashMap(); - this.bodyParams = new HashMap(); + this.querystringParams = new ParameterList(); + this.bodyParams = new ParameterList(); this.headers = new HashMap(); } @@ -74,7 +73,7 @@ class Request private void createConnection() throws IOException { - String effectiveUrl = URLUtils.appendParametersToQueryString(url, querystringParams); + String effectiveUrl = querystringParams.appendTo(url); if (connection == null) { System.setProperty("http.keepAlive", connectionKeepAlive ? "true" : "false"); @@ -139,7 +138,7 @@ class Request */ public void addBodyParameter(String key, String value) { - this.bodyParams.put(key, value); + this.bodyParams.add(key, value); } /** @@ -150,7 +149,7 @@ class Request */ public void addQuerystringParameter(String key, String value) { - this.querystringParams.put(key, value); + this.querystringParams.add(key, value); } /** @@ -179,20 +178,20 @@ class Request } /** - * Get a {@link Map} of the query string parameters. + * Get a {@link ParameterList} with the query string parameters. * - * @return a map containing the query string parameters - * @throws OAuthException if the URL is not valid + * @return a {@link ParameterList} containing the query string parameters. + * @throws OAuthException if the request URL is not valid. */ - public Map getQueryStringParams() + public ParameterList getQueryStringParams() { try { - Map params = new HashMap(); + ParameterList result = new ParameterList(); String queryString = new URL(url).getQuery(); - params.putAll(MapUtils.queryStringToMap(queryString)); - params.putAll(this.querystringParams); - return params; + result.addQuerystring(queryString); + result.addAll(querystringParams); + return result; } catch (MalformedURLException mue) { @@ -201,11 +200,11 @@ class Request } /** - * Obtains a {@link Map} of the body parameters. + * Obtains a {@link ParameterList} of the body parameters. * - * @return a map containing the body parameters. + * @return a {@link ParameterList}containing the body parameters. */ - public Map getBodyParams() + public ParameterList getBodyParams() { return bodyParams; } @@ -251,7 +250,7 @@ class Request byte[] getByteBodyContents() { if (bytePayload != null) return bytePayload; - String body = (payload != null) ? payload : URLUtils.formURLEncodeMap(bodyParams); + String body = (payload != null) ? payload : bodyParams.asFormUrlEncodedString(); try { return body.getBytes(getCharset()); @@ -327,7 +326,7 @@ class Request } /** - * Sets wether the underlying Http Connection is persistent or not. + * Sets whether the underlying Http Connection is persistent or not. * * @see http://download.oracle.com/javase/1.5.0/docs/guide/net/http-keepalive.html * @param connectionKeepAlive diff --git a/src/main/java/org/scribe/utils/MapUtils.java b/src/main/java/org/scribe/utils/MapUtils.java index a5076b3..18f507e 100644 --- a/src/main/java/org/scribe/utils/MapUtils.java +++ b/src/main/java/org/scribe/utils/MapUtils.java @@ -69,26 +69,4 @@ public class MapUtils } return result.toString().substring(0, result.length() - 1); } - - /** - * Parses and form-urldecodes a querystring-like string into a map - * - * @param queryString querystring-like String - * @return a map with the form-urldecoded parameters - */ - public static Map queryStringToMap(String queryString) - { - Map result = new HashMap(); - if (queryString != null && queryString.length() > 0) - { - for (String param : queryString.split(PARAM_SEPARATOR)) - { - String pair[] = param.split(PAIR_SEPARATOR); - String key = URLUtils.formURLDecode(pair[0]); - String value = pair.length > 1 ? URLUtils.formURLDecode(pair[1]) : EMPTY_STRING; - result.put(key, value); - } - } - return result; - } } diff --git a/src/main/java/org/scribe/utils/OAuthEncoder.java b/src/main/java/org/scribe/utils/OAuthEncoder.java new file mode 100644 index 0000000..0d9ec9f --- /dev/null +++ b/src/main/java/org/scribe/utils/OAuthEncoder.java @@ -0,0 +1,57 @@ +package org.scribe.utils; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; +import org.scribe.exceptions.OAuthException; + +/** + * @author: Pablo Fernandez + */ +public class OAuthEncoder +{ + private static String CHARSET = "UTF-8"; + private static final Map ENCODING_RULES; + + static + { + Map rules = new HashMap(); + rules.put("*", "%2A"); + rules.put("+", "%20"); + rules.put("%7E", "~"); + ENCODING_RULES = Collections.unmodifiableMap(rules); + } + + public static String encode(String plain) + { + Preconditions.checkNotNull(plain, "Cannot encode null object"); + String encoded = ""; + try + { + encoded = URLEncoder.encode(plain, CHARSET); + } + catch (UnsupportedEncodingException uee) + { + throw new OAuthException("Charset not found while encoding string: " + CHARSET, uee); + } + for(Map.Entry rule : ENCODING_RULES.entrySet()) + { + encoded = applyRule(encoded, rule.getKey(), rule.getValue()); + } + return encoded; + } + + private static String applyRule(String encoded, String toReplace, String replacement) + { + return encoded.replaceAll(Pattern.quote(toReplace), replacement); + } + + public static String decode(String encoded) + { + Preconditions.checkNotNull(encoded, "Cannot decode null object"); + return encoded; + } +} diff --git a/src/main/java/org/scribe/utils/URLUtils.java b/src/main/java/org/scribe/utils/URLUtils.java index 745e113..7c655ad 100644 --- a/src/main/java/org/scribe/utils/URLUtils.java +++ b/src/main/java/org/scribe/utils/URLUtils.java @@ -110,29 +110,6 @@ public class URLUtils } } - /** - * Append given parameters to the query string of the url - * - * @param url the url to append parameters to - * @param params any map - * @return new url with parameters on query string - */ - public static String appendParametersToQueryString(String url, Map params) - { - Preconditions.checkNotNull(url, "Cannot append to null URL"); - String queryString = URLUtils.formURLEncodeMap(params); - if (queryString.equals(EMPTY_STRING)) - { - return url; - } - else - { - url += url.indexOf(QUERY_STRING_SEPARATOR) != -1 ? PARAM_SEPARATOR : QUERY_STRING_SEPARATOR; - url += queryString; - return url; - } - } - private static final class EncodingRule { private final String ch; diff --git a/src/test/java/org/scribe/examples/TwitterExample.java b/src/test/java/org/scribe/examples/TwitterExample.java index f47fa0e..28d9bea 100644 --- a/src/test/java/org/scribe/examples/TwitterExample.java +++ b/src/test/java/org/scribe/examples/TwitterExample.java @@ -9,7 +9,7 @@ import org.scribe.oauth.*; public class TwitterExample { - private static final String PROTECTED_RESOURCE_URL = "http://api.twitter.com/1/account/verify_credentials.xml"; + private static final String PROTECTED_RESOURCE_URL = "https://api.twitter.com/1/statuses/update.json"; public static void main(String[] args) { @@ -45,7 +45,8 @@ public class TwitterExample // Now let's go and ask for a protected resource! System.out.println("Now we're going to access a protected resource..."); - OAuthRequest request = new OAuthRequest(Verb.GET, PROTECTED_RESOURCE_URL); + OAuthRequest request = new OAuthRequest(Verb.POST, PROTECTED_RESOURCE_URL); + request.addBodyParameter("status", "this is sparta! *"); service.signRequest(accessToken, request); Response response = request.send(); System.out.println("Got it! Lets see what we found..."); diff --git a/src/test/java/org/scribe/model/ParameterListTest.java b/src/test/java/org/scribe/model/ParameterListTest.java new file mode 100644 index 0000000..46f4007 --- /dev/null +++ b/src/test/java/org/scribe/model/ParameterListTest.java @@ -0,0 +1,62 @@ +package org.scribe.model; + +import java.util.HashMap; +import java.util.Map; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.scribe.utils.URLUtils; + +/** + * @author: Pablo Fernandez + */ +public class ParameterListTest +{ + private ParameterList params; + + @Before + public void setup() + { + this.params = new ParameterList(); + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowExceptionWhenAppendingNullMapToQuerystring() + { + String url = null; + params.appendTo(url); + } + + @Test + public void shouldAppendNothingToQuerystringIfGivenEmptyMap() + { + String url = "http://www.example.com"; + Assert.assertEquals(url, params.appendTo(url)); + } + + @Test + public void shouldAppendParametersToSimpleUrl() + { + String url = "http://www.example.com"; + String expectedUrl = "http://www.example.com?param1=value1¶m2=value%20with%20spaces"; + + params.add("param1", "value1"); + params.add("param2", "value with spaces"); + + url = params.appendTo(url); + Assert.assertEquals(url, expectedUrl); + } + + @Test + public void shouldAppendParametersToUrlWithQuerystring() + { + String url = "http://www.example.com?already=present"; + String expectedUrl = "http://www.example.com?already=present¶m1=value1¶m2=value%20with%20spaces"; + + params.add("param1", "value1"); + params.add("param2", "value with spaces"); + + url = params.appendTo(url); + Assert.assertEquals(url, expectedUrl); + } +} diff --git a/src/test/java/org/scribe/model/RequestTest.java b/src/test/java/org/scribe/model/RequestTest.java index 5bf46db..f2ddc93 100644 --- a/src/test/java/org/scribe/model/RequestTest.java +++ b/src/test/java/org/scribe/model/RequestTest.java @@ -34,8 +34,7 @@ public class RequestTest { assertEquals(2, getRequest.getQueryStringParams().size()); assertEquals(0, postRequest.getQueryStringParams().size()); - assertTrue(getRequest.getQueryStringParams().containsKey("qsparam")); - assertTrue(getRequest.getQueryStringParams().get("qsparam").equals("value")); + assertTrue(getRequest.getQueryStringParams().contains(new Parameter("qsparam", "value"))); } @Test @@ -51,7 +50,7 @@ public class RequestTest @Test public void shouldSetBodyParamsAndAddContentLength() { - assertEquals("param=value¶m+with+spaces=value+with+spaces", postRequest.getBodyContents()); + assertEquals("param=value¶m%20with%20spaces=value%20with%20spaces", postRequest.getBodyContents()); postRequest.send(); assertTrue(connection.getHeaders().containsKey("Content-Length")); } @@ -77,7 +76,7 @@ public class RequestTest @Test public void shouldHandleQueryStringSpaceEncodingProperly() { - assertTrue(getRequest.getQueryStringParams().get("other param").equals("value with spaces")); + assertTrue(getRequest.getQueryStringParams().contains(new Parameter("other param","value with spaces"))); } @Test diff --git a/src/test/java/org/scribe/utils/URLUtilsTest.java b/src/test/java/org/scribe/utils/URLUtilsTest.java index 7927d51..f218077 100644 --- a/src/test/java/org/scribe/utils/URLUtilsTest.java +++ b/src/test/java/org/scribe/utils/URLUtilsTest.java @@ -101,51 +101,6 @@ public class URLUtilsTest URLUtils.formURLDecode(toDecode); } - @Test(expected = IllegalArgumentException.class) - public void shouldThrowExceptionWhenAppendingNullMapToQuerystring() - { - String url = "http://www.example.com"; - Map nullMap = null; - URLUtils.appendParametersToQueryString(url, nullMap); - } - - @Test - public void shouldAppendNothingToQuerystringIfGivenEmptyMap() - { - String url = "http://www.example.com"; - Map emptyMap = new HashMap(); - String newUrl = URLUtils.appendParametersToQueryString(url, emptyMap); - Assert.assertEquals(url, newUrl); - } - - @Test - public void shouldAppendParametersToSimpleUrl() - { - String url = "http://www.example.com"; - String expectedUrl = "http://www.example.com?param1=value1¶m2=value+with+spaces"; - - Map params = new HashMap(); - params.put("param1", "value1"); - params.put("param2", "value with spaces"); - - url = URLUtils.appendParametersToQueryString(url, params); - Assert.assertEquals(url, expectedUrl); - } - - @Test - public void shouldAppendParametersToUrlWithQuerystring() - { - String url = "http://www.example.com?already=present"; - String expectedUrl = "http://www.example.com?already=present¶m1=value1¶m2=value+with+spaces"; - - Map params = new HashMap(); - params.put("param1", "value1"); - params.put("param2", "value with spaces"); - - url = URLUtils.appendParametersToQueryString(url, params); - Assert.assertEquals(url, expectedUrl); - } - @Test public void shouldPercentEncodePlusSymbol() {