Commit 6b4add03 authored by Christopher Bohn's avatar Christopher Bohn 🤔
Browse files

Added documentation

- Javadoc
- README
parent 6dd5dae5
# JSON Connections for Files and REST APIs
## With an enclosing connector for OpenWeather data
REpresentational State Transfer APIs (also known as REST APIs, RESTful APIs, or
RESTful web services) are a mechanism to request data from and send data to
remote servers without maintaining active, stateful connections over a
possibly-unreliable network. The
`edu.unl.cse.soft160.json_connections.connection.RestConnection` class is
sufficient to request data from a RESTful service.
The `edu.unl.cse.soft160.json_connections.connection.FileConnection` class
offers the same interface for requesting data, except that the data will be
found on the local file system. `FileConnection` can also save data to a file
which can be used to save data retrieved from a server using `RestConnection`
to be used later.
The `edu.unl.cse.soft160.json_connections.connector.OpenWeatherConnector` class
provides a wrapper for `RestConnection` and `FileConnection` to simplify client
code that uses data from [OpenWeathermap.org](https://openweathermap.org).
### Instructions to use `OpenWeatherConnector`
1. Obtain an API key from OpenWeathermap.org by
[signing up](https://home.openweathermap.org/users/sign_up) for a free
account. This will automatically create an API key that you can copy from
your [account page](https://home.openweathermap.org/api_keys) (you can
create additional keys if you wish).
2. In the `src/main/resources/` directory, ***copy*** `apikeys.json-TEMPLATE` to
`apikeys.json` and then open `apikeys.json` for editing. In the line that has
```
"openweathermap": ""
```
insert your API key inside the blank quotes, for example:
```
"openweathermap": "0123456789abcdeffedcba9876543210"
```
- Do NOT *rename* `apikeys.json-TEMPLATE` as `apikeys.json`, or you will
cause `apikeys.json`, with your API key, to be present in your repository,
despite the `src/main/resources/.gitignore` entry that is meant to prevent
this.
- Do NOT place your API key in `apikeys.json-TEMPLATE`, or you will cause
your API key to be present in your repository.
3. When creating an `OpenWeatherConnector` object, if it should connect to the
local file system, then use the single-argument constructor, using "weather",
"onecall", "forecast", or "air_pollution" as the argument (or another
subdirectory of `src/main/resources/` that is named after one of the data
sets provided by the
[OpenWeathermap.org API](https://openweathermap.org/api)).
4. When creating an `OpenWeatherConnector` object, if it should connect to the
OpenWeathermap.org RESTful service, use the two-argument constructor. The
first argument needs to be one of the data sets provided by the
[OpenWeathermap.org API](https://openweathermap.org/api), and the second
argument needs to be the user's API key. Do not hard-code an API key.
Instead, Call `RestConnection.getApiKey("openweathermap")` to obtain the
API key from `apikeys.json` or prompt the user to enter their API key.
5. You *must* invoke an `OpenWeatherConnector` object's `retrieveData(String)`
method before invoking any of its other non-constructor methods to obtain
the data that the other methods will use. You *may* make additional calls to
`retrieveData(String)` to replace the data with other data.
- `retrieveData(String)`'s argument specifies the data to be used.
- If the `OpenWeatherConnector` instance is connected to the local file
system, then the argument is the name of the file to be loaded.
- If the `OpenWeatherConnector` instance is connected to OpenWeathermap.org
RESTful service, then the argument is the query string to be sent to
the RESTful service. See the
[OpenWeathermap.org API specification](https://openweathermap.org/api) for
details.
- [*"weather"*](https://openweathermap.org/current), the current weather
data set
- [*"onecall"*](https://openweathermap.org/api/one-call-api), the "One
Call API" data set
- [*forecast*](https://openweathermap.org/forecast5), the 5-day/3-hour
forecast data set
- [*air_pollution*](https://openweathermap.org/api/air-pollution), the air
quality and pollution data set
6. The remaining `OpenWeatherConnector` methods provide typed values for the
JSON fields in the data loaded by `retrieveData(String)`.
### Limitations
- `OpenWeatherConnector` only supports the free, no-cost APIs provided by
[OpenWeathermap.org](https://openweathermap.org). We do not have plans to
support the subscription-based APIs.
- As of this writing, `OpenWeatherConnector` only supports the *"weather"* data
set. We plan to add support for the *"onecall"*, *"forecast"*, and
*"air_pollution"* data sets.
- We may add support for the
[Geocoding API](https://openweathermap.org/api/geocoding-api) (*"direct"* and
*"reverse"* data sets).
- We do not anticipate supporting the
[Weather Stations](https://openweathermap.org/stations) API nor the
[Weather Triggers](https://openweathermap.org/triggers) API soon, as they are
not needed for our intended use of `OpenWeatherConnector`. (Also,
`RestConnection` does not (yet) support PUSH and PUT calls, which would be
required for those APIs.)
- I need to write some tests.
\ No newline at end of file
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>edu.unl.cse.soft160.rest_connector</groupId>
<artifactId>rest_and_file_connector</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>rest_connector</name>
<url>http://maven.apache.org</url>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>edu.unl.cse.soft160.rest_connector</groupId>
<artifactId>rest_and_file_connector</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>rest_connector</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
......@@ -22,6 +22,16 @@
<target>8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<show>public</show>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
......@@ -37,11 +47,5 @@
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
package edu.unl.cse.soft160.json_connections;
import edu.unl.cse.soft160.json_connections.connection.RestConnection;
import edu.unl.cse.soft160.json_connections.connector.OpenWeatherConnector;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Objects;
public class Main {
private static String getApiKey() {
JSONObject apiKeyJson = null;
String filename = "apikey.json";
try (InputStreamReader inputStreamReader = new InputStreamReader(Objects.requireNonNull(
Main.class.getClassLoader().getResourceAsStream(filename)))) {
apiKeyJson = (JSONObject)new JSONParser().parse(inputStreamReader);
} catch (NullPointerException nullPointerException) {
System.err.println("Could not find " + filename + ". Terminating.");
System.exit(1);
public static void main(String... args) {
String apiKey = null;
try {
apiKey = RestConnection.getApiKey("openweathermap");
} catch (IOException ioException) {
System.err.println("Error loading " + filename + ". Terminating.");
System.exit(1);
} catch (ParseException parseException) {
System.err.println("Parse exception encountered while parsing " + filename + ". Terminating.");
System.exit(1);
System.err.println("IO Exception: " + ioException.getClass());
System.err.println("\t" + ioException.getMessage());
System.err.println("Caused by: " + ioException.getCause());
System.err.println("\t" + ioException.getCause().getMessage());
}
return apiKeyJson.get("apikey").toString();
}
public static void main(String... args) {
String apiKey = getApiKey();
OpenWeatherConnector weather = new OpenWeatherConnector("weather", apiKey);
String data = null;
try {
......@@ -39,7 +24,7 @@ public class Main {
e.printStackTrace();
}
System.out.println(data);
System.out.println(weather.getWeatherCategory());
System.out.println(weather.getWeatherCategories());
System.out.println("Temperature: " + weather.getTemperature() + "K");
System.out.println("Cloud cover: " + weather.getCloudCover() + "%");
System.out.println("Timestamp: " + weather.getTimestamp());
......
......@@ -5,6 +5,18 @@ import org.json.simple.parser.ParseException;
import java.io.IOException;
/**
* Interface for connections to JSON data sources.
*/
public interface Connection {
JSONObject get(String filename) throws IOException, ParseException;
/**
* Retrieves data in JSON format.
*
* @param dataSpecification identifies the data to be retrieved; the nature of this String depends on the
* implementing class
* @return requested data
* @throws IOException if the data cannot be requested or delivered
* @throws ParseException if the data is not a properly-formatted JSON string
*/
JSONObject get(String dataSpecification) throws IOException, ParseException;
}
......@@ -9,17 +9,37 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Objects;
/**
* Connection to JSON files stored locally.
*/
public class FileConnection implements Connection {
String directory;
protected String directory;
/**
* Initiates connection to files at the top-level directory.
*/
public FileConnection() {
directory = "";
}
/**
* Initiates connection to files in a particular directory.
*
* @param directory location of files that will be accessed
*/
public FileConnection(String directory) {
this.directory = directory + "/";
}
/**
* Retrieves data from a JSON file.
*
* @param filename name of the file containing data
* @return requested data
* @throws IOException if the data cannot be requested or delivered; probably the most-common reason is that
* the requested file is not in the directory
* @throws ParseException if the data is not a properly-formatted JSON string
*/
public JSONObject get(String filename) throws IOException, ParseException {
JSONObject data;
try (InputStreamReader inputStreamReader = new InputStreamReader(Objects.requireNonNull(
......@@ -31,6 +51,13 @@ public class FileConnection implements Connection {
return data;
}
/**
* Saves data to a JSON file.
*
* @param filename name of the file that will hold the data
* @param data data to be saved
* @throws IOException if the data cannot be written to the file
*/
public void save(String filename, JSONObject data) throws IOException {
FileWriter fileWriter = new FileWriter(filename);
data.writeJSONString(fileWriter);
......
package edu.unl.cse.soft160.json_connections.connection;
import edu.unl.cse.soft160.json_connections.Main;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLConnection;
import java.util.Objects;
/**
* Connection to REST API.
*/
public class RestConnection implements Connection {
private static final String FILENAME = "apikeys.json";
/**
* Retrieves the specified API key from the {@code apikeys.json} file.
* @param apiKeyName name of the key to be retrieved
* @return the API key
* @throws IOException if no data stored in {@code apikeys.json} could be retrieved
*/
public static String getApiKey(String apiKeyName) throws IOException {
JSONObject apiKeyJson;
try (InputStreamReader inputStreamReader = new InputStreamReader(Objects.requireNonNull(
Main.class.getClassLoader().getResourceAsStream(FILENAME)))) {
apiKeyJson = (JSONObject)new JSONParser().parse(inputStreamReader);
} catch (NullPointerException nullPointerException) {
FileNotFoundException newException = new FileNotFoundException("File " + FILENAME + " not found.");
newException.initCause(nullPointerException);
throw newException;
} catch (ParseException parseException) {
throw new IOException("Error while parsing file " + FILENAME + ".", parseException);
}
if (!apiKeyJson.containsKey(apiKeyName)) {
System.err.println("WARNING! Could not locate API key named " + apiKeyName + " in file " + FILENAME + ".");
}
String apiKey = apiKeyJson.get(apiKeyName).toString();
if (apiKey.equals("")) {
System.err.println("WARNING! API key named " + apiKeyName + " in file " + FILENAME + " is blank.");
}
return apiKey;
}
protected final String protocol;
protected final String authority;
protected final String path;
protected final String apiKey;
/**
* Initializes connection to REST API service.
*
* @param protocol transfer protocol to be used, typically as "http"
* @param authority service provider, typically the server's fully-resolved name
* @param path filepath on the authority to the REST service
* @param apiKey token provided by service owner to authenticate requester and grant appropriate privileges
*/
public RestConnection(String protocol, String authority, String path, String apiKey) {
this.protocol = protocol;
this.authority = authority;
......@@ -25,6 +69,14 @@ public class RestConnection implements Connection {
this.apiKey = apiKey;
}
/**
* Retrieves data from REST service.
*
* @param query the REST query specifying the requested data
* @return requested data
* @throws IOException if the data cannot be requested or delivered, such as an incorrect URL or network problems
* @throws ParseException if the data is not a properly-formatted JSON string
*/
public JSONObject get(String query) throws IOException, ParseException {
JSONObject data;
try {
......@@ -34,7 +86,7 @@ public class RestConnection implements Connection {
path,
query + "&appid=" + apiKey,
null).toURL().openConnection();
// connection.setRequestProperty("Accept", "application/json");
connection.setRequestProperty("Accept", "application/json");
InputStreamReader inputStreamReader = new InputStreamReader(connection.getInputStream());
data = (JSONObject)new JSONParser().parse(inputStreamReader);
} catch (URISyntaxException | MalformedURLException originalException) {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment