Skip to content

Commit fbc4c2f

Browse files
author
Bradley
committed
initial backend with GET and POST routes
1 parent 6169dd5 commit fbc4c2f

File tree

7 files changed

+345
-53
lines changed

7 files changed

+345
-53
lines changed

backend/pom.xml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,36 @@
1414
<version>3.8.1</version>
1515
<scope>test</scope>
1616
</dependency>
17+
<dependency>
18+
<groupId>com.sparkjava</groupId>
19+
<artifactId>spark-core</artifactId>
20+
<version>2.6.0</version>
21+
</dependency>
22+
<dependency>
23+
<groupId>com.google.code.gson</groupId>
24+
<artifactId>gson</artifactId>
25+
<version>2.8.1</version>
26+
</dependency>
1727
</dependencies>
28+
<build>
29+
<plugins>
30+
<plugin>
31+
<groupId>org.apache.maven.plugins</groupId>
32+
<artifactId>maven-compiler-plugin</artifactId>
33+
<version>3.6.1</version>
34+
<configuration>
35+
<source>1.8</source>
36+
<target>1.8</target>
37+
</configuration>
38+
</plugin>
39+
<plugin>
40+
<groupId>org.codehaus.mojo</groupId>
41+
<artifactId>exec-maven-plugin</artifactId>
42+
<version>1.6.0</version>
43+
<configuration>
44+
<mainClass>merhoo.backend.App</mainClass>
45+
</configuration>
46+
</plugin>
47+
</plugins>
48+
</build>
1849
</project>
Lines changed: 72 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,85 @@
1-
package edu.lehigh.cse216.mfs409.admin;
1+
package merhoo.backend;
22

3-
import java.sql.Connection;
4-
import java.sql.DriverManager;
5-
import java.sql.SQLException;
6-
import java.util.Map;
3+
// Import the Spark package, so that we can make use of the "get" function to
4+
// create an HTTP GET route
5+
import spark.Spark;
6+
7+
// Import Google's JSON library
8+
import com.google.gson.*;
79

810
/**
9-
* App is our basic admin app. For now, all it does is connect to the database
10-
* and then disconnect
11+
* For now, our app creates an HTTP server that can only get and add data.
1112
*/
1213
public class App {
13-
/**
14-
* The main routine reads arguments from the environment and then uses those
15-
* arguments to connect to the database.
16-
*/
17-
public static void main(String[] argv) {
18-
// get the Postgres configuration from the environment
19-
Map<String, String> env = System.getenv();
20-
String ip = env.get("POSTGRES_IP");
21-
String port = env.get("POSTGRES_PORT");
22-
String user = env.get("POSTGRES_USER");
23-
String pass = env.get("POSTGRES_PASS");
14+
public static void main(String[] args) {
2415

25-
// Some students find that they need the following lines
26-
// *before DriverManager.getConnection* in order to get the postgres
27-
// driver to load
16+
// gson provides us with a way to turn JSON into objects, and objects
17+
// into JSON.
18+
//
19+
// NB: it must be final, so that it can be accessed from our lambdas
20+
//
21+
// NB: Gson is thread-safe. See
22+
// https://stackoverflow.com/questions/10380835/is-it-ok-to-use-gson-instance-as-a-static-field-in-a-model-bean-reuse
23+
final Gson gson = new Gson();
2824

29-
// try {
30-
// Class.forName("org.postgresql.Driver");
31-
// } catch (ClassNotFoundException cnfe) {
32-
// System.out.println("Unable to find postgresql driver");
33-
// return;
34-
// }
25+
// dataStore holds all of the data that has been provided via HTTP
26+
// requests
27+
//
28+
// NB: every time we shut down the server, we will lose all data, and
29+
// every time we start the server, we'll have an empty dataStore,
30+
// with IDs starting over from 0.
31+
final DataStore dataStore = new DataStore();
3532

36-
// conn is a connection to the database. In this simple example, it is
37-
// a local variable, though in a realistic program it might not be
38-
Connection conn = null;
33+
// GET route that returns all message titles and Ids. All we do is get
34+
// the data, embed it in a StructuredResponse, turn it into JSON, and
35+
// return it. If there's no data, we return "[]", so there's no need
36+
// for error handling.
37+
Spark.get("/messages", (request, response) -> {
38+
// ensure status 200 OK, with a MIME type of JSON
39+
response.status(200);
40+
response.type("application/json");
41+
return gson.toJson(new StructuredResponse("ok", null, dataStore.readAll()));
42+
});
3943

40-
// Connect to the database or fail
41-
System.out.print("Connecting to " + ip + ":" + port);
42-
try {
43-
// Open a connection, fail if we cannot get one
44-
conn = DriverManager.getConnection("jdbc:postgresql://" + ip + ":" + port + "/", user, pass);
45-
if (conn == null) {
46-
System.out.println("\n\tError: DriverManager.getConnection() returned a null object");
47-
return;
44+
// GET route that returns everything for a single row in the DataStore.
45+
// The ":id" suffix in the first parameter to get() becomes
46+
// request.params("id"), so that we can get the requested row ID. If
47+
// ":id" isn't a number, Spark will reply with a status 500 Internal
48+
// Server Error. Otherwise, we have an integer, and the only possible
49+
// error is that it doesn't correspond to a row with data.
50+
Spark.get("/messages/:id", (request, response) -> {
51+
int idx = Integer.parseInt(request.params("id"));
52+
// ensure status 200 OK, with a MIME type of JSON
53+
response.status(200);
54+
response.type("application/json");
55+
DataRow data = dataStore.readOne(idx);
56+
if (data == null) {
57+
return gson.toJson(new StructuredResponse("error", idx + " not found", null));
58+
} else {
59+
return gson.toJson(new StructuredResponse("ok", null, data));
4860
}
49-
} catch (SQLException e) {
50-
System.out.println("\n\tError: DriverManager.getConnection() threw a SQLException");
51-
e.printStackTrace();
52-
return;
53-
}
54-
System.out.println(" ... successfully connected");
61+
});
5562

56-
System.out.print("Disconnecting from database");
57-
try {
58-
conn.close();
59-
} catch (SQLException e) {
60-
System.out.println("\n\tError: close() threw a SQLException");
61-
e.printStackTrace();
62-
return;
63-
}
64-
System.out.println(" ... connection successfully closed");
63+
// POST route for adding a new element to the DataStore. This will read
64+
// JSON from the body of the request, turn it into a SimpleRequest
65+
// object, extract the title and message, insert them, and return the
66+
// ID of the newly created row.
67+
Spark.post("/messages", (request, response) -> {
68+
// NB: if gson.Json fails, Spark will reply with status 500 Internal
69+
// Server Error
70+
SimpleRequest req = gson.fromJson(request.body(), SimpleRequest.class);
71+
// ensure status 200 OK, with a MIME type of JSON
72+
// NB: even on error, we return 200, but with a JSON object that
73+
// describes the error.
74+
response.status(200);
75+
response.type("application/json");
76+
// NB: createEntry checks for null title and message
77+
int newId = dataStore.createEntry(req.mTitle, req.mMessage);
78+
if (newId == -1) {
79+
return gson.toJson(new StructuredResponse("error", "error performing insertion", null));
80+
} else {
81+
return gson.toJson(new StructuredResponse("ok", "" + newId, null));
82+
}
83+
});
6584
}
6685
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package merhoo.backend;
2+
3+
import java.util.Date;
4+
5+
/**
6+
* DataRow holds a row of information. A row of information consists of
7+
* an identifier, strings for a "title" and "content", and a creation date.
8+
*
9+
* Because we will ultimately be converting instances of this object into JSON
10+
* directly, we need to make the fields public. That being the case, we will
11+
* not bother with having getters and setters... instead, we will allow code to
12+
* interact with the fields directly.
13+
*/
14+
public class DataRow {
15+
/**
16+
* The unique identifier associated with this element. It's final, because
17+
* we never want to change it.
18+
*/
19+
public final int mId;
20+
21+
/**
22+
* The title for this row of data
23+
*/
24+
public String mTitle;
25+
26+
/**
27+
* The content for this row of data
28+
*/
29+
public String mContent;
30+
31+
/**
32+
* The creation date for this row of data. Once it is set, it cannot be
33+
* changed
34+
*/
35+
public final Date mCreated;
36+
37+
/**
38+
* Create a new DataRow with the provided id and title/content, and a
39+
* creation date based on the system clock at the time the constructor was
40+
* called
41+
*
42+
* @param id The id to associate with this row. Assumed to be unique
43+
* throughout the whole program.
44+
*
45+
* @param title The title string for this row of data
46+
*
47+
* @param content The content string for this row of data
48+
*/
49+
DataRow(int id, String title, String content) {
50+
mId = id;
51+
mTitle = title;
52+
mContent = content;
53+
mCreated = new Date();
54+
}
55+
56+
/**
57+
* Copy constructor to create one datarow from another
58+
*/
59+
DataRow(DataRow data) {
60+
mId = data.mId;
61+
// NB: Strings and Dates are immutable, so copy-by-reference is safe
62+
mTitle = data.mTitle;
63+
mContent = data.mContent;
64+
mCreated = data.mCreated;
65+
}
66+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package merhoo.backend;
2+
3+
/**
4+
* DataRowLite is for communicating back a subset of the information in a
5+
* DataRow. Specifically, we only send back the id and title. Note that
6+
* in order to keep the client code as consistent as possible, we ensure
7+
* that the field names in DataRowLite match the corresponding names in
8+
* DataRow. As with DataRow, we plan to convert DataRowLite objects to
9+
* JSON, so we need to make their fields public.
10+
*/
11+
public class DataRowLite {
12+
/**
13+
* The id for this row; see DataRow.mId
14+
*/
15+
public int mId;
16+
17+
/**
18+
* The title string for this row of data; see DataRow.mTitle
19+
*/
20+
public String mTitle;
21+
22+
/**
23+
* Create a DataRowLite by copying fields from a DataRow
24+
*/
25+
public DataRowLite(DataRow data) {
26+
this.mId = data.mId;
27+
this.mTitle = data.mTitle;
28+
}
29+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package merhoo.backend;
2+
3+
import java.util.ArrayList;
4+
5+
/**
6+
* DataStore provides access to a set of objects, and makes sure that each has
7+
* a unique identifier that remains unique even after the object is deleted.
8+
*
9+
* We follow the convention that member fields of a class have names that start
10+
* with a lowercase 'm' character, and are in camelCase.
11+
*
12+
* NB: The methods of DataStore are synchronized, since they will be used from a
13+
* web framework and there may be multiple concurrent accesses to the DataStore.
14+
*/
15+
public class DataStore {
16+
/**
17+
* The rows of data in our DataStore
18+
*/
19+
private ArrayList<DataRow> mRows;
20+
21+
/**
22+
* A counter for keeping track of the next ID to assign to a new row
23+
*/
24+
private int mCounter;
25+
26+
/**
27+
* Construct the DataStore by resetting its counter and creating the
28+
* ArrayList for the rows of data.
29+
*/
30+
DataStore() {
31+
mCounter = 0;
32+
mRows = new ArrayList<>();
33+
}
34+
35+
/**
36+
* Add a new row to the DataStore
37+
*
38+
* Note: we return -1 on an error. There are many good ways to handle an
39+
* error, to include throwing an exception. In robust code, returning -1
40+
* may not be the most appropriate technique, but it is sufficient for this
41+
* tutorial.
42+
*
43+
* @param title The title for this newly added row
44+
* @param content The content for this row
45+
* @return the ID of the new row, or -1 if no row was created
46+
*/
47+
public synchronized int createEntry(String title, String content) {
48+
if (title == null || content == null)
49+
return -1;
50+
// NB: we can safely assume that id is greater than the largest index in
51+
// mRows, and thus we can use the index-based add() method
52+
int id = mCounter++;
53+
DataRow data = new DataRow(id, title, content);
54+
mRows.add(id, data);
55+
return id;
56+
}
57+
58+
/**
59+
* Get one complete row from the DataStore using its ID to select it
60+
*
61+
* @param id The id of the row to select
62+
* @return A copy of the data in the row, if it exists, or null otherwise
63+
*/
64+
public synchronized DataRow readOne(int id) {
65+
if (id >= mRows.size())
66+
return null;
67+
DataRow data = mRows.get(id);
68+
if (data == null)
69+
return null;
70+
return new DataRow(data);
71+
}
72+
73+
/**
74+
* Get all of the ids and titles that are present in the DataStore
75+
* @return An ArrayList with all of the data
76+
*/
77+
public synchronized ArrayList<DataRowLite> readAll() {
78+
ArrayList<DataRowLite> data = new ArrayList<>();
79+
// NB: we copy the data, so that our ArrayList only has ids and titles
80+
for (DataRow row : mRows) {
81+
if (row != null)
82+
data.add(new DataRowLite(row));
83+
}
84+
return data;
85+
}
86+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package merhoo.backend;
2+
3+
/**
4+
* SimpleRequest provides a format for clients to present title and message
5+
* strings to the server.
6+
*
7+
* NB: since this will be created from JSON, all fields must be public, and we
8+
* do not need a constructor.
9+
*/
10+
public class SimpleRequest {
11+
/**
12+
* The title being provided by the client.
13+
*/
14+
public String mTitle;
15+
16+
/**
17+
* The message being provided by the client.
18+
*/
19+
public String mMessage;
20+
}

0 commit comments

Comments
 (0)