implemented addRecipe method, and renamed my class to ServerUtils

This commit is contained in:
Mei Chang van der Werff 2025-11-27 15:57:43 +01:00
commit f8efed44c1
7 changed files with 228 additions and 184 deletions

View file

@ -1,118 +0,0 @@
package client;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import commons.Recipe;
import java.io.IOException;
import java.io.StringWriter;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;
public class DetailView {
private static final String BASE_URL = "http://localhost:8080/api";
private final HttpClient client;
private final ObjectMapper objectMapper = new ObjectMapper();
private final int statusOK = 200;
public DetailView() {
client = HttpClient.newHttpClient();
}
/**
* Gets all the recipes from the backend
* @return a JSON string with all the recipes
*/
public List<Recipe> findAll() throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create(BASE_URL+ "/recipes"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return objectMapper.readValue(response.body(), new TypeReference<List<Recipe>>() {
});// JSON string-> List<Recipe> (Jackson)
}
/**
* Gets a single recipe based on its id
* @param id every recipe has it's unique id
* @return a singe recipe with the given id
*/
public Recipe findId(long id) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create(BASE_URL+"/recipes/" + id))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if(response.statusCode() != statusOK){
System.out.println("No recipe found with this id");
return null;
}
return objectMapper.readValue(response.body(),Recipe.class);
}
/**
* Deletes a recipe
* @param id the recipe that get deleted
*/
public void deleteRecipe(long id) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.DELETE()
.uri(URI.create(BASE_URL + "/recipes/" + id))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if(response.statusCode() != statusOK){
System.out.println("No recipe to delete");
}
}
/**
* Clones a recipe
* @param id the id of the recipe to be cloned
* @return a duplicated recipe of the given recipe
*/
public Recipe cloneRecipe(long id) throws IOException, InterruptedException {
//Get the recipe
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create(BASE_URL + "/recipes/" + id))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
Recipe ogRecipe = objectMapper.readValue(response.body(),Recipe.class);
//200 is the status code for success, other codes can mean there is no recipe to clone
if(response.statusCode() != statusOK){
return null;
}
// recipe exists so you can make a "new" recipe aka the clone
Recipe clone = new Recipe();
String cloneName = ogRecipe.getName() + "(clone)"; // I suppose we want unique names?
clone.setName(cloneName);
clone.setIngredients(ogRecipe.getIngredients());
clone.setPreparationSteps(ogRecipe.getPreparationSteps());
//From String to Json
StringWriter s = new StringWriter();
objectMapper.writeValue(s, clone);
// Post the clone to backend
HttpRequest request2 = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofString(s.toString()))
.uri(URI.create(BASE_URL + "/recipes/"))
.build();
HttpResponse<String> cloneResponse = client.send(request2, HttpResponse.BodyHandlers.ofString());
return objectMapper.readValue(cloneResponse.body(),Recipe.class);
}
}

View file

@ -25,7 +25,7 @@ import com.google.inject.Injector;
import client.scenes.AddQuoteCtrl; import client.scenes.AddQuoteCtrl;
import client.scenes.MainCtrl; import client.scenes.MainCtrl;
import client.scenes.QuoteOverviewCtrl; import client.scenes.QuoteOverviewCtrl;
import client.utils.ServerUtils; import client.utils.ServerUtilsExample;
import javafx.application.Application; import javafx.application.Application;
import javafx.stage.Stage; import javafx.stage.Stage;
@ -41,7 +41,7 @@ public class Main extends Application {
@Override @Override
public void start(Stage primaryStage) throws Exception { public void start(Stage primaryStage) throws Exception {
var serverUtils = INJECTOR.getInstance(ServerUtils.class); var serverUtils = INJECTOR.getInstance(ServerUtilsExample.class);
if (!serverUtils.isServerAvailable()) { if (!serverUtils.isServerAvailable()) {
var msg = "Server needs to be started before the client, but it does not seem to be available. Shutting down."; var msg = "Server needs to be started before the client, but it does not seem to be available. Shutting down.";
System.err.println(msg); System.err.println(msg);

View file

@ -17,7 +17,7 @@ package client.scenes;
import com.google.inject.Inject; import com.google.inject.Inject;
import client.utils.ServerUtils; import client.utils.ServerUtilsExample;
import commons.Person; import commons.Person;
import commons.Quote; import commons.Quote;
import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.WebApplicationException;
@ -29,7 +29,7 @@ import javafx.stage.Modality;
public class AddQuoteCtrl { public class AddQuoteCtrl {
private final ServerUtils server; private final ServerUtilsExample server;
private final MainCtrl mainCtrl; private final MainCtrl mainCtrl;
@FXML @FXML
@ -42,7 +42,7 @@ public class AddQuoteCtrl {
private TextField quote; private TextField quote;
@Inject @Inject
public AddQuoteCtrl(ServerUtils server, MainCtrl mainCtrl) { public AddQuoteCtrl(ServerUtilsExample server, MainCtrl mainCtrl) {
this.mainCtrl = mainCtrl; this.mainCtrl = mainCtrl;
this.server = server; this.server = server;

View file

@ -20,7 +20,7 @@ import java.util.ResourceBundle;
import com.google.inject.Inject; import com.google.inject.Inject;
import client.utils.ServerUtils; import client.utils.ServerUtilsExample;
import commons.Quote; import commons.Quote;
import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
@ -32,7 +32,7 @@ import javafx.scene.control.TableView;
public class QuoteOverviewCtrl implements Initializable { public class QuoteOverviewCtrl implements Initializable {
private final ServerUtils server; private final ServerUtilsExample server;
private final MainCtrl mainCtrl; private final MainCtrl mainCtrl;
private ObservableList<Quote> data; private ObservableList<Quote> data;
@ -47,7 +47,7 @@ public class QuoteOverviewCtrl implements Initializable {
private TableColumn<Quote, String> colQuote; private TableColumn<Quote, String> colQuote;
@Inject @Inject
public QuoteOverviewCtrl(ServerUtils server, MainCtrl mainCtrl) { public QuoteOverviewCtrl(ServerUtilsExample server, MainCtrl mainCtrl) {
this.server = server; this.server = server;
this.mainCtrl = mainCtrl; this.mainCtrl = mainCtrl;
} }

View file

@ -1,64 +1,153 @@
/*
* Copyright 2021 Delft University of Technology
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package client.utils; package client.utils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import commons.Recipe;
import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.client.ClientBuilder;
import org.glassfish.jersey.client.ClientConfig;
import java.io.IOException;
import java.io.StringWriter;
import java.net.ConnectException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.List;
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ConnectException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import org.glassfish.jersey.client.ClientConfig;
import commons.Quote;
import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType;
public class ServerUtils { public class ServerUtils {
private static final String SERVER = "http://localhost:8080/api";
private final HttpClient client;
private final ObjectMapper objectMapper = new ObjectMapper();
private final int statusOK = 200;
private static final String SERVER = "http://localhost:8080/"; public ServerUtils() {
client = HttpClient.newHttpClient();
public void getQuotesTheHardWay() throws IOException, URISyntaxException {
var url = new URI("http://localhost:8080/api/quotes").toURL();
var is = url.openConnection().getInputStream();
var br = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} }
public List<Quote> getQuotes() { /**
return ClientBuilder.newClient(new ClientConfig()) // * Gets all the recipes from the backend
.target(SERVER).path("api/quotes") // * @return a JSON string with all the recipes
.request(APPLICATION_JSON) // */
.get(new GenericType<List<Quote>>() {}); public List<Recipe> getRecipes() throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create(SERVER + "/recipe"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return objectMapper.readValue(response.body(), new TypeReference<List<Recipe>>() {
});// JSON string-> List<Recipe> (Jackson)
} }
public Quote addQuote(Quote quote) { /**
return ClientBuilder.newClient(new ClientConfig()) // * Gets a single recipe based on its id
.target(SERVER).path("api/quotes") // * @param id every recipe has it's unique id
.request(APPLICATION_JSON) // * @return a singe recipe with the given id
.post(Entity.entity(quote, APPLICATION_JSON), Quote.class); */
public Recipe findId(long id) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create(SERVER +"/recipes/" + id))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if(response.statusCode() != statusOK){
System.out.println("No recipe found with this id");
return null;
}
return objectMapper.readValue(response.body(),Recipe.class);
}
// todo : make an add button
public Recipe addRecipe(Recipe newRecipe) throws IOException, InterruptedException {
StringWriter s = new StringWriter();
objectMapper.writeValue(s, newRecipe);
HttpRequest request = HttpRequest.newBuilder()
.PUT(HttpRequest.BodyPublishers.ofString(s.toString()))
.uri(URI.create(SERVER + "/recipe/new"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if(response.statusCode() != 200){
System.out.println("No recipe to add");
return null;
}
return objectMapper.readValue(response.body(),Recipe.class);
}
// public Quote addR(Quote quote) {
// return ClientBuilder.newClient(new ClientConfig()) //
// .target(SERVER).path("api/quotes") //
// .request(APPLICATION_JSON) //
// .post(Entity.entity(quote, APPLICATION_JSON), Quote.class);
// }
/**
* Deletes a recipe
* @param id the recipe that get deleted
*/
public void deleteRecipe(long id) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.DELETE()
.uri(URI.create(SERVER + "/recipe/" + id))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if(response.statusCode() != statusOK){
System.out.println("No recipe to delete");
}
}
/**
* Clones a recipe
* @param id the id of the recipe to be cloned
* @return a duplicated recipe of the given recipe
*/
public Recipe cloneRecipe(long id) throws IOException, InterruptedException {
//Get the recipe
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create(SERVER + "/recipe/" + id))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
//200 is the status code for success, other codes can mean there is no recipe to clone
if(response.statusCode() != statusOK){
return null;
}
// recipe exists so you can make a "new" recipe aka the clone
Recipe recipe = objectMapper.readValue(response.body(), Recipe.class);
// TODO : Make the names unique, this isn't unique. Cause when cloning again we get the same name
int version = 1;
while(version != 1){ // fix the condition!!!!!!!
version++;
}
String newName = recipe.getName() + "("+ version + ")"; // I suppose we want unique names?
recipe.setName(newName);
//From String to Json
StringWriter s = new StringWriter();
objectMapper.writeValue(s, recipe);
// Post the clone to backend
HttpRequest request2 = HttpRequest.newBuilder()
.PUT(HttpRequest.BodyPublishers.ofString(s.toString()))
.uri(URI.create(SERVER + "/recipe/new"))
.build();
HttpResponse<String> cloneResponse = client.send(request2, HttpResponse.BodyHandlers.ofString());
return objectMapper.readValue(cloneResponse.body(),Recipe.class);
} }
public boolean isServerAvailable() { public boolean isServerAvailable() {

View file

@ -0,0 +1,72 @@
/*
* Copyright 2021 Delft University of Technology
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package client.utils;
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
import java.net.ConnectException;
import java.util.List;
import jakarta.ws.rs.core.GenericType;
import org.glassfish.jersey.client.ClientConfig;
import commons.Quote;
import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.Entity;
public class ServerUtilsExample {
private static final String SERVER = "http://localhost:8080/";
// public void getQuotesTheHardWay() throws IOException, URISyntaxException {
// var url = new URI("http://localhost:8080/api/quotes").toURL();
// var is = url.openConnection().getInputStream();
// var br = new BufferedReader(new InputStreamReader(is));
// String line;
// while ((line = br.readLine()) != null) {
// System.out.println(line);
// }
// }
public List<Quote> getQuotes() {
return ClientBuilder.newClient(new ClientConfig()) //
.target(SERVER).path("api/quotes") //
.request(APPLICATION_JSON) //
.get(new GenericType<List<Quote>>() {});
}
public Quote addQuote(Quote quote) {
return ClientBuilder.newClient(new ClientConfig()) //
.target(SERVER).path("api/quotes") //
.request(APPLICATION_JSON) //
.post(Entity.entity(quote, APPLICATION_JSON), Quote.class);
}
public boolean isServerAvailable() {
try {
ClientBuilder.newClient(new ClientConfig()) //
.target(SERVER) //
.request(APPLICATION_JSON) //
.get();
} catch (ProcessingException e) {
if (e.getCause() instanceof ConnectException) {
return false;
}
}
return true;
}
}

View file

@ -1,5 +1,6 @@
package client; package client;
import client.utils.ServerUtils;
import commons.Recipe; import commons.Recipe;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -9,26 +10,26 @@ import java.util.List;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class DetailViewTest { class ServerUtilsTest {
static DetailView dv; static ServerUtils dv;
static Recipe testRecipe; static Recipe testRecipe;
@BeforeAll @BeforeAll
static void setup(){ static void setup() throws IOException, InterruptedException {
dv = new DetailView(); dv = new ServerUtils();
Recipe r = new Recipe(); Recipe r = new Recipe();
r.setName("Tosti"); r.setName("Tosti");
r.setIngredients(List.of("Bread", "Cheese", "Ham")); r.setIngredients(List.of("Bread", "Cheese", "Ham"));
r.setPreparationSteps(List.of("Step 1:", "Step 2")); r.setPreparationSteps(List.of("Step 1:", "Step 2"));
// testRecipe = dv.create(r); testRecipe = dv.addRecipe(r);
} }
@Test @Test
void findAll() throws IOException, InterruptedException { void findAllTest() throws IOException, InterruptedException {
List<Recipe> recipes = dv.findAll(); List<Recipe> recipes = dv.getRecipes();
assertNotNull(recipes, "The list should not be null"); assertNotNull(recipes, "The list should not be null");
assertTrue(recipes.size() >= 0, "The list should be 0 (when no recipes), or more"); assertTrue(recipes.size() >= 0, "The list should be 0 (when no recipes), or more");
@ -45,14 +46,14 @@ class DetailViewTest {
} }
@Test @Test
void deleteTest() throws IOException, InterruptedException { void deleteRecipeTest() throws IOException, InterruptedException {
dv.deleteRecipe(testRecipe.getId()); dv.deleteRecipe(testRecipe.getId());
assertNull(dv.findId(testRecipe.getId())); assertNull(dv.findId(testRecipe.getId()));
} }
@Test @Test
void cloneTest() throws IOException, InterruptedException { void cloneRecipeTest() throws IOException, InterruptedException {
Recipe clone = dv.cloneRecipe(testRecipe.getId()); Recipe clone = dv.cloneRecipe(testRecipe.getId());
assertNotEquals(clone.getId(), testRecipe.getId()); assertNotEquals(clone.getId(), testRecipe.getId());