This is an assignment to the class Advanced Programming at the University of Applied Sciences Rosenheim.
In this assignment we will use Java annotations and reflection to interact with a remote REST (Representational State Transfer) API.
This assignment is split into two parts:
- De-/Serializable with GSON
- Remote REST calls with Retrofit.
As everyone (or maybe just me) loves Chuck Norris jokes we will implement a simple program to get random Chuck Norris jokes from the ICNDB (Internet Chuck Norris Database).
- Create a fork of this repository (button in the right upper corner)
- Clone the project (get the link by clicking the green Clone or download button)
- Import the project to your IDE (remember the guide in assignment 1)
- Validate your environment by running the tests from your IntelliJ and by running
gradle test
on the command line.
When we started to use Gradle we already talked about dependency management. In this assignment we will use Gradle to manage the required libraries.
To complete this assignment you will need the following libraries:
With Gradle, project dependencies (both at compile and runtime) are specified in the build.gradle
file, in the dependencies
section.
Open the existing build.gradle file and inspect the dependencies
object (Gradle uses Groovy, a language similar to Java and Javascript).
Every dependency has a scope where it will be available.
To use a library across the whole project, declare it with the scope implementation
.
As an example of dependencies:
dependencies {
implementation("org.apache.commons:commons-lang3:$commons_lang3_version")
implementation("org.apache.logging.log4j:log4j-api:${log4j2Version}")
implementation("org.apache.logging.log4j:log4j-core:${log4j2Version}")
// --> NEW DEPENDENCIES SHOULD GO HERE! FIND OUT WHICH!
testCompile("org.junit.jupiter:junit-jupiter-api:${junitVersion}")
testRuntime("org.junit.jupiter:junit-jupiter-engine:${junitVersion}")
testRuntime("org.junit.jupiter:junit-jupiter-params:${junitVersion}")
}
The dependencies for Retrofit and Gson you might find on the web sites!
Gradle is designed to help you in all development phases and is extensible by plugins.
In the given build.gradle
are three plugins already applied:
java
: brings Java support to Gradle e.g. compilation)application
: enable you to run and package the application you will develop in this assignmentidea
: helps with IntelliJ import
To run the main
method in the App
class without IntelliJ you can now use the following Gradle command on the command line:
gradle run
The hard part of this assignment is you need to combine three parts to form the whole program:
- Gson for serialization (to/from JSON)
- Retrofit for HTTP requests
It is strongly advised to read through the whole assignment and related documentations first; having the complete picture before starting with the parts helps a lot!
Google Gson is a library to serialize and deserialize JSON to or from Java objects.
- Use the
Joke
class in an try to serialize and deserialize it. Be aware that yourjoke
class is different to the JSON format below.
Therefore, you have to use annotations (@SerializedName) for mapping.
-
Write various tests for serialization and deserialization. Do not forget to test categories/rubrics
-
How would you write your own
toJson
andfromJson
using reflection and annotations.
The following code snippet shows the structure of a simple JSON object:
{
"id": 558,
"joke": "Ghosts are actually caused by Chuck Norris killing people faster than Death can process them.",
"categories": []
}
The most basic use case is to de/serialize objects; by default, GSON uses reflection to determine the properties.
class Joke {
int number;
String content;
String[] rubrics;
}
Try to get the following code in App.main
running:
Gson gson = new Gson();
// JSON String --> Object
Joke j = gson.fromJson("{\"id\": 10, \"joke\": \"Haha.\"}", Joke.class);
// categories remains `null`
// Object --> JSON String
String json = gson.toJson(j);
Gson makes use of annotations to map JSON keys to fields of your class. Have a look at the docs and complete the model described in the following UML:
Hint: the given JSON object describes the exact structure of the JSON objects we want to deserialize. Use anntations to help gson map JSON fields to differently named Java field names.
Here is what to do:
- Import Gson to your project (dependencies in your build.gradle)
- Familiarize yourself with Gson by trying a few examples
- Get familiar with the
@SerializedName
annotation - Get the code snippet in
App.main
running and try a fe serialization and deserelization things - Write a couple of tests to your mapping correct
What is your Json would look like this?
{ "type": "success",
"value": [
{ "id": 164,
"joke": "Chuck Norris once sued Burger King after they refused to put razor wire in his Whopper Jr, insisting that that actually is "his" way.",
"categories": [] },
{ "id": 275,
"joke": "Little Miss Muffet sat on her tuffet, until Chuck Norris roundhouse kicked her into a glacier.",
"categories": [] },
{ "id": 66,
"joke": "Chuck Norris can't finish a "color by numbers" because his markers are filled with the blood of his victims. Unfortunately, all blood is dark red.",
"categories": [] }
],
}
- Can you write a Java class to which GSON could serialize this?
- Make reuse of your
Joke
class
Next step, lets figure out the format of the ICNDB jokes. Open your browser and try the following URL:
http://api.icndb.com/jokes/random
As you could see, the actual response body of the ICNDB API looks like the following (your might be different content-wise since it is random!):
{
"type": "success",
"value": {
"id": 467,
"joke": "Chuck Norris can delete the Recycling Bin.",
"categories": [
"nerdy"
]
}
}
The actual joke (Joke
) is wrapped inside a response object which indicates if the request was successfull.
To be able to desirialze the joke we are going to make use of a class JokeResponse
which respects the format above. Thus, the JokeResponse
should look like:
- Implement the classes
JokeResponse
andJokesResponse
. - Test if deserialization would work.
Retrofit is a library to implement HTTP clients.
To create an HTTP client, create an interface containing some methods you will call later to perform HTTP requests.
Retrofit also uses annotations to conveniently map these methods to API resource paths, e.g. getJoke(488, "Bruce", "Wayne")
can be mapped to GET http://api.icndb.com/jokes/488?firstName=Bruce&lastName=Wayne
.
Read through the Retrofit documentation and implement the ICNDBApi
interface as shown in the following UML:
- Get the correct dependencies in your
build.gradle
- Start by implementing the method
getRandomJoke()
; use the appropriate annotations to decodate the interface method. - Modify the
main
method in theApp
class to create an instance of theICNDBApi
usingRetrofit.Builder
.
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://api.icndb.com")
.addConverterFactory(GsonConverterFactory.create())
.build();
- As you can see in the interface definition of ICNDBApi we are using either the
JokeResponse
or theJokesResponse
class directly. - To do so, we have to use the
GsonConverterFactroy
(as you can see in the code above!) - Print a random joke to
System.out
, and complete the test methodtestCollision
. Recall that you work withCall
objects that need to be executed before you can retrieve the response body. - After completing the
getRandomJoke()
method try to add the other methods. as it is described in the UML. - If you are not sure if your query strings are correct you can test them within the command line using
curl
or in a browser extension such as Postman.
Most unix systems will provide the cURL program:
curl -X GET "http://api.icndb.com/jokes/random" -H "accept: application/json"
On Windows, you can use the PowerShell to accomplish the same like so:
(Invoke-WebRequest
-Uri http://api.icndb.com/jokes/random
-Headers @{"accept"="application/json"}
).Content | ConvertFrom-Json | ConvertTo-Json
(The part | ConvertFrom-Json | ConvertTo-Json
is only necessary for formatting.)
Remark: to execute this command you have to remove the newlines!