Marc Hauschildt
Web Technologies and Computer Software Development Instructor at Kirkwood Community College in Cedar Rapids, IA.
Week 6
In top-nav.jspf, update the "Edit Profile" button link.
<a href="${appURL}/edit-profile" class="btn btn-primary me-2">Edit Profile</a>
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 jakarta.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/edit-profile")
public class EditProfile extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
User user = (User)session.getAttribute("activeUser");
if(user == null) {
session.setAttribute("flashMessageWarning", "You must be logged in to edit your profile.");
resp.sendRedirect(resp.encodeRedirectURL(req.getContextPath() + "/login?redirect=edit-profile"));
return;
} else if(user != null && !user.getStatus().equals("active")) {
session.setAttribute("flashMessageDanger", "Your account is locked or inactive.");
resp.sendRedirect(resp.encodeRedirectURL(req.getContextPath() + "/"));
return;
}
req.setAttribute("pageTitle", "Edit Profile");
req.getRequestDispatcher("WEB-INF/edit-profile.jsp").forward(req, resp);
}
}
String redirect = req.getParameter("redirect");
if(redirect != null && !redirect.equals("")) {
req.setAttribute("redirect", redirect);
}
<input type="hidden" name="redirect" value="${redirect}">
String redirect = req.getParameter("redirect");
req.setAttribute("redirect", redirect);
// code omitted
// Flash Message that greets the user
if(redirect != null && !redirect.equals("")) {
resp.sendRedirect(resp.encodeRedirectURL(req.getContextPath() + "/" + redirect));
} else {
resp.sendRedirect(resp.encodeRedirectURL(req.getContextPath() + "/")); // Redirects to the home page
}
<div class="col-lg-3">
<!-- Responsive offcanvas body START -->
<div class="offcanvas-lg offcanvas-start" tabindex="-1" id="offcanvasSidebar">
<!-- Offcanvas header -->
<div class="offcanvas-header bg-light">
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#offcanvasSidebar" aria-label="Close"></button>
</div>
<!-- Offcanvas body -->
<div class="offcanvas-body p-3 p-lg-0">
<div class="bg-light border rounded-3 p-3 w-100">
<!-- Dashboard menu -->
<div class="list-group list-group-light list-group-borderless">
<a class="list-group-item ${pageTitle == "Edit Profile" ? "active" : ""}" href="${appURL}/edit-profile"><i class="bi bi-person-gear me-2"></i>Edit Profile</a>
<a class="list-group-item ${pageTitle == "Delete Account" ? "active" : ""}" href="${appURL}/delete-account"><i class="bi bi-trash me-2"></i>Delete Account</a>
</div>
</div>
</div>
</div>
<!-- Responsive offcanvas body END -->
</div>
<section class="my-4">
<div class="container">
<div class="row">
<!-- Profile banner START -->
<div class="col-12">
<div class="card bg-light card-body">
<!-- Profile info -->
<div class="col d-flex justify-content-between align-items-center">
<div>
<h4>Good morning<c:if test="${not empty activeUser.firstName}">, ${activeUser.firstName}</c:if>!</h4>
<ul class="list-inline mb-0">
<li class="list-item"><i class="bi bi-calendar-event-fill fs-6"></i> March 1 20XX, 8:00 AM</li>
<li class="list-item"><i class="bi bi-star-fill fs-6"></i> Member since January 1, 20XX</li>
</ul>
</div>
<!-- Responsive toggler START -->
<button class="btn btn-primary d-lg-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasSidebar" aria-controls="offcanvasSidebar">
<i class="bi bi-list fs-4"></i>
</button>
<!-- Responsive toggler END -->
</div>
</div>
</div>
<!-- Profile banner END -->
</div>
</div>
</section>
<main>
<%@ include file="/WEB-INF/edit-profile-header.jspf" %>
<!-- Page content START -->
<section class="pt-0">
<div class="container">
<div class="row">
<%@ include file="/WEB-INF/left-sidebar.jspf" %>
<!-- Main content START -->
<div class="col-lg-9">
<!-- Edit profile START -->
<div class="card bg-transparent border rounded-3">
<!-- Card header -->
<div class="card-header bg-light border-bottom">
<h3 class="card-header-title mb-0">Edit Profile</h3>
</div>
<!-- Card body START -->
<div class="card-body">
<!-- Form -->
<form class="row g-4">
<!-- First name -->
<div class="col-md-6">
<label class="form-label" for="firstName">First Name</label>
<input class="form-control" type="text" id="firstName" name="firstName">
</div>
<!-- Last name -->
<div class="col-md-6">
<label class="form-label" for="lastName">Last Name</label>
<input type="text" class="form-control" id="lastName" name="lastName">
</div>
<!-- Email id -->
<div class="col-md-6">
<label class="form-label" for="email">Email</label>
<input class="form-control" type="text" id="email" name="email">
</div>
<!-- Phone number -->
<div class="col-md-6">
<label class="form-label" for="phone">Phone number</label>
<input type="text" class="form-control" id="phone" name="phone">
</div>
<!-- Select option -->
<div class="col-md-6">
<!-- Language Preference -->
<label class="form-label" for="language">Language</label>
<select class="form-select js-choice z-index-9 bg-transparent" aria-label=".form-select-sm" id="language" name="language">
<option value="en-US">English</option>
<option value="es-MX">Spanish</option>
<option value="fr-FR">French</option>
</select>
</div>
<!-- Save button -->
<div class="d-sm-flex justify-content-end">
<button type="submit" class="btn btn-primary mb-0">Save changes</button>
</div>
</form>
</div>
<!-- Card body END -->
</div>
<!-- Edit profile END -->
</div>
<!-- Main content END -->
</div><!-- Row END -->
</div>
</section>
</main>
Add this value attribute to the Email field.
value="${activeUser.email}"
Refresh the page and the user's email will be populated in the form.
The remaining fields currently don't have values in the database, but we should still set them.
Add a value attribute to the First name input.
value="${activeUser.firstName}"
Add a value attribute to the Last name input.
value="${activeUser.lastName}"
Add a value attribute to the Phone input.
value="${activeUser.phone}"
Add an expression to select one of the languages. You are welcome to add or remove languages. You must have at least 3.
<option value="en-US" ${activeUser.language == 'en-US' ? 'selected' : ''}>English</option>
<option value="es-MX" ${activeUser.language == 'es-MX' ? 'selected' : ''}>Spanish</option>
<option value="fr-FR" ${activeUser.language == 'fr-FR' ? 'selected' : ''}>French</option>
Set the form attributes.
<form class="row g-4" method="POST" action="${appURL}/edit-profile">
Create a doPost method in the EditProfile Servlet. Add this code to get the form parameters and the active user object.
I only set the email and phone as request attributes because those are the only two text inputs that will be validated
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String firstName = req.getParameter("firstName");
String lastName = req.getParameter("lastName");
String email = req.getParameter("email");
String phone = req.getParameter("phone");
String language = req.getParameter("language");
req.setAttribute("email", email);
req.setAttribute("phone", phone);
HttpSession session = req.getSession();
User activeUser = (User)session.getAttribute("activeUser");
req.setAttribute("pageTitle", "Edit Profile");
req.getRequestDispatcher("WEB-INF/edit-profile.jsp").forward(req, resp);
}
Do I need to make a hard copy of the activeUser?
As written, calling the set method updates the user in the session.
Create new user
Error when immediately visiting edit profile page because "created at" isn't set
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String firstName = req.getParameter("firstName");
String lastName = req.getParameter("lastName");
String email = req.getParameter("email");
String phone = req.getParameter("phone");
String language = req.getParameter("language");
req.setAttribute("email", email);
req.setAttribute("phone", phone);
HttpSession session = req.getSession();
User activeUser = (User)session.getAttribute("activeUser");
req.setAttribute("pageTitle", "Edit Profile");
req.getRequestDispatcher("WEB-INF/edit-profile.jsp").forward(req, resp);
}
Create a series of try-catch statements that check if the input value differs from the current values set in the activeUser object.
If true, call the User class set methods to validate the input.
For the email validation, note that I am first checking if the email already exists, then I am checking if it is valid. If I reversed the order of the statements, the program would set any valid email, even if it were already taken.
boolean errorFound = false;
try {
if(!firstName.equals(activeUser.getFirstName())) {
activeUser.setFirstName(firstName);
}
} catch(IllegalArgumentException e) {
errorFound = true;
req.setAttribute("firstNameError", e.getMessage());
}
try {
if(!lastName.equals(activeUser.getLastName())) {
activeUser.setLastName(lastName);
}
} catch(IllegalArgumentException e) {
errorFound = true;
req.setAttribute("lastNameError", e.getMessage());
}
if(email != null && !email.equals("") && !email.equals(activeUser.getEmail()) && UserDAO.get(email) != null) {
errorFound = true;
req.setAttribute("emailError", "A user with that email already exists.");
} else {
try {
activeUser.setEmail(email);
} catch(IllegalArgumentException e) {
req.setAttribute("emailError", e.getMessage());
}
}
try {
if(phone != null && !phone.equals(activeUser.getPhone())) {
activeUser.setPhone(phone);
}
} catch(IllegalArgumentException e) {
errorFound = true;
req.setAttribute("phoneError", e.getMessage());
}
try {
if(!language.equals(activeUser.getLanguage())) {
activeUser.setLanguage(language);
}
} catch(IllegalArgumentException e) {
errorFound = true;
req.setAttribute("languageError", e.getMessage());
}
Add Patterns for a US Phone number and Language.
You may change the language code to whatever matches your select menu.
public static boolean isValidPhone(String phone) {
String regex = "^\\D?(\\d{3})\\D?\\D?(\\d{3})\\D?(\\d{4})$";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(phone);
return matcher.matches();
}
public static boolean isValidLanguage(String language) {
String regex = "^(en-US|es-MX|fr-FR)$";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(language);
return matcher.matches();
}
Write code to validate the phone number and language in the set methods.
The phone number will allow empty strings because they are optional.
If a phone number is entered it will be validated.
public void setPhone(String phone) {
if (phone != null && !phone.trim().isEmpty() && !Validators.isValidPhone(phone)) {
throw new IllegalArgumentException("Invalid phone number");
}
this.phone = phone;
}
public void setLanguage(String language) {
if (!Validators.isValidLanguage(language)) {
throw new IllegalArgumentException("Invalid language");
}
this.language = language;
}
If there are no errors, update the user in the database and reset the activeUser session attribute.
if (!errorFound) {
UserDAO.update(activeUser);
session.setAttribute("activeUser", activeUser);
session.setAttribute("flashMessageSuccess", "Your profile was updated");
} else {
session.setAttribute("flashMessageWarning", "Your profile was not updated");
}
CREATE PROCEDURE sp_update_user(
IN orig_user_id int,
IN orig_first_name VARCHAR(255),
IN orig_last_name VARCHAR(255),
IN orig_email VARCHAR(255),
IN orig_phone VARCHAR(255),
IN orig_language VARCHAR(255),
IN orig_status VARCHAR(255),
IN orig_privileges VARCHAR(255),
IN orig_timezone VARCHAR(50),
IN new_first_name VARCHAR(255),
IN new_last_name VARCHAR(255),
IN new_email VARCHAR(255),
IN new_phone VARCHAR(255),
IN new_language VARCHAR(255),
IN new_status VARCHAR(255),
IN new_privileges VARCHAR(255),
IN new_timezone VARCHAR(50)
)
BEGIN
UPDATE user
SET first_name = new_first_name,
last_name = new_last_name,
email = new_email,
phone = new_phone,
language = new_language,
status = new_status,
privileges = new_privileges,
timezone = new_timezone
WHERE user_id = orig_user_id
AND first_name = orig_first_name
AND last_name = orig_last_name
AND email = orig_email
AND phone = orig_phone
AND language = orig_language
AND status = orig_status
AND privileges = orig_privileges
AND timezone = orig_timezone;
END
2026, Also delete any password_reset records. If a record exists, the foreign key constraint will prevent you from updating your email address.
public static boolean update(User user) {
try(Connection connection = getConnection();
CallableStatement statement = connection.prepareCall("{CALL sp_update_user(?,?,?,?,?,?,?,?,?)}")
) {
statement.setInt(1, user.getUserId());
statement.setString(2, user.getFirstName());
statement.setString(3, user.getLastName());
statement.setString(4, user.getEmail());
statement.setString(5, user.getPhone());
statement.setString(6, user.getLanguage());
statement.setString(7, user.getStatus());
statement.setString(8, user.getPrivileges());
statement.setString(9, user.getTimezone());
int rowsAffected = statement.executeUpdate();
return rowsAffected == 1;
} catch(SQLException e) {
System.out.println(e.getMessage());
return false;
}
}
Add invalid-feedback blocks below each of the form inputs.
<c:if test="${not empty firstNameError}"><div class="invalid-feedback">${firstNameError}</div></c:if>
<c:if test="${not empty lastNameError}"><div class="invalid-feedback">${lastNameError}</div></c:if>
<c:if test="${not empty emailError }"><div class="invalid-feedback">${emailError}</div></c:if>
<c:if test="${not empty phoneError }"><div class="invalid-feedback">${phoneError}</div></c:if>
<c:if test="${not empty languageError }"><div class="invalid-feedback">${languageError}</div></c:if>
Add a statement to the class attribute of each input tag.
class="form-control <c:if test="${not empty firstNameError}">is-invalid</c:if>"
class="form-control <c:if test="${not empty lastNameError}">is-invalid</c:if>"
class="form-control <c:if test="${not empty emailError}">is-invalid</c:if>"
class="form-control <c:if test="${not empty phoneError}">is-invalid</c:if>"
class="form-select <c:if test="${not empty languageError}">is-invalid</c:if>
Run the program to test the form. If an invalid email address or phone number is entered, the values from the activeUser session variable will display.
We will eventually use the phone number to send text messages and phone calls, and use the language to display content on our website in a different language.
We will edit the "Good morning" text, current date/time, and replace "January 1, 2000" with the created_at date in an upcoming lesson.
An opportunity to exceed expectation this week will be to add a form field asking the user for their time zone.
Run this code to view a list of all supported time zone IDs.
public static void main(String[] args) {
java.util.Arrays.asList(java.util.TimeZone.getAvailableIDs()).forEach(System.out::println);
}
Use a c:forEach tag to iterate through TimeZone.getAvailableIDs() to create the <option> tags.
In the User.setTimeZone method, validate the input using the List.contains() method.
Use white label text if your project has a dark background
Storing the user's date of birth as a date in the database.
Declare the date of birth as a LocalDate in the User class. Use LocalDate instead.
Call setDate in the UserDAO methods.
Convert LocalDate to Date in the update method. https://www.baeldung.com/java-date-to-localdate-and-localdatetime#localdate-date
By Marc Hauschildt
Web Technologies and Computer Software Development Instructor at Kirkwood Community College in Cedar Rapids, IA.