--- tags: parse,spring-boot,Fall22 --- # Shopping Cart (Spring/Parse) Most often data being sent or recieved via a webservice is stored in some type of database. For our example we will use Firebase as we have used MongoDB with Parse Server in the past. ## Creating your Spring Project [Spring](https://spring.io) is a Java based framework for building full stack web applications. This includes: frontend, backend, and webservices (SOAP & REST) among other features. We will be using Spring in different ways. Initially we will use all it's capabilities and in the second version look at using ReactJS frontend with Spring. Now let's create our project! In IntelliJ: 1. Select **New Project > Spring Intializr** ![Creating Spring Project](https://www.dropbox.com/s/az8c5na14i2p7qx/spring-set-up.PNG?dl=1) 2. Next, add the Spring dependecies. These are the libraries found in Spring that we will need. Select the following: - Developer Tools > Lombok - Web > Spring Web - Web > Rest Repositories - Web > Jersey ![Creating Spring Project](https://www.dropbox.com/s/qjznmjp9y2sq40a/spring-dependencies.PNG?dl=1) 3. Click **Finish** *If you do not have that option, create you project from the site [Spring Initializr](https://start.spring.io/)* ## Creating your database For this and future activities we will be using Parse. Parse is an open-source platform that interfaces with MongoDB. We will be accessing this via [Back4App](https://back4app.com). You will need to sign up for an account. When you have completed sign-up, go to "My Apps" and select "Build new app". Name your application, and it will create a new MongoDB database for you. It will then take your to the Parse Dashboard for your app. Here you can create your Classes (tables). ### Tables for this project **Product** |Name |Type |Example |---|---|---| |title |String |"A string" |desc |String |"A string" |price |Number |1 |img |String |"A string" |categories |Array |[1, "a string"] |color |Array |[1, "a string"] |size |Array |[1, "a string"] |inStock |Boolean |true **Cart** |Name |Type |Example |---|---|---| |userId |String |"A string" |products |Array |[1, "a string"] **Order** |Name |Type |Example |---|---|---| |userId |String |"A string" |amount |Number |1 |address |Object |{ "foo": "bar" } |status |String |"A string" |products |Array |[1, "a string"] ### Create dummy data Now that you have created your database, fill it in with dummy data. Your store can sell whatever products you would like. You should have at least 8 products, 1 user, 1 cart, and 1 order entry. ## Connecting to your database To connect to your database, you will need to get your Application ID and your REST API Key. We will be using both of these in the code to connect. These can be found under **App Settings>Sercurity & Keys** on your application's dashboard. ### Adding the dependency There is currently no officially supported Parse Java SDK; however, you can find a couple online. We will be using JAR file found [here](https://www.dropbox.com/s/hq03fczeb4c2pj0/parse4j.jar?dl=1). This is a modified version of one othe libaries. To add your dependency in IntelliJ, go to **File>Project Structure** ![Project Structure](https://www.dropbox.com/s/1kl5kk84d8r3sc8/projectStructure.png?dl=1) Then select **Libraries**. Next, click the plus button and select "Java". It will open a file browser. Browse to where you saved the JAR file, and select it. ***Please remember this location for future projects*** ![Libraries](https://www.dropbox.com/s/4x2ri61taqnf0qn/librariedAdd.png?dl=1) There are two other dependecies we need to use. We will retrieve them from the Maven repository. Edit your `pom.xml` file, and add to the dependencies to following: ```xml= <dependency> <groupId>io.github.cdimascio</groupId> <artifactId>dotenv-java</artifactId> <version>2.2.4</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.9.1</version> </dependency> ``` The first allows us to access our `env` environment files. The other is so we can convert JSON objects to Java Objects. ### Connecting at Program Start We will add code to method main that will allow the program to automatically connect to the Parse each time we start the application. First, create a new file `env` under you main project folder (ShoppingCart folder). **Be sure not to add this file to your repository.** In this file add your keys. ``` PARSE_APP_ID=YOUR_APP_ID PARSE_REST_ID=YOUR_REST_API_ID ``` Next we will add the following code to main above the existing line of code: ```java= Dotenv dotenv = Dotenv.configure().filename("env").load(); Parse.initialize(dotenv.get("PARSE_APP_ID"), dotenv.get("PARSE_REST_ID")); ``` ## Setting Up Project Strucutre Next we will create the required packages and classes. We will be following the Model-View-Controller (MVC) design pattern. [Here](https://www.geeksforgeeks.org/mvc-design-pattern/) is a brief explanation of the pattern. ### Models Create a new package, below your root package, called `models`. This package will contain our data models, all the classes for each of the tables. We will be creating three types of models: Genreal, Parse and Serializable models. Under the models folder we will first create our general models: ### CartItem ```java!= @Data @AllArgsConstructor @NoArgsConstructor public class CartItem { private Product product; private int quantity; private String color; private String size; public JSONObject getJSONObject(){ JSONObject obj = new JSONObject(); obj.append("product", product.getSerializable()); obj.append("quantity", quantity); obj.append("color", color); obj.append("size", size); return obj; } } ``` ### Address ```java!= @Data @AllArgsConstructor @NoArgsConstructor public class Address { String city; String country; String line1; String line2; String postal_code; String state; } ``` These will be uesed other classes below.Think of them as helper classes. Next we will create a package called `serializable` under `models`. This will hold the Serializable instances of our classes. We need these classes so that Spring can return data to us from the controllers. Because of the format of our Parse classes we need to make POJO (Plain Old Java Object) version of your classes as well. ### SerializbleProduct ```java= @Data //creates setters and getters automatically @AllArgsConstructor //creates constructor with all values automatically @NoArgsConstructor //creates no argument constructor automatically public class SerializableProduct { private String productId; private String title; private String desc; private String img; private ArrayList<String> categories; private ArrayList<String> color; private ArrayList<String> size; private double price; private boolean inStock; } ``` ### SeriablizbleOrder ```java= @Data //creates setters and getters automatically @AllArgsConstructor //creates constructor with all values automatically @NoArgsConstructor //creates no argument constructor automatically public class SerializableOrder { private String objectId; private double amount; private @Nullable ArrayList<CartItem> products; private @Nullable Address address; private String status; } ``` ### SerializableCart ```java= @Data @AllArgsConstructor @NoArgsConstructor public class SerializableCart { private String objectId; private ArrayList<CartItem> products; } ``` Finally we will create our Parse data models. These classes will look very differen. Under the models package create another called `parse`. In the Parse folder we will create a model for each of our 3 classes. The Parse classes must inherit from the ParseObject class which is part of the library we added to our project earlier. This makes querying the database much simplier. #### Product ```java= @ParseClassName("Product") public class Product extends ParseObject { private final String TITLE = "title"; private final String DESC = "desc"; private final String IMG = "img"; private final String CAT = "categories"; private final String COLOR = "color"; private final String SIZE = "size"; private final String PRICE = "price"; private final String INSTOCK = "inStock"; public String getTitle() { return getString(TITLE); } public void setTitle(String title) { put(TITLE ,title); } public String getDesc() { return getString(DESC); } public void setDesc(String desc) { put(DESC, desc); } public String getImg() { return getString(IMG); } public void setImg(String img) { put(IMG, img); } public ArrayList<String> getCategories() { return (ArrayList<String>) get(CAT); } public void setCategories(ArrayList<String> categories) { put(CAT, createJSONArray(categories)); } public ArrayList<String> getColor() { return (ArrayList<String>) get(COLOR); } public void setColor(ArrayList<String> color) { put(COLOR, createJSONArray(color)); } public ArrayList<String> getSize() { return (ArrayList<String>) get(SIZE); } public void setSize(ArrayList<String> size) { put(SIZE, createJSONArray(size)); } public double getPrice() { return getDouble(PRICE); } public void setPrice(double price) { put(PRICE, price); } public boolean getInStock() { return getBoolean(INSTOCK); } public void setInStock(boolean inStock) { put(INSTOCK, inStock); } private JSONArray createJSONArray(ArrayList<?> arr ) { JSONArray list = new JSONArray(); for(Object s : arr) list.put(s); return list; } public SerializableProduct getSerializable() { return new SerializableProduct( getObjectId(), getTitle(),getDesc(), getImg(), getCategories(),getColor(), getSize(), getPrice(), getInStock() ); } } ``` #### Order ```java!= @ParseClassName("Order") public class Order extends ParseObject { final static String AMOUNT = "amount"; final static String ADDRESS = "address"; final static String PRODUCTS = "products"; final static String STATUS = "status"; public double getAmount() { return getDouble(AMOUNT); } public void setAmount(double amount) { put(AMOUNT, amount); } public ArrayList<CartItem> getProducts() { return (ArrayList<CartItem>) get(PRODUCTS); } public void setProducts(ArrayList<CartItem> products) { JSONArray items = new JSONArray(); for(CartItem c : products) items.put(c.getJSONObject()); put(PRODUCTS, items); } public Address getAddress() { Gson gson = new Gson(); JsonElement jsonElement = gson.toJsonTree(get(ADDRESS)); return gson.fromJson(jsonElement, Address.class); } public void setAddress(Address add) { put(ADDRESS, add); } public String getStatus() { return getString(STATUS); } public void setStatus(String status) { put(STATUS, status); } public SerializableOrder getSerializable() { return new SerializableOrder(getObjectId(), getAmount(), getProducts(), getAddress(), getStatus()); } } ``` ### Cart ```java!= @ParseClassName("Cart") public class Cart extends ParseObject { final static String PRODUCTS = "products"; public ArrayList<CartItem> getProducts() { return (ArrayList<CartItem>) get(PRODUCTS); } public void setProducts(ArrayList<CartItem> products) { JSONArray items = new JSONArray(); for(CartItem c : products) items.put(c.getJSONObject()); put(PRODUCTS, items); } public SerializableCart getSerializable() { return new SerializableCart(getObjectId(), getProducts()); } } ``` ### Services As mentioned on the MVC explanation, this pattern does not include the business logic. For web-based applications the business logic is often referred to as a **service**. As such we will be write the logic of how data in handled in our service classes. These classes will contain the logic of retrieving and sending data between the application and Firebase, as well as any data manipulation required. Create a package under the root package named `services`. This package should be on the same level as models. We will create a service for each of models. Create 3 service classes `ProductService`, `OrderService`, and `CartService`. #### ProductService ```java= @Service //lets Spring know that this is a service public class ProductService { protected final Log logger = LogFactory.getLog(this.getClass()); //used to write to the console public ArrayList<Product> retrieveProducts() { logger.info(Parse.isIsRootMode()); final ArrayList<Product> products = new ArrayList<>(); ParseQuery<Product> query = ParseQuery.getQuery(Product.class); try { List<Product> list = query.find(); for (Product p : list) { //logger.info(p.toString()); //use if you want to see your products in the console products.add(p); } } catch(Exception e) { logger.error("Error occurred", e); } logger.info(products.size()); return products; } public Product getProductById(String id) { Product product = null; //defines the query for the product class ParseQuery<Product> query = ParseQuery.getQuery(Product.class); try{ product = query.get(id); //gets a single record based on objectId }catch (ParseException e) { e.printStackTrace(); } return product; } } ``` #### OrderService ```java= @Service public class OrderService { protected final Log logger = LogFactory.getLog(this.getClass()); public ArrayList<Order> retrieveOrders() { final ArrayList<Order> orders = new ArrayList<>(); ParseQuery<Order> query = ParseQuery.getQuery(Order.class); try { List<Order> list = query.find(); for (Order o : list) { orders.add(o); } } catch(Exception e) { logger.error("Error occurred", e); } logger.info(orders.size()); return orders; } } ``` #### CartService ```java= @Service public class CartService { protected final Log logger = LogFactory.getLog(this.getClass()); public ArrayList<Cart> retrieveCarts() { final ArrayList<Cart> carts = new ArrayList<>(); ParseQuery<Cart> query = ParseQuery.getQuery(Cart.class); try { List<Cart> list = query.find(); for (Cart c : list) { carts.add(c); } } catch(Exception e) { logger.error("Error occurred", e); } return carts; } } ``` ### Controllers Final step is to create our controllers. This is where we will put our endpoint definitions. This week we will ony be creating one endpoint in each controller; we will add more in the next lesson. Create a packagage called `controllers`. Create the following classes in that package `ProductController` , `OrderController` and `CartController`. #### ProductController ```java= @RestController //identified this class a controller used for REST API class. @RequestMapping("/api/v1/product") //sets up the base url for all calls to methods in this file public class ProductController { private ProductService productService; public ProductController(ProductService productService) { this.productService = productService; } //get all @GetMapping("/") //sets the path to this method public ArrayList<SerializableProduct> getProducts() { ArrayList<SerializableProduct> products = new ArrayList<>(); //Convert the Parse Product object to a POJO Product object that can be serialized by Spring ArrayList<Product> list = productService.retrieveProducts(); for(Product p : list) { products.add(p.getSerializable()); } return products; } //get only one based on object id @GetMapping("/find/{id}") public SerializableProduct getProductById(@PathVariable String id){ return productService.getProductById(id).getSerializable(); } } ``` #### OrderController ```java= @RestController @RequestMapping("/api/v1/order") public class OrderController { private OrderService orderService; public OrderController(OrderService orderService) { this.orderService = orderService; } @GetMapping("/") public ArrayList<SerializableOrder> getOrders() { ArrayList<SerializableOrder> list = new ArrayList<>(); ArrayList<Order> orders = orderService.retrieveOrders(); for(Order o : orders) list.add(o.getSerializable()); return list; } } ``` #### CartController ```java= @RestController @RequestMapping("/api/v1/cart") public class CartController { private CartService cartService; public CartController(CartService cartService) { this.cartService = cartService; } @GetMapping("/") public ArrayList<SerializableCart> getCarts() { ArrayList<SerializableCart> list = new ArrayList<>(); ArrayList<Cart> carts = cartService.retrieveCarts(); for(Cart c : carts) list.add(c.getSerializable()); return list; } } ``` ### Registering Your Custom Class Because we are extended the ParseObject class, we need to register our classes with Parse in our main application. In main, before the `initialize` statement, add the following code: ```java= ParseRegistry.registerSubclass(Product.class); ParseRegistry.registerSubclass(Order.class); ParseRegistry.registerSubclass(Cart.class); ``` **EVERY TIME YOU ADD A NEW PARSE SUB CLASS, YOU MUST REGISTER IT TO BE ABLE TO USE IT FOR QUERIES**