Java 3 - 2025

Week 3

  • https://www.figma.com/design/hdBPE6mtbNl4J8pSwsvv0p/Emerald-Park-UML-Diagrams?node-id=0-1&p=f&t=UPqUu4EZiXTad9wp-0
  • https://www.figma.com/design/wSGREKTmyk3sYd24g16YJm/Emerald-Park-UML-Diagrams%3A-Edit-Profile?node-id=0-1
  • https://docs.google.com/document/d/1aX6dHh3Cb4fSu2kqWkD_9TO6K94Q8C8jQS5xhgTUzrs/edit?tab=t.0#heading=h.2zknlry6wyqg

Signup Servlet doPost

  • In the Signup servlet, create a doPost method to get the parameters from the form submission.

  • Immediately set those values as attributes. Note the ternary operator being used to set the terms attribute to "agree" only if the checkbox was checked.

  • At the end of the doPost method, set the page title and forward the data to the JSP.
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String email = req.getParameter("email");
    String password1 = req.getParameter("password1");
    String password2 = req.getParameter("password2");
    String[] terms = req.getParameterValues("terms");
    req.setAttribute("email", email);
    req.setAttribute("password1", password1);
    req.setAttribute("password2", password2);
    req.setAttribute("terms", (terms != null && terms[0].equals("agree")) ? "agree" : "");

    req.setAttribute("pageTitle", "Sign up for an account");
    req.getRequestDispatcher("WEB-INF/signup.jsp").forward(req, resp);
}

signup.jsp

  • Use expressions in value attributes to get attributes from the servlet
    value="${email}"
    value="${password1}"
    value="${password2}"

  • Use a c:if tag inside in the checkbox input to re-select it.
    <c:if test="${terms eq 'agree'}">checked</c:if>

  • Run Tomcat. When you enter values and press submit, the values should remain in the form.

Validators

  • Create a shared/Validators class with code we created in Java 2.

  • Add a method to validate a strong password.

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Validators {
    public static boolean isStrongPassword(String password) {
        // Password requires 8 characters with at least 3 out 4 (uppercase letter, lowercase letter, number, special character ~`!@#$%^&*()_-+={}[]|\:;"'<>,.?/
        Pattern pattern = Pattern.compile("^(?:(?=.*\\d)(?=.*[A-Z])(?=.*[a-z])|(?=.*\\d)(?=.*[^A-Za-z0-9])(?=.*[a-z])|(?=.*[^A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z])|(?=.*\\d)(?=.*[A-Z])(?=.*[^A-Za-z0-9]))(?!.*(.)\\1{2,})[A-Za-z0-9~`!\\@#\\$%\\^&*()_\\-+={}\\[\\]\\|\\\\:;\"'<>,.?\\/]{8,128}$");
        Matcher matcher = pattern.matcher(password);
        return matcher.matches();
    }
    
    public static boolean isANumber(String str) {
        try {
            Double.parseDouble(str);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    public static boolean isValidEmail(String email) {
        String regex = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}$";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(email);
        return matcher.matches();
    }

    public static boolean isValidState(String state) {
        state = state.toUpperCase();
        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);
        Matcher matcher = pattern.matcher(state);
        return matcher.matches();
    }

    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();
    }
    
    public static boolean isValidCountry(String country) {
        country = country.toUpperCase();
        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);
        Matcher matcher = pattern.matcher(country);
        return matcher.matches();
    }
}

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three
public void setPassword(char[] password) {
    String passwordStr = String.valueOf(password);
    String regex = "^" +
        "(?=.*[0-9])" + // a digit must occur at least once
        "(?=.*[a-z])" + // a lower case letter must occur at least once
        "(?=.*[A-Z])" + // an upper case letter must occur at least once
        // "(?=.*[!@#$%^&*()+-=])" + // a special character must occur at least once
        // "(?=\\S+$)" + // no whitespace allowed in the entire string
        ".{8,}" + // anything, at least eight characters
        "$";
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(passwordStr);
    if(!matcher.matches()) {
        throw new IllegalArgumentException("Password must contain a minimum of 8 characters, 1 digit, 1 lowercase letter, and 1 uppercase letter.");
    }
    this.password = password;
}

User class

  • Set the email and password only if they are valid. 

  • The setPassword method will allow null values because we will set the password to null after it has been validated.

     

public void setEmail(String email) {
    if (!Validators.isValidEmail(email)) {
        throw new IllegalArgumentException("Invalid email address");
    }
    this.email = email;
}

public void setPassword(char[] password) {
	if (password != null) {
    	String passwordStr = String.valueOf(password);
    	if (!Validators.isStrongPassword(passwordStr)) {
        	throw new IllegalArgumentException("Password requires 8 characters with at least 3 out 4 (uppercase letter, lowercase letter, number, special character)");
    	}
    }
    this.password = password;
}

User class

  • The getPassword() method returns a char[] and not a String. This helps in security. This is mainly due to the immutability of Strings.

  • Since String ’s are an immutable data type, they cannot be changed after a value has been created on the String Pool . This will be automatically removed by the Java Garbage Collection Process after a while. Until then, this value will continue to exist on the Heap.

  • Therefore, the char[] is used to store the password. This is because the character array can be clear as an empty array immediately after using it. Therefore it will not be stored in the heap so no unauthorized user can access the heap and retrieve the entered password.

Signup Servlet

  • After setting the attributes in the doPost method, create a User object and validate all of the inputs. 

User user = new User();
boolean errorFound = false;
try {
    user.setEmail(email);
} catch(IllegalArgumentException e) {
    req.setAttribute("emailError", e.getMessage());
    errorFound = true;
}
try {
    user.setPassword(password1.toCharArray());
} catch(IllegalArgumentException e) {
    req.setAttribute("password1Error", e.getMessage());
    errorFound = true;
}
if(password2.equals("")) {
    req.setAttribute("password2Error", "This input is required");
    errorFound = true;
}
if(!password1.equals(password2)) {
    req.setAttribute("password2Error", "Passwords don't match");
    errorFound = true;
}
if(terms == null || !terms[0].equals("agree")){
    req.setAttribute("termsError", "You must agree to our terms of use");
    errorFound = true;
}

signup.jsp

  • Use c:if tags with expressions to display error messages after each label.

    <c:if test="${not empty emailError}"><div class="invalid-feedback">${emailError}</div></c:if>

    <c:if test="${not empty password1Error }"><div class="invalid-feedback">${password1Error}</div></c:if>

    <c:if test="${not empty password2Error }"><div class="invalid-feedback">${password2Error}</div></c:if>

    <c:if test="${not empty termsError }"><div class="invalid-feedback">${termsError}</div></c:if>

signup.jsp

  • Use c:if tags with expressions in the form input class attributes.

    <c:if test="${not empty emailError}">is-invalid</c:if>

    <c:if test="${not empty password1Error }">is-invalid</c:if>

    <c:if test="${not empty password2Error }">is-invalid</c:if>

    <c:if test="${not empty termsError}">is-invalid</c:if>

  • Run the project and test invalid inputs.

Get User By Email

CREATE PROCEDURE sp_get_user(IN p_email VARCHAR(255))
BEGIN
    SELECT user_id, first_name, last_name, email, phone, password, language, status, privileges, created_at, timezone
    FROM user
    WHERE email = p_email;
END;
  • Create a stored procedure to get a single user by their email address

Add User

CREATE PROCEDURE sp_add_user(
    IN p_email VARCHAR(255),
    IN p_password VARCHAR(255),
    IN p_status VARCHAR(10),
    IN p_privileges VARCHAR(10)
)
BEGIN
    INSERT INTO user (email, password, status, privileges)
    VALUES (p_email,p_password,p_status,p_privileges);
END;
  • Create a stored procedure to create a user.

UserDAO

public static User get(String email) {
    User user = null;
    try (Connection connection = getConnection()) {
        if (connection != null) {
            try (CallableStatement statement = connection.prepareCall("{CALL sp_get_user(?)}")) {
                statement.setString(1, email);
                try (ResultSet resultSet = statement.executeQuery()) {
                    if (resultSet.next()) {
                        int userId = resultSet.getInt("user_id");
                        String firstName = resultSet.getString("first_name");
                        String lastName = resultSet.getString("last_name");
                        String phone = resultSet.getString("phone");
                        char[] password = resultSet.getString("password").toCharArray();
                        String language = resultSet.getString("language");
                        String status = resultSet.getString("status");
                        String privileges = resultSet.getString("privileges");
                        Instant created_at = resultSet.getTimestamp("created_at").toInstant();
                        String timezone = resultSet.getString("timezone");
                        user = new User(userId, firstName, lastName, email, phone, password, language, status, privileges, created_at, timezone);
                    }
                }
            }
        }
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
    return user;
}
  • Implement a method to get a user by their email address.

Database Vocabulary

  • CallableStatement

    CallableStatement is an interface used to execute stored procedures.  

  • Connection

    An interface for classes representing a communication session with a database server.

  • ResultSet

    A ResultSet is a table-like structure used to hold records returned from a SQL query.  

UserDAO

public static void main(String[] args) {
    System.out.println(get("test@example.com"));
}
  • Query sp_add_user stored procedure to add a temporary user.
CALL sp_add_user('test@example.com', 'badpassword');
  • In the UserDAO class, call the get function inside the main method.

Signup Servlet

if(UserDAO.get(email) != null) {
    req.setAttribute("emailError", "A user with that email already exists. <a href="login">Login</a> or <a href=\"reset-password\">reset the password</a>.");
    errorFound = true;
}
  • Add an if statement that checks if the user already exists. If true, set an emailError attribute.
  • Run the program. Try to sign up a user with the email address already added to your database.

End Day 5

Start Day 6

pom.xml

public static boolean add(User user) {
    try (Connection connection = getConnection()) {
        if (connection != null) {
            try (CallableStatement statement = connection.prepareCall("{CALL sp_add_user(?, ?)}")) {
                statement.setString(1, user.getEmail());
                String encryptedPassword = BCrypt.hashpw(new String(user.getPassword()), BCrypt.gensalt(12));
                statement.setString(2, encryptedPassword);
                int rowsAffected = statement.executeUpdate();
                return rowsAffected == 1;
            }
        }
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
    return false;
}
  • Implement a method to add a User. 
  • The stored procedure was created in the Week 1 presentation.

UserDAO

public static void main(String[] args) {
    User user = new User();
    user.setEmail("test2@example.com");
    user.setPassword("P@ssw0rd".toCharArray());
    add(user);
}
  • Add this code to the main method to test if the add method works.

UserDAO

  • Query the database to see the new user. Notice the encrypted password.
    CALL sp_get_all_users();
if (!errorFound) {
    user.setStatus("active"); // Changes default 'inactive' status to 'active'
    user.setPrivileges("user"); // Changes default 'subscriber' privileges to 'user'
    boolean userAdded = false;
    try {
        userAdded = UserDAO.add(user);
    } catch (RuntimeException e) {
        req.setAttribute("userAddFail", "User not added.");
    }
    if(userAdded) {
        user.setPassword(null); // Remove the password before setting the User object as a session attribute

        HttpSession session = req.getSession(); // Get existing HttSession object
        session.invalidate(); // Remove any existing session attributes
        session = req.getSession(); // Create new HttpSession
        session.setAttribute("activeUser", user);
        session.setAttribute("flashMessageSuccess", "Welcome New User!");

        resp.sendRedirect(resp.encodeRedirectURL(req.getContextPath() + "/")); // Redirects to the home page
        return;
    }
}
  • If there are no errors, update the user's status and privileges. Then call the method to add a user to the database.
  • If the user was added, remove their password by setting it null.
  • Then, create an HttpSession object. Set two session attributes, one to track the User object, and one for a flash message.
  • Instead of forwarding the request and response to a JSP, redirect the user to any page, such as the home page.

Signup Servlet

  • The signup servlet needs to assign a CreatedAt value to the user. after line 73 add
    user.setCreatedAt(Instant.now());
  • Or we need to require the user to login so we get the User from the database so that value is set.
  • Without it we will get an error when visiting the edit profile page immediately after Signing up

Signup Servlet

<session-config>
    <session-timeout>60</session-max-age>
</session-config>
  • To configure the length of an HttpSession, the session timeout can be configured in the web.xml deployment descriptor file.
  • Session timeout is in minutes
  • A value of -1 indicates the session will never expire.

web.xml

<c:if test="${not empty userAddFail}">
    <div class="alert alert-danger mb-2" role="alert">${userAddFail}</div>
</c:if>
  • If the user could not be added, display a message above the form.
  • To test the fail message, comment out the code in the Signup servlet that checks if an email already exists. Then, try to create a new user with an existing email address.

signup.jsp

  • Our session object will track things such as logged in users, shopping cart contents, and more.

  • Edit the Login and Sign-up buttons so they only display when the "activeUser" session variable is not set.

  • When "activeUser" is set, display the My Profile and Sign Out buttons.

top-nav.jspf

<div class="text-end">
<c:choose>
    <c:when test="${empty sessionScope.activeUser}">
        <a href="#" class="btn btn-outline-dark me-2">Login</a>
        <a href="${appURL}/signup" class="btn btn-warning">Sign-up</a>
    </c:when>
    <c:otherwise>
       <a href="#" class="btn btn-outline-secondary">Sign out</a> 
       <a href="#" class="btn btn-primary me-2">My Profile</a>
    </c:otherwise>
</c:choose>
</div>
<div class="container">
<c:choose>
    <c:when test="${not empty sessionScope.flashMessageSuccess}">
        <div class="alert alert-success my-2" role="alert">
                ${sessionScope.flashMessageSuccess}
        </div>
        <c:remove var="flashMessageSuccess" scope="session" />
    </c:when>
    <c:when test="${not empty sessionScope.flashMessageDanger}">
        <div class="alert alert-danger my-2" role="alert">
                ${sessionScope.flashMessageDanger}
        </div>
        <c:remove var="flashMessageDanger" scope="session" />
    </c:when>
    <c:when test="${not empty sessionScope.flashMessageWarning}">
        <div class="alert alert-warning my-2" role="alert">
                ${sessionScope.flashMessageWarning}
        </div>
        <c:remove var="flashMessageWarning" scope="session" />
    </c:when>
</c:choose>
</div>
  • In top.jspf, after including top-nav.jspf, add the following c:choose statement.
    • Success messages display green.
    • Danger messages display red.
    • Warning messages display yellow.
  • The c:remove statement is used to remove a session attribute after it is used.

top.jspf

signup.jsp

  • Test out the form using an email that doesn't exist.

  • If everything works correctly, you will be redirected to the home page, and a green success message will appear between the navigation and the carousel slider. Also, the "Login" and "Sign-up" buttons will now say "Sign out" and "My Profile"

  • Query the database to see the new user.
    CALL sp_get_all_users();

  • If you refresh the page, the flash message will disappear.

  • If you add two users with different email addresses but the same password, you will notice their hashed passwords are different.

  • Delete any unnecessary user database records before continuing.
    DELETE FROM user WHERE user_id = N;

    • If you delete a row from the query console output, be sure to press the up arrow button to submit changes.

End Day 6

2026

  • Get User from database after creating a new user to obtain the created_at field.
if(!errorFound){
    user.setPrivileges("user");
    user.setStatus("active");
    boolean userAdded = false;
    try {
        userAdded = UserDAO.add(user);
        user = UserDAO.get(user.getEmail());
    } catch (RuntimeException e) {
        req.setAttribute("userAddFail","User could not be added");
    }
    if(userAdded) {
        user.setPassword(null);

Google Recaptcha

Java 3 - Week 3

By Marc Hauschildt

Java 3 - Week 3

  • 238