summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Wu <peter@lekensteyn.nl>2014-04-23 19:51:17 +0200
committerPeter Wu <peter@lekensteyn.nl>2014-04-23 19:52:24 +0200
commit532efef6350682fa3eb136ee08d106791deb360a (patch)
tree195d605ebb44df7e61c11d225dea434044f4403a
parent86827fff1d1fba26db4f80e93e27b6b7705a9661 (diff)
downloadTwitterDataAnalytics-532efef6350682fa3eb136ee08d106791deb360a.tar.gz
[WIP] API requester abstraction
Authentication, etc. is shuffled into Requester. Implementators (currently OAuth and Bearer) should prepare proper requests.
-rw-r--r--lib/commons-io-2.4.jarbin0 -> 185140 bytes
-rw-r--r--nbproject/project.properties2
-rw-r--r--src/mining/AbstractRequester.java72
-rw-r--r--src/mining/BearerRequester.java72
-rw-r--r--src/mining/OAuthRequester.java35
-rw-r--r--src/mining/Requester.java23
6 files changed, 204 insertions, 0 deletions
diff --git a/lib/commons-io-2.4.jar b/lib/commons-io-2.4.jar
new file mode 100644
index 0000000..90035a4
--- /dev/null
+++ b/lib/commons-io-2.4.jar
Binary files differ
diff --git a/nbproject/project.properties b/nbproject/project.properties
index 57f05f8..e5aabb7 100644
--- a/nbproject/project.properties
+++ b/nbproject/project.properties
@@ -31,6 +31,7 @@ file.reference.collections-generic-4.01.jar=lib/collections-generic-4.01.jar
file.reference.colt-1.2.0.jar=lib/colt-1.2.0.jar
file.reference.commons-codec-1.7.jar=lib/commons-codec-1.7.jar
file.reference.commons-httpclient-3.1_1.jar=lib/commons-httpclient-3.1_1.jar
+file.reference.commons-io-2.4.jar=lib/commons-io-2.4.jar
file.reference.commons-lang-2.6.jar=lib/commons-lang-2.6.jar
file.reference.commons-logging-1.1.1.jar=lib/commons-logging-1.1.1.jar
file.reference.concurrent-1.3.4.jar=lib/concurrent-1.3.4.jar
@@ -66,6 +67,7 @@ javac.classpath=\
${file.reference.colt-1.2.0.jar}:\
${file.reference.commons-codec-1.7.jar}:\
${file.reference.commons-httpclient-3.1_1.jar}:\
+ ${file.reference.commons-io-2.4.jar}:\
${file.reference.commons-lang-2.6.jar}:\
${file.reference.commons-logging-1.1.1.jar}:\
${file.reference.concurrent-1.3.4.jar}:\
diff --git a/src/mining/AbstractRequester.java b/src/mining/AbstractRequester.java
new file mode 100644
index 0000000..a42eb80
--- /dev/null
+++ b/src/mining/AbstractRequester.java
@@ -0,0 +1,72 @@
+package mining;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import org.apache.commons.io.Charsets;
+import org.apache.commons.io.IOUtils;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Performs an API Request.
+ *
+ * @author Peter Wu
+ */
+public abstract class AbstractRequester implements Requester {
+
+ @Override
+ public JSONObject getJSON(String resource) throws IOException {
+ HttpURLConnection conn = open(buildUrl(resource));
+ preconnect(conn);
+ if (conn.getResponseCode() != 200) {
+ // TODO: print more helpful details
+ throw new IOException("Unexpected response code");
+ }
+ return getResponseAsJson(conn);
+ }
+
+ protected final URL buildUrl(String resource) throws IOException {
+ String spec = "https://api.twitter.com/1.1/";
+ // TODO: detect API version? For example, drop 1.1 for oauth2/token
+ spec += resource;
+ return new URL(spec);
+ }
+
+ /**
+ * Opens a connection to the URL.
+ */
+ protected final HttpURLConnection open(URL url) throws IOException {
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ // set default param: fail if no response within 5 seconds
+ conn.setReadTimeout(5000);
+ return conn;
+ }
+
+ /**
+ * Reads the response body from a connection (in JSON format).
+ */
+ protected final JSONObject getResponseAsJson(URLConnection conn)
+ throws IOException {
+ StringWriter writer = new StringWriter();
+ IOUtils.copy(conn.getInputStream(), writer, Charsets.UTF_8);
+ try {
+ return new JSONObject(writer.toString());
+ } catch (JSONException ex) {
+ // treat JSON errors as if an I/O error occurred
+ throw new IOException(ex);
+ }
+ }
+
+ /**
+ * Prepare the request before it gets send.
+ *
+ * @param conn A connection for the request.
+ * @throws java.io.IOException on failing to prepare the request.
+ */
+ protected abstract void preconnect(URLConnection conn)
+ throws IOException;
+}
diff --git a/src/mining/BearerRequester.java b/src/mining/BearerRequester.java
new file mode 100644
index 0000000..94c5314
--- /dev/null
+++ b/src/mining/BearerRequester.java
@@ -0,0 +1,72 @@
+package mining;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.Charsets;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * An API requester used for application-only requests. Not all requests are
+ * allowed, see https://dev.twitter.com/docs/auth/application-only-auth for
+ * details.
+ *
+ * @author Peter Wu
+ */
+public class BearerRequester extends AbstractRequester {
+
+ private final String access_token;
+
+ /**
+ *
+ * @param access_token The bearer token that authenticates a request.
+ */
+ public BearerRequester(String access_token) {
+ this.access_token = access_token;
+ }
+
+ public BearerRequester(String consumerKey, String consumerSecret)
+ throws IOException {
+ String creds = encodeToken(consumerKey, consumerSecret);
+ String postData = "grant_type=client_credentials";
+ URL url = buildUrl("oauth2/token");
+ HttpURLConnection conn = open(url);
+ conn.setRequestMethod("POST");
+ // set request headers
+ conn.addRequestProperty("Authorization", "Basic " + creds);
+ conn.addRequestProperty("Content-Type",
+ "application/x-www-form-urlencoded; charset=UTF-8");
+ conn.setFixedLengthStreamingMode(postData.length());
+ // connect and send request
+ conn.getOutputStream().write(postData.getBytes(Charsets.UTF_8));
+
+ try {
+ JSONObject resp = getResponseAsJson(conn);
+ // TODO: parse resp.errors
+ if (!resp.getString("token_type").equals("bearer")) {
+ throw new IOException("Expected bearer token type");
+ }
+ access_token = resp.getString("access_token");
+ } catch (JSONException ex) {
+ // treat JSON errors as if an I/O error occurred
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ protected void preconnect(URLConnection conn) throws IOException {
+ // requests do not have to be signed, instead rely on the Bearer token
+ conn.addRequestProperty("Authorization", "Bearer " + access_token);
+ }
+
+ private String encodeToken(String consumerKey, String consumerSecret) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(consumerKey);
+ sb.append(":");
+ sb.append(consumerSecret);
+ return Base64.encodeBase64String(sb.toString().getBytes(Charsets.UTF_8));
+ }
+}
diff --git a/src/mining/OAuthRequester.java b/src/mining/OAuthRequester.java
new file mode 100644
index 0000000..e72360e
--- /dev/null
+++ b/src/mining/OAuthRequester.java
@@ -0,0 +1,35 @@
+package mining;
+
+import java.io.IOException;
+import java.net.URLConnection;
+import oauth.signpost.OAuthConsumer;
+import oauth.signpost.basic.DefaultOAuthConsumer;
+import oauth.signpost.exception.OAuthException;
+
+/**
+ * An API requester that uses OAuth to sign its requests.
+ *
+ * @author Peter Wu
+ */
+public class OAuthRequester extends AbstractRequester {
+
+ /**
+ * Instance that signs signs HTTP requests with an OAuth token and secret.
+ */
+ private final OAuthConsumer consumer;
+
+ public OAuthRequester(String consumerKey, String consumerSecret) {
+ // create a new application-specific OAuth consumer
+ consumer = new DefaultOAuthConsumer(consumerKey, consumerSecret);
+ // TODO: access tokens?
+ }
+
+ @Override
+ protected void preconnect(URLConnection conn) throws IOException {
+ try {
+ consumer.sign(conn);
+ } catch (OAuthException ex) {
+ throw new IOException(ex);
+ }
+ }
+}
diff --git a/src/mining/Requester.java b/src/mining/Requester.java
new file mode 100644
index 0000000..931b0ac
--- /dev/null
+++ b/src/mining/Requester.java
@@ -0,0 +1,23 @@
+package mining;
+
+import java.io.IOException;
+import org.json.JSONObject;
+
+/**
+ * Performs an API request.
+ *
+ * @author Peter Wu
+ */
+public interface Requester {
+
+ /**
+ * Performs an API request for a resource, for example
+ * "statuses/mentions_timeline" (note that there is no version or leading
+ * slash).
+ *
+ * @param resource The REST resource.
+ * @return A JSON object resulting from the request.
+ * @throws java.io.IOException on error fetching the resource.
+ */
+ public JSONObject getJSON(String resource) throws IOException;
+}