Java 2

Week 12

6 Step Process

  1. STEP 1: Create/Update stored procedure

  2. STEP 2: Create/Update a POJO

  3. STEP 3: Create/Update a DAO

  4. STEP 4: Create/Update a Servlet

  5. STEP 5: Create/Update a JSP

  6. REPEAT

Database Setup

  1. Make sure your database is set up as shown on this slide.

  2. If not, drop the tables and re-create them.

DROP table customers;
DROP table orderitems;
DROP table orders;
DROP table products;
DROP table vendors;

Stored Procedure

  • Create a stored procedure to add a new vendor.
CREATE PROCEDURE sp_add_vendor_admin(
    IN p_vend_id      varchar(10),
    IN p_vend_name    varchar(50),
    IN p_vend_address varchar(50),
    IN p_vend_city    varchar(50),
    IN p_vend_state   varchar(5),
    IN p_vend_zip     varchar(10),
    IN p_vend_country varchar(50)
)
BEGIN
    INSERT INTO vendors (vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
    VALUES (p_vend_id, p_vend_name, p_vend_address, p_vend_city, p_vend_state, p_vend_zip, p_vend_country);
END;

POJO

  • Create Vendor and Address classes if you never did the Week 9 assignment
public class Vendor {
    private String vend_id;
    private String vend_name;
    private Address address;

    public Vendor() {
    }

    public Vendor(String vend_id, String vend_name, Address address) {
        this.vend_id = vend_id;
        this.vend_name = vend_name;
        this.address = address;
    }

    public String getVend_id() {
        return vend_id;
    }

    public void setVend_id(String vend_id) {
        this.vend_id = vend_id;
    }

    public String getVend_name() {
        return vend_name;
    }

    public void setVend_name(String vend_name) {
        this.vend_name = vend_name;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Vendor{" +
                "vend_id='" + vend_id + '\'' +
                ", vend_name='" + vend_name + '\'' +
                ", address=" + address +
                '}';
    }
}
public class Address {
    private String address;
    private String city;
    private String state;
    private String zip;
    private String country;

    public Address() {
    }

    public Address(String address, String city, String state, String zip, String country) {
        this.address = address;
        this.city = city;
        this.state = state;
        this.zip = zip;
        this.country = country;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getZip() {
        return zip;
    }

    public void setZip(String zip) {
        this.zip = zip;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    @Override
    public String toString() {
        return "Address{" +
                "address='" + address + '\'' +
                ", city='" + city + '\'' +
                ", state='" + state + '\'' +
                ", zip='" + zip + '\'' +
                ", country='" + country + '\'' +
                '}';
    }
}

DAO

  • Create the VendorDAO Class if you never did the Week 9 assignment.
  • Add a method to add a new Vendor record.
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;

public class VendorDAO {
    public static void main(String[] args) {
        Vendor vendor = new Vendor("0", "Test Vendor", new Address("123 Sample St", "Cedar Rapids", "IA", "55555", "USA"));
        addVendor(vendor);
    }

    // Assignment 9 method to get a List<Vendor>

    public static boolean addVendor(Vendor vendor) {
        try(Connection connection = getConnection()) {
            CallableStatement statement = connection.prepareCall("{CALL sp_add_vendor_admin(?, ?, ?, ?, ?, ?, ?)}");
            statement.setString(1, vendor.getVend_id());
            statement.setString(2, vendor.getVend_name());
            statement.setString(3, vendor.getAddress().getAddress());
            statement.setString(4, vendor.getAddress().getCity());
            statement.setString(5, vendor.getAddress().getState());
            statement.setString(6, vendor.getAddress().getZip());
            statement.setString(7, vendor.getAddress().getCountry());
            int rowsAffected = statement.executeUpdate();
            return rowsAffected == 1;
        } catch(SQLException e) {
            System.out.println(e.getMessage());
            return false;
        }
    }
}

Servlet

  • Create an AdminAddVendor servlet
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet("/add-vendor")
public class AdminAddVendor extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("WEB-INF/ecommerce/admin-add-vendor.jsp").forward(req, resp);
    }
}

JSP

  • Add a button to the admin-vendors.jsp from your Week 9 Assignment that takes the user to the add-vendor page.
<body>
<%@ include file="../../main-nav.jsp" %>
<div class="container py-4">
    <a href="add-vendor" class="btn btn-primary" role="button">Add New Vendor</a>
    <h2>Vendors</h2>

</div>
<%-- Your c:forEach code from the Week 9 Assignment --%>

JSP

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Add New Vendor</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>
<body>
<%@ include file="../../main-nav.jsp" %>
<div class="container py-4">
    <a href="vendors" class="btn btn-primary" role="button">View All Vendors</a>
    <h2>Add New Vendor</h2>
    <div class="alert alert-success" role="alert">
        A simple success alert—check it out!
    </div>
    <form class="row g-3">
        <div class="col-md-4">
            <label for="validationServer01" class="form-label">First name</label>
            <input type="text" class="form-control is-valid" id="validationServer01" value="Mark" required>
            <div class="valid-feedback">
                Looks good!
            </div>
        </div>
        <div class="col-md-4">
            <label for="validationServer02" class="form-label">Last name</label>
            <input type="text" class="form-control is-valid" id="validationServer02" value="Otto" required>
            <div class="valid-feedback">
                Looks good!
            </div>
        </div>
        <div class="col-md-4">
            <label for="validationServerUsername" class="form-label">Username</label>
            <div class="input-group has-validation">
                <span class="input-group-text" id="inputGroupPrepend3">@</span>
                <input type="text" class="form-control is-invalid" id="validationServerUsername" aria-describedby="inputGroupPrepend3 validationServerUsernameFeedback" required>
                <div id="validationServerUsernameFeedback" class="invalid-feedback">
                    Please choose a username.
                </div>
            </div>
        </div>
        <div class="col-md-6">
            <label for="validationServer03" class="form-label">City</label>
            <input type="text" class="form-control is-invalid" id="validationServer03" aria-describedby="validationServer03Feedback" required>
            <div id="validationServer03Feedback" class="invalid-feedback">
                Please provide a valid city.
            </div>
        </div>
        <div class="col-md-3">
            <label for="validationServer04" class="form-label">State</label>
            <select class="form-select is-invalid" id="validationServer04" aria-describedby="validationServer04Feedback" required>
                <option selected disabled value="">Choose...</option>
                <option>...</option>
            </select>
            <div id="validationServer04Feedback" class="invalid-feedback">
                Please select a valid state.
            </div>
        </div>
        <div class="col-md-3">
            <label for="validationServer05" class="form-label">Zip</label>
            <input type="text" class="form-control is-invalid" id="validationServer05" aria-describedby="validationServer05Feedback" required>
            <div id="validationServer05Feedback" class="invalid-feedback">
                Please provide a valid zip.
            </div>
        </div>
        <div class="col-12">
            <div class="form-check">
                <input class="form-check-input is-invalid" type="checkbox" value="" id="invalidCheck3" aria-describedby="invalidCheck3Feedback" required>
                <label class="form-check-label" for="invalidCheck3">
                    Agree to terms and conditions
                </label>
                <div id="invalidCheck3Feedback" class="invalid-feedback">
                    You must agree before submitting.
                </div>
            </div>
        </div>
        <div class="col-12">
            <button class="btn btn-primary" type="submit">Submit form</button>
        </div>
    </form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
        crossorigin="anonymous"></script>
</body>
</html>

JSP

  • Modify the form label text and for attributes.
  • Modify the input id attributes to match the label's for attribute.
  • I added country before address. I deleted the input-group/has-validation div tag. I deleted the span tag before the username/address input. I deleted the id attributes from the invalid-feedback divs. I removed the aria-describedby and required attributes. I changed state select menu to a text input. 
<div class="container py-4">
    <a href="vendors" class="btn btn-primary" role="button">View All Vendors</a>
    <h1>Add Vendors</h1>
    <div class="alert alert-success" role="alert">
        A simple success alert—check it out!
    </div>
    <form class="row g-3">
        <div class="col-md-4">
            <label for="vend_id" class="form-label">ID</label>
            <input type="text" class="form-control is-valid" id="vend_id" value="Mark">
            <div class="valid-feedback">
                Looks good!
            </div>
        </div>
        <div class="col-md-4">
            <label for="vend_name" class="form-label">Name</label>
            <input type="text" class="form-control is-valid" id="vend_name" value="Otto">
            <div class="valid-feedback">
                Looks good!
            </div>
        </div>
        <div class="col-md-4">
            <label for="country" class="form-label">Country Abbreviation</label>
            <input type="text" class="form-control is-invalid" id="country">
            <div class="invalid-feedback">
                Please choose a username.
            </div>
        </div>
        <div class="col-md-6">
            <label for="address" class="form-label">Street Address</label>
            <input type="text" class="form-control is-invalid" id="address">
            <div class="invalid-feedback">
                Please provide a valid city.
            </div>
        </div>
        <div class="col-md-3">
            <label for="city" class="form-label">City</label>
            <input type="text" class="form-control is-invalid" id="city">
            <div class="invalid-feedback">
                Please select a valid state.
            </div>
        </div>
        <div class="col-md-3">
            <label for="state" class="form-label">State Abbreviation</label>
            <input type="text" class="form-control is-invalid" id="state">
            <div class="invalid-feedback">
                Please provide a valid zip.
            </div>
        </div>
        <div class="col-md-3">
            <label for="zip" class="form-label">Zip</label>
            <input type="text" class="form-control is-invalid" id="zip">
            <div class="invalid-feedback">
                Please provide a valid zip.
            </div>
        </div>
        <div class="col-12">
            <button class="btn btn-primary" type="submit">Submit form</button>
        </div>
    </form>
</div>

JSP

  • Add a matching name attribute.
  • Modify the input value attributes to use EL tags ${name} that match the name attribute
  • Add method and action attributes to the form tag.
  • Change the Bootstrap column widths (md-3, md-4, etc.)
<div class="container py-4">
    <a href="vendors" class="btn btn-primary" role="button">View All Vendors</a>
    <h1>Add Vendors</h1>
    <div class="alert alert-success" role="alert">
        A simple success alert—check it out!
    </div>
    <%-- A Bootstrap row contains a grid of 12 columns --%>
    <form class="row g-3" method="POST" action="add-vendor">
        <%-- col-md-4 means the column will be 1/3 of the row's width  --%>
        <div class="col-md-4">
            <label for="vend_id" class="form-label">ID</label>
            <input type="text" class="form-control is-valid" id="vend_id" name="vend_id" value="${vend_id}">
            <div class="valid-feedback">
                Looks good!
            </div>
        </div>
        <%-- col-md-8 means the column will be 2/3 of the row's width  --%>
        <div class="col-md-8">
            <label for="vend_name" class="form-label">Name</label>
            <input type="text" class="form-control is-valid" id="vend_name" name="vend_name" value="${vend_name}">
            <div class="valid-feedback">
                Looks good!
            </div>
        </div>
        <%-- col-md-3 means the column will be 1/4 of the row's width  --%>
        <div class="col-md-3">
            <label for="country" class="form-label">Country Abbreviation</label>
            <input type="text" class="form-control is-invalid" id="country" name="country" value="${country}">
            <div class="invalid-feedback">
                Please choose a username.
            </div>
        </div>
        <%-- col-md-9 means the column will be 3/4 of the row's width  --%>
        <div class="col-md-9">
            <label for="address" class="form-label">Street Address</label>
            <input type="text" class="form-control is-invalid" id="address" name="address" value="${address}">
            <div class="invalid-feedback">
                Please provide a valid city.
            </div>
        </div>
        <div class="col-md-4">
            <label for="city" class="form-label">City</label>
            <input type="text" class="form-control is-invalid" id="city" name="city" value="${city}">
            <div class="invalid-feedback">
                Please select a valid state.
            </div>
        </div>
        <div class="col-md-4">
            <label for="state" class="form-label">State Abbreviation</label>
            <input type="text" class="form-control is-invalid" id="state" name="state" value="${state}">
            <div class="invalid-feedback">
                Please provide a valid zip.
            </div>
        </div>
        <div class="col-md-4">
            <label for="zip" class="form-label">Zip</label>
            <input type="text" class="form-control is-invalid" id="zip" name="zip" value="${zip}">
            <div class="invalid-feedback">
                Please provide a valid zip.
            </div>
        </div>
        <div class="col-12">
            <button class="btn btn-primary" type="submit">Submit form</button>
        </div>
    </form>
</div>

Servlet doPost

  • Add a doPost method to the AdminAddVendor class.
    • Get all parameters, set all attributes, forward to the JSP
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String vendorId = req.getParameter("vendorId");
    String vendorName = req.getParameter("vendorName");
    String country = req.getParameter("country");
    String streetAddress = req.getParameter("streetAddress");
    String city = req.getParameter("city");
    String state = req.getParameter("state");
    String zip = req.getParameter("zip");
    req.setAttribute("vendorId", vendorId);
    req.setAttribute("vendorName", vendorName);
    req.setAttribute("country", country);
    req.setAttribute("streetAddress", streetAddress);
    req.setAttribute("city", city);
    req.setAttribute("state", state);
    req.setAttribute("zip", zip);


    req.getRequestDispatcher("WEB-INF/ecommerce/admin-add-vendor.jsp").forward(req, resp);
}

JSP

  • Add <c:choose>, <c:when>, and <c:otherwise> tags around the "is-valid" and "is-invalid" classes,  as well as the "invalid-feedback" and "valid-feedback" classes. 
  • See the next slide for correct placement of these tags.
<c:choose><c:when test="${vendorIdError == true}">is-invalid</c:when><c:when test="${vendorIdError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>
<c:choose><c:when test="${vendorIdError == true}">invalid-feedback</c:when><c:when test="${vendorIdError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>

<c:choose><c:when test="${vendorNameError == true}">is-invalid</c:when><c:when test="${vendorNameError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>
<c:choose><c:when test="${vendorNameError == true}">invalid-feedback</c:when><c:when test="${vendorNameError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>

<c:choose><c:when test="${countryError == true}">is-invalid</c:when><c:when test="${countryError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>
<c:choose><c:when test="${countryError == true}">invalid-feedback</c:when><c:when test="${countryError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>

<c:choose><c:when test="${streetAddressError == true}">is-invalid</c:when><c:when test="${streetAddressError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>
<c:choose><c:when test="${streetAddressError == true}">invalid-feedback</c:when><c:when test="${streetAddressError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>

<c:choose><c:when test="${cityError == true}">is-invalid</c:when><c:when test="${cityError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>
<c:choose><c:when test="${cityError == true}">invalid-feedback</c:when><c:when test="${cityError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>

<c:choose><c:when test="${stateError == true}">is-invalid</c:when><c:when test="${stateError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>
<c:choose><c:when test="${stateError == true}">invalid-feedback</c:when><c:when test="${stateError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>

<c:choose><c:when test="${zipError == true}">is-invalid</c:when><c:when test="${zipError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>
<c:choose><c:when test="${zipError == true}">invalid-feedback</c:when><c:when test="${zipError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>
<div class="container py-4">
    <a href="vendors" class="btn btn-primary" role="button">View All Vendors</a>
    <h1>Add Vendors</h1>
    <div class="alert alert-success" role="alert">
        A simple success alert—check it out!
    </div>
    <%-- A Bootstrap row contains a grid of 12 columns --%>
    <form class="row g-3" method="POST" action="add-vendor">
        <%-- col-md-4 means the column will be 1/3 of the row's width  --%>
        <div class="col-md-4">
            <label for="vend_id" class="form-label">ID</label>
            <input type="text" class="form-control <c:choose><c:when test="${vendorIdError == true}">is-invalid</c:when><c:when test="${vendorIdError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>" id="vend_id" name="vend_id" value="${vend_id}">
            <div class="<c:choose><c:when test="${vendorIdError == true}">invalid-feedback</c:when><c:when test="${vendorIdError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>">
                Looks good!
            </div>
        </div>
        <%-- col-md-8 means the column will be 2/3 of the row's width  --%>
        <div class="col-md-8">
            <label for="vend_name" class="form-label">Name</label>
            <input type="text" class="form-control <c:choose><c:when test="${vendorNameError == true}">is-invalid</c:when><c:when test="${vendorNameError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>" id="vend_name" name="vend_name" value="${vend_name}">
            <div class="<c:choose><c:when test="${vendorNameError == true}">invalid-feedback</c:when><c:when test="${vendorNameError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>">
                Looks good!
            </div>
        </div>
        <%-- col-md-3 means the column will be 1/4 of the row's width  --%>
        <div class="col-md-3">
            <label for="country" class="form-label">Country Abbreviation</label>
            <input type="text" class="form-control <c:choose><c:when test="${countryError == true}">is-invalid</c:when><c:when test="${countryError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>" id="country" name="country" value="${country}">
            <div class="<c:choose><c:when test="${countryError == true}">invalid-feedback</c:when><c:when test="${countryError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>">
                Please choose a username.
            </div>
        </div>
        <%-- col-md-9 means the column will be 3/4 of the row's width  --%>
        <div class="col-md-9">
            <label for="address" class="form-label">Street Address</label>
            <input type="text" class="form-control <c:choose><c:when test="${addressError == true}">is-invalid</c:when><c:when test="${addressError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>" id="address" name="address" value="${address}">
            <div class="<c:choose><c:when test="${addressError == true}">invalid-feedback</c:when><c:when test="${addressError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>">
                Please provide a valid city.
            </div>
        </div>
        <div class="col-md-4">
            <label for="city" class="form-label">City</label>
            <input type="text" class="form-control <c:choose><c:when test="${cityError == true}">is-invalid</c:when><c:when test="${cityError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>" id="city" name="city" value="${city}">
            <div class="<c:choose><c:when test="${cityError == true}">invalid-feedback</c:when><c:when test="${cityError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>">
                Please select a valid state.
            </div>
        </div>
        <div class="col-md-4">
            <label for="state" class="form-label">State Abbreviation</label>
            <input type="text" class="form-control <c:choose><c:when test="${stateError == true}">is-invalid</c:when><c:when test="${stateError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>" id="state" name="state" value="${state}">
            <div class="<c:choose><c:when test="${stateError == true}">invalid-feedback</c:when><c:when test="${stateError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>">
                Please provide a valid zip.
            </div>
        </div>
        <div class="col-md-4">
            <label for="zip" class="form-label">Zip</label>
            <input type="text" class="form-control <c:choose><c:when test="${zipError == true}">is-invalid</c:when><c:when test="${zipError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>" id="zip" name="zip" value="${zip}">
            <div class="<c:choose><c:when test="${zipError == true}">invalid-feedback</c:when><c:when test="${zipError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>">
                Please provide a valid zip.
            </div>
        </div>
        <div class="col-12">
            <button class="btn btn-primary" type="submit">Submit form</button>
        </div>
    </form>
</div>

JSP

  • Add <c:if>, <c:choose>, <c:when>, and <c:otherwise> tags around the alert box.
  • Add message placeholders between the div tags.
<div class="container py-4">
    <a href="vendors" class="btn btn-primary" role="button">View All Vendors</a>
    <h1>Add Vendors</h1>
    <div class="alert <c:choose><c:when test="${vendorAdded == true}">alert-success</c:when><c:when test="${vendorAdded == false}">alert-danger</c:when><c:otherwise></c:otherwise></c:choose>" role="alert">
        ${vendorAddedMessage}
    </div>
    <%-- A Bootstrap row contains a grid of 12 columns --%>
    <form class="row g-3" method="POST" action="add-vendor">
        <%-- col-md-4 means the column will be 1/3 of the row's width  --%>
        <div class="col-md-4">
            <label for="vend_id" class="form-label">ID</label>
            <input type="text" class="form-control <c:choose><c:when test="${vendorIdError == true}">is-invalid</c:when><c:when test="${vendorIdError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>" id="vend_id" name="vend_id" value="${vend_id}">
            <div class="<c:choose><c:when test="${vendorIdError == true}">invalid-feedback</c:when><c:when test="${vendorIdError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>">
                ${vendorIdMessage}
            </div>
        </div>
        <%-- col-md-8 means the column will be 2/3 of the row's width  --%>
        <div class="col-md-8">
            <label for="vend_name" class="form-label">Name</label>
            <input type="text" class="form-control <c:choose><c:when test="${vendorNameError == true}">is-invalid</c:when><c:when test="${vendorNameError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>" id="vend_name" name="vend_name" value="${vend_name}">
            <div class="<c:choose><c:when test="${vendorNameError == true}">invalid-feedback</c:when><c:when test="${vendorNameError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>">
                ${vendorNameMessage}
            </div>
        </div>
        <%-- col-md-3 means the column will be 1/4 of the row's width  --%>
        <div class="col-md-3">
            <label for="country" class="form-label">Country Abbreviation</label>
            <input type="text" class="form-control <c:choose><c:when test="${countryError == true}">is-invalid</c:when><c:when test="${countryError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>" id="country" name="country" value="${country}">
            <div class="<c:choose><c:when test="${countryError == true}">invalid-feedback</c:when><c:when test="${countryError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>">
                ${countryMessage}
            </div>
        </div>
        <%-- col-md-9 means the column will be 3/4 of the row's width  --%>
        <div class="col-md-9">
            <label for="address" class="form-label">Street Address</label>
            <input type="text" class="form-control <c:choose><c:when test="${addressError == true}">is-invalid</c:when><c:when test="${addressError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>" id="address" name="address" value="${address}">
            <div class="<c:choose><c:when test="${addressError == true}">invalid-feedback</c:when><c:when test="${addressError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>">
                ${addressMessage}
            </div>
        </div>
        <div class="col-md-4">
            <label for="city" class="form-label">City</label>
            <input type="text" class="form-control <c:choose><c:when test="${cityError == true}">is-invalid</c:when><c:when test="${cityError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>" id="city" name="city" value="${city}">
            <div class="<c:choose><c:when test="${cityError == true}">invalid-feedback</c:when><c:when test="${cityError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>">
                ${cityMessage}
            </div>
        </div>
        <div class="col-md-4">
            <label for="state" class="form-label">State Abbreviation</label>
            <input type="text" class="form-control <c:choose><c:when test="${stateError == true}">is-invalid</c:when><c:when test="${stateError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>" id="state" name="state" value="${state}">
            <div class="<c:choose><c:when test="${stateError == true}">invalid-feedback</c:when><c:when test="${stateError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>">
                ${stateMessage}
            </div>
        </div>
        <div class="col-md-4">
            <label for="zip" class="form-label">Zip</label>
            <input type="text" class="form-control <c:choose><c:when test="${zipError == true}">is-invalid</c:when><c:when test="${zipError == false}">is-valid</c:when><c:otherwise></c:otherwise></c:choose>" id="zip" name="zip" value="${zip}">
            <div class="<c:choose><c:when test="${zipError == true}">invalid-feedback</c:when><c:when test="${zipError == false}">valid-feedback</c:when><c:otherwise></c:otherwise></c:choose>">
                ${zipMessage}
            </div>
        </div>
        <div class="col-12">
            <button class="btn btn-primary" type="submit">Submit form</button>
        </div>
    </form>
</div>

Validators

// Source: OWASP: https://owasp.org/www-community/OWASP_Validation_Regex_Repository
public static boolean isValidState(String state) {
    String regex = "^(AL|AK|AZ|AR|CA|CO|CT|DE|DC|FL|GA|HI|ID|IL|IN|IA|KS|KY|LA|ME|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|OH|OK|OR|PA|RI|SC|TN|TX|UT|VT|VA|WA|WV|WI|WY)$";
    Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
    Matcher matcher = pattern.matcher(state);
    return matcher.matches();
}
// Source: OWASP: https://owasp.org/www-community/OWASP_Validation_Regex_Repository
public static boolean isValidZip(String zip) {
    String regex = "^\\d{5}(-\\d{4})?$";
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(zip);
    return matcher.matches();
}
// Source: https://stackoverflow.com/questions/22214408/regular-expressions-for-country-name/35178090#35178090
public static boolean isValidCountry(String country) {
    String regex = "^(AF|AX|AL|DZ|AS|AD|AO|AI|AQ|AG|AR|AM|AW|AU|AT|AZ|BS|BH|BD|BB|BY|BE|BZ|BJ|BM|BT|BO|BQ|BA|BW|BV|BR|IO|BN|BG|BF|BI|KH|CM|CA|CV|KY|CF|TD|CL|CN|CX|CC|CO|KM|CG|CD|CK|CR|CI|HR|CU|CW|CY|CZ|DK|DJ|DM|DO|EC|EG|SV|GQ|ER|EE|ET|FK|FO|FJ|FI|FR|GF|PF|TF|GA|GM|GE|DE|GH|GI|GR|GL|GD|GP|GU|GT|GG|GN|GW|GY|HT|HM|VA|HN|HK|HU|IS|IN|ID|IR|IQ|IE|IM|IL|IT|JM|JP|JE|JO|KZ|KE|KI|KP|KR|KW|KG|LA|LV|LB|LS|LR|LY|LI|LT|LU|MO|MK|MG|MW|MY|MV|ML|MT|MH|MQ|MR|MU|YT|MX|FM|MD|MC|MN|ME|MS|MA|MZ|MM|NA|NR|NP|NL|NC|NZ|NI|NE|NG|NU|NF|MP|NO|OM|PK|PW|PS|PA|PG|PY|PE|PH|PN|PL|PT|PR|QA|RE|RO|RU|RW|BL|SH|KN|LC|MF|PM|VC|WS|SM|ST|SA|SN|RS|SC|SL|SG|SX|SK|SI|SB|SO|ZA|GS|SS|ES|LK|SD|SR|SJ|SZ|SE|CH|SY|TW|TJ|TZ|TH|TL|TG|TK|TO|TT|TN|TR|TM|TC|TV|UG|UA|AE|GB|US|UM|UY|UZ|VU|VE|VN|VG|VI|WF|EH|YE|ZM|ZW|AFG|ALB|DZA|ASM|AND|AGO|AIA|ATA|ATG|ARG|ARM|ABW|AUS|AUT|AZE|BHS|BHR|BGD|BRB|BLR|BEL|BLZ|BEN|BMU|BTN|BOL|BIH|BWA|BVT|BRA|IOT|VGB|BRN|BGR|BFA|BDI|KHM|CMR|CAN|CPV|CYM|CAF|TCD|CHL|CHN|CXR|CCK|COL|COM|COD|COG|COK|CRI|CIV|CUB|CYP|CZE|DNK|DJI|DMA|DOM|ECU|EGY|SLV|GNQ|ERI|EST|ETH|FRO|FLK|FJI|FIN|FRA|GUF|PYF|ATF|GAB|GMB|GEO|DEU|GHA|GIB|GRC|GRL|GRD|GLP|GUM|GTM|GIN|GNB|GUY|HTI|HMD|VAT|HND|HKG|HRV|HUN|ISL|IND|IDN|IRN|IRQ|IRL|ISR|ITA|JAM|JPN|JOR|KAZ|KEN|KIR|PRK|KOR|KWT|KGZ|LAO|LVA|LBN|LSO|LBR|LBY|LIE|LTU|LUX|MAC|MKD|MDG|MWI|MYS|MDV|MLI|MLT|MHL|MTQ|MRT|MUS|MYT|MEX|FSM|MDA|MCO|MNG|MSR|MAR|MOZ|MMR|NAM|NRU|NPL|ANT|NLD|NCL|NZL|NIC|NER|NGA|NIU|NFK|MNP|NOR|OMN|PAK|PLW|PSE|PAN|PNG|PRY|PER|PHL|PCN|POL|PRT|PRI|QAT|REU|ROU|RUS|RWA|SHN|KNA|LCA|SPM|VCT|WSM|SMR|STP|SAU|SEN|SCG|SYC|SLE|SGP|SVK|SVN|SLB|SOM|ZAF|SGS|ESP|LKA|SDN|SUR|SJM|SWZ|SWE|CHE|SYR|TWN|TJK|TZA|THA|TLS|TGO|TKL|TON|TTO|TUN|TUR|TKM|TCA|TUV|VIR|UGA|UKR|ARE|GBR|UMI|USA|URY|UZB|VUT|VEN|VNM|WLF|ESH|YEM|ZMB|ZWE)$";
    Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
    Matcher matcher = pattern.matcher(country);
    return matcher.matches();
}
  • Add maxlength attributes to the state and country text inputs to allow no more than 2 and 3 characters. Modify the prompt to specify the format (i.e. State (two-letter abbreviation))

Vendor and Address POJOs

  • Add code to the setter methods to validate input. 
  • Only validate the state if the country is United States
public void setVend_id(String vend_id) {
    // If set, capitalize the vendor ID for consistency
    if (vend_id != null) {
        vend_id = vend_id.toUpperCase();
    }
    // Requires any text
    if(vend_id == null || vend_id.strip().length() == 0) {
        throw new IllegalArgumentException("Vendor ID is required");
    }
    this.vend_id = vend_id;
}

public void setVend_name(String vend_name) {
    // If set, capitalize the first letter for consistency
    if (vend_id != null) {
        vend_name = vend_name.substring(0,1).toUpperCase() + vend_name.substring(1);
    }
    // Requires any text
    if(vend_name == null || vend_name.strip().length() == 0) {
        throw new IllegalArgumentException("Name is required");
    }
    this.vend_name = vend_name;
}

public void setAddress(Address address) {
    if(address == null) {
        throw new IllegalArgumentException("Vendor Address is required");
    }
    this.address = address;
}
public void setAddress(String address) {
    // Requires any text
    if(address == null || address.strip().length() == 0) {
        throw new IllegalArgumentException("Street address is required");
    }
    this.address = address;
}

public void setCity(String city) {
    // Requires any text
    if(city == null || city.strip().length() == 0) {
        throw new IllegalArgumentException("City is required");
    }
    this.city = city;
}

public void setState(String state) {
    // If set, capitalize the state for consistency
    if (state != null) {
        state = state.toUpperCase();
    }
    // Requires valid state only if the country is the United States
    if(isUnitedStates()) {
        if (state == null || !Validators.isValidState(state)) {
            throw new IllegalArgumentException("State is not valid");
        }
    }
    this.state = state;
}

public boolean isUnitedStates() {
    return country != null && (country.equals("US") || country.equals("USA"));
}

public void setZip(String zip) {
    // Requires valid zip
    if(zip == null || !Validators.isValidZip(zip)) {
        throw new IllegalArgumentException("Zip is not valid");
    }
    this.zip = zip;
}

public void setCountry(String country) {
    // If set, capitalize the country for consistency
    if (country != null) {
        country = country.toUpperCase();
    }
    // Requires valid country
    if(country == null || !Validators.isValidCountry(country)) {
        throw new IllegalArgumentException("Country is not valid");
    }
    this.country = country;
}

Servlet

  • Instantiate a default object.
  • Validate the inputs one at a time by calling setter methods. Set error attributes and message attributes.
Vendor vendor = new Vendor();
boolean validationError = false;

try {
    vendor.setVend_id(vendorId);
    req.setAttribute("vendorIdError", false);
    req.setAttribute("vendorIdMessage", "Looks good!");
} catch (IllegalArgumentException e) {
    validationError = true;
    req.setAttribute("vendorIdError", true);
    req.setAttribute("vendorIdMessage", e.getMessage());
}

req.getRequestDispatcher("WEB-INF/ecommerce/admin-add-vendor.jsp").forward(req, resp);
  • When you run the program, the form should not be red or green. If you submit with no input, the Vendor ID field will turn red and display the error message. If you type anything, it will turn green and say "Looks good!".

Servlet

  • Validate the remaining inputs by calling setter methods. Set error attributes and message attributes.
  • Validate the country before the state. Only require the state if the country code is not null and equal to US or USA.
try {
    vendor.setVend_name(vend_name);
    req.setAttribute("vendorNameError", false);
    req.setAttribute("vendorNameMessage", "Looks good!");
} catch(IllegalArgumentException e) {
    validationError = true;
    req.setAttribute("vendorNameError", true);
    req.setAttribute("vendorNameMessage", e.getMessage());
}

Address address = new Address();

try {
    address.setCountry(country);
    req.setAttribute("countryError", false);
    req.setAttribute("countryMessage", "Looks good!");
} catch(IllegalArgumentException e) {
    validationError = true;
    req.setAttribute("countryError", true);
    req.setAttribute("countryMessage", e.getMessage());
}

try {
    address.setAddress(streetAddress);
    req.setAttribute("addressError", false);
    req.setAttribute("addressMessage", "Looks good!");
} catch(IllegalArgumentException e) {
    validationError = true;
    req.setAttribute("addressError", true);
    req.setAttribute("addressMessage", e.getMessage());
}

try {
    address.setZip(zip);
    // Validate the country first
    if(!address.getCountry().isEmpty()) {
        req.setAttribute("zipError", false);
        req.setAttribute("zipMessage", "Looks good!");
    }
} catch(IllegalArgumentException e) {
    validationError = true;
    req.setAttribute("zipError", true);
    req.setAttribute("zipMessage", e.getMessage());
}

try {
    address.setCity(city);
    // Validate the country first
    if(!address.getCountry().isEmpty()) {
        req.setAttribute("cityError", false);
        req.setAttribute("cityMessage", "Looks good!");
    }
} catch(IllegalArgumentException e) {
    validationError = true;
    req.setAttribute("cityError", true);
    req.setAttribute("cityMessage", e.getMessage());
}

try {
    address.setState(state);
    // Validate the country first
    if(address.isUnitedStates()) {
        req.setAttribute("stateError", false);
        req.setAttribute("stateMessage", "Looks good!");
    }
} catch(IllegalArgumentException e) {
    validationError = true;
    req.setAttribute("stateError", true);
    req.setAttribute("stateMessage", e.getMessage());
}

vendor.setAddress(address);

Servlet

  • If no errors exist, pass the object to the DAO. 
  • If the data was inserted successfully, set an attribute to display in the alert box.
	// code omitted
    
	if(!validationError) {
        boolean vendorAdded = VendorDAO.addVendor(vendor);
        req.setAttribute("vendorAdded", vendorAdded);
        if(vendorAdded) {
            req.setAttribute("vendorAddedMessage", "Successfully added vendor!");
        } else {
            req.setAttribute("vendorAddedMessage", "Error adding vendor.");
        }
    }

    req.getRequestDispatcher("WEB-INF/ecommerce/admin-add-vendor.jsp").forward(req, resp);
}
  • Adding valid data should display a green alert saying "Successfully added vendor! ". 
  • Submitting the form a second time with the same data should display a red alert saying "Error adding vendor." because the vendor id cannot be duplicated.

Stored Procedure

  • Create a stored procedure to get a vendor by its ID.
CREATE PROCEDURE sp_get_vendor_by_id(IN p_vend_id varchar(10))
BEGIN
    SELECT vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country
    FROM vendors
    WHERE vend_id = p_vend_id;
END;

POJO

  • Edit the setVend_id method to convert the vend_id string to uppercase if it exists.
    public void setVend_id(String vend_id) {
        if(vend_id != null) {
            vend_id = vend_id.trim().toUpperCase();
        }
        if(vend_id == null || vend_id.strip().length() == 0) {
            throw new IllegalArgumentException("Vendor ID is required");
        }
        this.vend_id = vend_id;
    }

DAO

  • Add a method to the VendorDAO class to get a single Vendor by it's vend_id.
    public static Vendor getVendor(String vend_id) {
        Vendor vendor = null;
        if(vend_id != null) {
            vend_id = vend_id.trim();
            try (Connection connection = getConnection()) {
                CallableStatement statement = connection.prepareCall("{CALL sp_get_vendor_by_id(?)}");
                statement.setString(1, vend_id);
                ResultSet resultSet = statement.executeQuery();
                // Use an if statement instead of a while loop when the SELECT query returns one record
                if (resultSet.next()) {
                    String vend_name = resultSet.getString("vend_name");
                    String vend_address = resultSet.getString("vend_address");
                    String vend_city = resultSet.getString("vend_city");
                    String vend_state = resultSet.getString("vend_state");
                    String vend_zip = resultSet.getString("vend_zip");
                    String vend_country = resultSet.getString("vend_country");
                    vendor = new Vendor(vend_id, vend_name, new Address(vend_address, vend_city, vend_state, vend_zip, vend_country));
                }
            } catch (SQLException e) {
//            System.out.println(e.getMessage()); // Uncomment in case null is always being returned
            }
        }
        return vendor;
    }

DAO

  • Add a method to the VendorDAO class to get a single Vendor by it's vend_id.
    public static Vendor getVendor(String vend_id) {
        Vendor vendor = null;
        if(vend_id != null) {
            vend_id = vend_id.trim();
            try (Connection connection = getConnection()) {
                CallableStatement statement = connection.prepareCall("{CALL sp_get_vendor_by_id(?)}");
                statement.setString(1, vend_id);
                ResultSet resultSet = statement.executeQuery();
                // Use an if statement instead of a while loop when the SELECT query returns one record
                if (resultSet.next()) {
                    String vend_name = resultSet.getString("vend_name");
                    String vend_address = resultSet.getString("vend_address");
                    String vend_city = resultSet.getString("vend_city");
                    String vend_state = resultSet.getString("vend_state");
                    String vend_zip = resultSet.getString("vend_zip");
                    String vend_country = resultSet.getString("vend_country");
                    vendor = new Vendor(vend_id, vend_name, new Address(vend_address, vend_city, vend_state, vend_zip, vend_country));
                }
            } catch (SQLException e) {
//            System.out.println(e.getMessage()); // Uncomment in case null is always being returned
            }
        }
        return vendor;
    }

Servlet

  • Modify the code in the doPost method that validates the Vendor ID to first check if the ID already exists.
        Vendor vendorFromDB = VendorDAO.getVendor(vendorId);
        if (vendorFromDB != null) {
            validationError = true;
            req.setAttribute("vendorIdError", true);
            req.setAttribute("vendorIdMessage", "That vendor already exists");
        } else {
            try {
                vendor.setVend_id(vendorId);
                req.setAttribute("vendorIdError", false);
                req.setAttribute("vendorIdMessage", "Looks good!");
            } catch (IllegalArgumentException e) {
                validationError = true;
                req.setAttribute("vendorIdError", true);
                req.setAttribute("vendorIdMessage", e.getMessage());
            }
        }

Stored Procedure

  • Create a stored procedure to add a new product.
CREATE PROCEDURE sp_add_product_admin(
    IN p_prod_id    varchar(10),
  	IN p_vend_id    varchar(10),
  	IN p_prod_name  varchar(255),
  	IN p_prod_price decimal(8,2),
  	IN p_prod_desc  text
)
BEGIN
    INSERT INTO product (prod_id, vend_id, prod_name, prod_price, prod_desc)
    VALUES (p_prod_id, p_vend_id, p_prod_name, p_prod_price, p_prod_desc);
END;

POJO

  • No updates are needed to the Product Class

DAO

  • Update the XXXDAO Class

Servlet

  • Create an AdminAddProduct servlet
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet("/add-product")
public class AdminAddProduct extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("WEB-INF/ecommerce/admin-add-product.jsp").forward(req, resp);
    }
}

JSP

  • Add a button to the admin-products.jsp to add a new product
<section class="p-0 d-flex align-items-center position-relative overflow-hidden">
    <div class="container-fluid">
        <div class="row">
            <div class="col-12 col-lg-8 m-auto">
                <div class="row my-5">
                    <div class="col-sm-10 col-xl-8 m-auto">
                        <h2>Nice to meet you!</h2>
                        <p class="lead mb-4">Please sign up for an account.</p>

                        <!-- Form START -->
                        <form>
                            <!-- Email -->
                            <div class="mb-4">
                                <label for="inputEmail1" class="form-label">Email address *</label>
                                <div class="input-group input-group-lg">
                                    <span class="input-group-text bg-light rounded-start border-0 text-secondary px-3"><i class="bi bi-envelope-fill"></i></span>
                                    <input type="text" class="form-control border-0 bg-light rounded-end ps-1" placeholder="E-mail" id="inputEmail1">
                                </div>
                            </div>
                            <!-- Password -->
                            <div class="mb-4">
                                <label for="inputPassword1" class="form-label">Password *</label>
                                <div class="input-group input-group-lg">
                                    <span class="input-group-text bg-light rounded-start border-0 text-secondary px-3"><i class="fas fa-lock"></i></span>
                                    <input type="password" class="form-control border-0 bg-light rounded-end ps-1" placeholder="*********" id="inputPassword1">
                                </div>
                            </div>
                            <!-- Confirm Password -->
                            <div class="mb-4">
                                <label for="inputPassword2" class="form-label">Confirm Password *</label>
                                <div class="input-group input-group-lg">
                                    <span class="input-group-text bg-light rounded-start border-0 text-secondary px-3"><i class="fas fa-lock"></i></span>
                                    <input type="password" class="form-control border-0 bg-light rounded-end ps-1" placeholder="*********" id="inputPassword2">
                                </div>
                            </div>
                            <!-- Check box -->
                            <div class="mb-4">
                                <div class="form-check">
                                    <input type="checkbox" class="form-check-input" id="checkbox-1">
                                    <label class="form-check-label" for="checkbox-1">By signing up, you agree to the <a href="${appURL}/terms">terms and conditions</a></label>
                                </div>
                            </div>
                            <!-- Button -->
                            <div class="align-items-center mt-0">
                                <div class="d-grid">
                                    <button class="btn btn-primary mb-0" type="submit">Sign Up</button>
                                </div>
                            </div>
                        </form>
                        <!-- Form END -->

                        <!-- Sign in link -->
                        <div class="mt-4 text-center">
                            <span>Already have an account? <a href="${appURL}/login">Log in here</a></span>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</section>
  • Create a WEB-INF/ecommerce/admin-new-product.jsp to add a new product
<a href="add-product" class="btn btn-primary" role="button">Add Product</a>

Stored Procedure

  • Create a stored procedure to update a product.
CREATE PROCEDURE sp_update_product_admin(
    IN p_prod_id    varchar(10),
  	IN p_vend_id    varchar(10),
  	IN p_prod_name  varchar(255),
  	IN p_prod_price decimal(8,2),
  	IN p_prod_desc  text
)
BEGIN
    UPDATE product
    SET vend_id =  p_vend_id,
        prod_name =  p_prod_name,
        prod_price =  p_prod_price,
        prod_desc =  p_prod_desc
    WHERE prod_id = p_prod_id;
END

POJO

  • Update the XXX Class

DAO

  • Update the XXXDAO Class

Servlet

  • Update the XXX Servlet

JSP

  • Update the XXX JSP

Stored Procedure

  • Create a stored procedure to delete a product.
CREATE PROCEDURE sp_delete_product(IN p_prod_id varchar(10))
BEGIN
    DELETE FROM product WHERE prod_id = p_prod_id;
END;

Stored Procedure

  • Create a stored procedure for customers to get a product by its ID.
CREATE PROCEDURE sp_get_product_by_id(IN p_prod_id varchar(10))
BEGIN
    SELECT prod_id, prod_name, prod_price, prod_desc
    FROM products
    WHERE prod_id = p_prod_id;
END;

Stored Procedure

  • Create a stored procedure for administrators to get a product by its ID.
CREATE PROCEDURE sp_get_product_by_id_admin(IN p_prod_id varchar(10))
BEGIN
    SELECT prod_id, prod_name, prod_price, prod_desc, products.vend_id, vend_name
    FROM products JOIN vendors
    ON products.vend_id = vendors.vend_id
    WHERE prod_id = p_prod_id;
END;

Stored Procedure

  • Add Customer (Non-Admin)
  • Edit Customer (Admin and Non-Admin)
  • Delete Customer (Admin and Non-Admin)
  • Add Order (Non-Admin)
  • Edit Order (Admin and Non-Admin)
  • Delete Order (Admin and Non-Admin)

Java 2 - Week 12

By Marc Hauschildt

Java 2 - Week 12

  • 95