/**
* Copyright (c) 2013 Samuel A. Rebelsky. All rights
* reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package edu.grinnell.glimmer.ushahidi;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import org.json.JSONArray;
import org.json.JSONObject;
/**
* An Ushahidi client that gets in data from the Web. The most typical form of
* Ushahidi client.
*
* @version 0.3 of 25 September 2013
* @author Samuel A. Rebelsky
* @author Daniel Torres
*/
public class UshahidiWebClient implements UshahidiClient {
// +-------+----------------------------------------------------------
// | Notes |
// +-------+
/*
* Right now, this code assumes that the server has no more than
* DEFAULT_NUM_INCIDENTS incidents. If the server has more than
* DEFAULT_NUM_INCIDENTS incidents, the client is likely to miss the ones
* with lower ids.
*/
// +-----------+------------------------------------------------------
// | Constants |
// +-----------+
/**
* The default number of incidents we fetch from the server.
*/
static final int DEFAULT_NUM_INCIDENTS = 5000;
// +--------+---------------------------------------------------------
// | Fields |
// +--------+
/**
* All of the incidents that we've loaded so far.
*/
UshahidiIncidentList incidents = null;
/**
* The base URL of the server that we're using (protocol plus hostname or IP
* address).
*/
String server;
/**
* The largest id we've seen so far.
*/
int maxId;
/**
* The number of incidents we fetch.
*/
int numIncidents;
// +--------------+---------------------------------------------------
// | Constructors |
// +--------------+
/**
* Create a new client that connects to server to obtain data.
*
* @param server
* A string that gives the prefix of the URL, including the
* protocol and the hostname. For example,
* https://farmersmarket.crowdmap.com/
* @exception Exception
* when we cannot connect to the server.
* @pre The constructor must be a valid URL for an Ushahidi server.
* @post The server is available for obtaining values.
*/
public UshahidiWebClient(String server) throws Exception {
this(server, DEFAULT_NUM_INCIDENTS);
} // UshahidiWebClient
/**
* Create a new client that connects to server to obtain up to numIncidents
* incidents.
*
* @param server
* A string that gives the prefix of the URL, including the
* protocol and the hostname. For example,
* https://farmersmarket.crowdmap.com/
* @param numIncidents
* The number of incidents we grab from the server, more or less.
* Must be a non-negative integer.
* @exception Exception
* when we cannot connect to the server.
* @pre The constructor must be a valid URL for an Ushahidi server.
* @post The server is available for obtaining values.
*/
public UshahidiWebClient(String server, int numIncidents) throws Exception {
// Fill in the fields
this.server = server;
this.numIncidents = numIncidents;
this.maxId = 0;
this.incidents = new UshahidiIncidentList();
if (this.fetchIncidents() == 0) {
throw new Exception("No incidents available.");
} // if (this.fetchIndicents() == 0)
} // UshahidiWebClient
// +---------+--------------------------------------------------------
// | Helpers |
// +---------+
/**
* Fetch the next set of incidents from the server.
*
* @return n, The number of incidents fetched
*
* @exception Exception
* If we cannot get incidents from the server, or if any of
* the incidents is malformed.
* @pre This object has been initialized.
* @post This object now contains up to numIncidents additional incidents.
* (It will be fewer than numIncidents if the server provides fewer.)
*/
public int fetchIncidents() throws Exception {
BufferedReader input; // Textual input from the server
String line; // One line of data from the server
String text = ""; // Data read from the server
JSONObject data; // Parsed text from the server
String errorNumber; // The number of the error
URL serverURL; // The full URL for fetching data
// Determine the URL to use. We use different URLs depending on
// whether this is the first time we've tried to fetch incidents
// or a subsequent time.
if (this.maxId == 0) {
serverURL = new URL(this.server
+ "/api?task=incidents&by=all&limit=" + numIncidents);
} else {
serverURL = new URL(this.server + "/api?task=incidents&by=sinceid"
+ "&id=" + this.maxId + "&limit=" + numIncidents);
}
// Connect to the server
HttpURLConnection connection;
try {
connection = (HttpURLConnection) serverURL.openConnection();
connection.connect();
} catch (Exception e) {
throw new Exception("Could not connect to " + this.server
+ " because " + e.toString());
}
// Read the data from the server
try {
input = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
} catch (Exception e) {
throw new Exception("Could not get Ushahidi data from "
+ this.server);
}
while ((line = input.readLine()) != null) {
text += line;
} // while
// Testing: What does the text look like?
// System.out.println(text);
// Grab the JSON text, which starts with an open brace
text = text.substring(text.indexOf("{"));
// Convert the text to a JSONObject
data = new JSONObject(text);
// Check to make sure that we succeeded
try {
JSONObject error = data.getJSONObject("error");
if (error.getInt("code") != 0) {
System.out.println("Could not get data from server because "
+ error.getInt("message"));
return 0;
} // if the code indicates that the request was invalid
} catch (Exception e) {
return 0;
} // catch (Exception)
// Grab all of the elements
JSONArray incidents = data.getJSONObject("payload").getJSONArray(
"incidents");
int len = incidents.length();
for (int i = 0; i < len; i++) {
UshahidiIncident nextIncident = new UshahidiIncident(
(JSONObject) incidents.get(i));
this.incidents.addIncident(nextIncident);
if (nextIncident.getId() > this.maxId)
this.maxId = nextIncident.getId();
} // for
return len;
} // fetchIncidents()
// +----------------+-------------------------------------------------
// | Public Methods |
// +----------------+
public UshahidiIncident[] getIncidents() {
return this.incidents.getIncidents();
} // getIncidents()
/**
* Determine if any unseen incidents remain.
*/
public boolean hasMoreIncidents() {
// If the list has more incidents, we're set.
if (this.incidents.hasMoreIncidents())
return true;
// Otherwise, try to fetch some more incidents.
try {
return (this.fetchIncidents() > 0);
} catch (Exception e) {
return false;
}
} // hasMoreIncidents()
/**
* Get the next unseen incident.
*
* @exception Exception
* If no incidents remain.
*/
public UshahidiIncident nextIncident() throws Exception {
// If there aren't any unvisited incidents left
if (!this.incidents.hasMoreIncidents()) {
// Try to get some more
if (this.fetchIncidents() == 0) {
throw new Exception("No incidents remain.");
} // if we can't fetch any incidents
} // if no incidents rmeain
return this.incidents.nextIncident();
} // nextIncident
} // UshahidiClient