diff --git a/README.md b/README.md index a30ba5a..7fdd345 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ - [asset manager](https://github.com/Azure-Samples/java-migration-copilot-samples/tree/main/asset-manager) : This is a complete workshop with an end-to-end scenario that migrates an app to Azure using predefined and custom formulas. After the migration, the app will run on Azure Container Apps and interact with Auzure Blob, Azure Service Bus and Azure Database for PostgreSQL. --[Todo Web API with Oracle Database](https://github.com/Azure-Samples/java-migration-copilot-samples/tree/main/todo-web-api-use-oracle-db) A To-do application using Oracle database for storage. It leverages Oracle-specific SQL features and data types, for instance, VARCHAR2. This sample migrates the application to use Azure Database for PostgreSQL instead. +- [Todo Web API with Oracle Database](https://github.com/Azure-Samples/java-migration-copilot-samples/tree/main/todo-web-api-use-oracle-db) A To-do application using Oracle database for storage. It leverages Oracle-specific SQL features and data types, for instance, VARCHAR2. This sample migrates the application to use Azure Database for PostgreSQL instead. + +- [Student Web App - Jakarta EE](jakarta-ee/student-web-app) A Java EE web application running on Open Liberty with a hybrid architecture that supports both traditional servlets and Spring MVC. The application manages student profiles with CRUD operations and demonstrates migrating from Ant to Maven and Java EE to Jakarta EE. ## Branches diff --git a/jakarta-ee/student-web-app/.gitignore b/jakarta-ee/student-web-app/.gitignore new file mode 100644 index 0000000..46b35c3 --- /dev/null +++ b/jakarta-ee/student-web-app/.gitignore @@ -0,0 +1,30 @@ +target/ +build/ +dist/ +*.war + +# IDE files +.idea/ +*.iml +.vscode/ +/bin/ +.project +.classpath +.settings/ + +# Local configuration files (contain environment-specific paths) +local.properties +liberty_config/server.env + +# Compile-time dependencies (can be downloaded) +compile-lib/*.jar + +# Docker files +mysql-connector/ + +# OS files +.DS_Store +Thumbs.db + +# Log files +*.log diff --git a/jakarta-ee/student-web-app/Dockerfile b/jakarta-ee/student-web-app/Dockerfile new file mode 100644 index 0000000..706446b --- /dev/null +++ b/jakarta-ee/student-web-app/Dockerfile @@ -0,0 +1,33 @@ +FROM open-liberty:25.0.0.7-kernel-slim-java17-openj9 + +# Copy Liberty configuration +COPY --chown=1001:0 liberty_config/server-docker.xml /config/server.xml +COPY --chown=1001:0 liberty_config/server-docker.env /config/server.env + +# Copy application +COPY --chown=1001:0 dist/OpenLibertyApp.war /config/apps/ + +# Create directories for libraries +RUN mkdir -p /opt/ol/wlp/usr/shared/resources/mysql + +# Copy MySQL connector for JDBC driver +COPY --chown=1001:0 mysql-connector/mysql-connector-j-8.0.33.jar /opt/ol/wlp/usr/shared/resources/mysql/ + +# Configure Liberty features +RUN features.sh + +# Expose ports +EXPOSE 9080 9443 + +# Set environment variables for Docker environment +ENV WLP_USER_DIR=/opt/ol/wlp/usr \ + SERVER_NAME=defaultServer \ + MYSQL_LIB_DIR=/opt/ol/wlp/usr/shared/resources/mysql \ + LIBERTY_SERVER_DIR=/config \ + KEYSTORE_LOCATION=/config/resources/security/key.p12 \ + KEYSTORE_PASSWORD=defaultPassword \ + TRUSTED_KEYSTORE_LOCATION=/config/resources/security/trust.p12 \ + TRUSTED_KEYSTORE_PASSWORD=defaultPassword \ + LTPA_KEY_FILE_SFA=/config/resources/security/ltpa.keys \ + LTPA_KEY_PASSWORD_SFA=defaultPassword \ + JDBC_DRIVER_CLASS=com.mysql.cj.jdbc.Driver diff --git a/jakarta-ee/student-web-app/README.md b/jakarta-ee/student-web-app/README.md new file mode 100644 index 0000000..7868468 --- /dev/null +++ b/jakarta-ee/student-web-app/README.md @@ -0,0 +1,80 @@ +# Migrate a legacy Java EE Ant project to a Jakarta EE 10 Maven project + +This workshop guides you through migrating a legacy Java EE Ant project to a Jakarta EE 10 Maven project. After completing the workshop, the project will be fully migrated from Java EE to Jakarta EE 10, upgraded from Spring Framework 5.3.39 to 6.2.x, and ready to build with Maven. + +## About this Sample Project + +A Java EE web application running on Open Liberty with a hybrid architecture that supports both traditional servlets and Spring MVC. The application manages student profiles with CRUD operations and demonstrates migrating from Ant to Maven and Java EE to Jakarta EE. + +For the project architecture, see [project details](doc/architecture.md). +For how to start this project, see [getting started](doc/getting-started.md) + +## Prerequisites + +- **Java 17** or higher +- **Apache Ant**, tested version: `1.10.14`. +- **Maven**, tested version: `3.8.7`. +- **Docker & Docker Compose** (Optional, for running the sample application) +- [Visual Studio Code](https://code.visualstudio.com/download) +- [VS Code Extension: GitHub Copilot App Modernization](https://marketplace.visualstudio.com/items?itemName=vscjava.migrate-java-to-azure) + - This extension depends on [VS Code Extension: GitHub Copilot App Modernization – Upgrade for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-upgrade). Both extensions will be installed automatically when you install **GitHub Copilot App Modernization**. + +## Assess the Project + +Please follow the steps below to assess and detect the Eclipse/Ant project: + +1. Open [student-web-app](jakarta-ee/student-web-app) in VSCode. + +1. Click **GitHub Copilot app modernization for Java** from the left side tool bar. + + Click the `Toggle Chat` icon on the top, select `Agent` mode. If the **reload button** (looks like an arrow going counter clockwise) appears at the bottom, click it to reload the tools registration. This is only required the first time you install the extensions. + + ![assessment-start](assets/assessment-start.png) + +1. Click **Run Assessment** button from the **Assessment** section on the left side view. It will open GitHub Copilot Chat window at the right side, and start to assess the project with the predefined prompt. + +1. Interact with Copilot in the chat window and select **Continue** to proceed whenever you're ok with the actions suggested by the Copilot. + +1. After the assessment is done, an **Assessment Report** will be generated and opened in the editor. From the bottom of the report, you can see **Build Tool (Ant)** in section **Java Modernization**, and associated action **Migrate** to migrate the project to Maven project. + + ![assessment-ant-to-maven-solution](assets/assessment-ant-to-maven-solution.png) + +## Convert Ant project to Maven project + +Now you can convert the Ant project to Maven project. + +1. Select Action **Migrate** associated with **Build Tool (Ant)**, and start the migration with the predefined prompt. We recommend using the LLM model **Claude Sonnet 3.7** for the migration. + + ![convert-ant-to-maven](assets/convert-ant-to-maven.png) + +1. Interact with Copilot in the chat window and select **Continue** to proceed whenever you're ok with the actions suggested by the Copilot. + + If you see the request from Copilot to ask you confirm the migration plan, please review and manually confirm the migration plan before proceeding. For example, input `Confirm` in the chat box and click `Send`. + +1. After the migration is complete, select **Keep** for changed files. + +## Assess and detect the legacy Java EE and Spring Framework project + +Please follow the similar steps before to assess and detect the legacy Java EE and Spring Framework project: + +1. Select **GitHub Copilot app modernization for Java** from the left side tool bar. +1. Select **Run Assessment** from the **Assessment** section. It will open GitHub Copilot Chat window at the right side, and start to assess the project with the predefined prompt. +1. Interact with Copilot in the chat window and select **Continue** to proceed whenever you're ok with the actions suggested by the Copilot. +1. After the assessment is done, an **Assessment Report** will be generated and opened in the editor. From the bottom of the report, you can see **Framework Upgrade (Java EE/Jakarta EE)** and **Framework Upgrade (Spring Framework)** in section **Java Modernization**, and associated action **Upgrade** to upgrade the project. + +Before you proceed with the next step, manually clean up the assistant files of the assessment tool. They will be automatically cleaned up in the future releases of the tool. + +1. Replace `appcat` with `*` in the `.github\appmod-java\.gitignore` file to ignore all files within the `.github\appmod-java` directory. +1. Open **Source Control** view in VSCode, and select **Discard All Changes** to discard all changes made by the assessment tool. + +## Upgrade the Project to Jakarta EE 10 and Spring Framework 6.x + +Now you can upgrade the project. In this workshop, you select to upgrade Spring Framework version as it will also upgrade dependent Java EE version. + +1. Select Action **Upgrade** associated with **Upgrade Spring Framework Version**, and start the upgrade with the predefined prompt. Recommend to use LLM model **Claude Sonnet 3.7** for the migration. +1. Interact with Copilot in the chat window and select **Continue** to proceed whenever you're ok with the actions suggested by the Copilot. +1. After the migration is complete, select **Keep** if there are any changed files. + +There is an Upgrade Summary generated, review and do any follow-up actions based on your needs. + + diff --git a/jakarta-ee/student-web-app/WebContent/WEB-INF/applicationContext.xml b/jakarta-ee/student-web-app/WebContent/WEB-INF/applicationContext.xml new file mode 100644 index 0000000..d59bf43 --- /dev/null +++ b/jakarta-ee/student-web-app/WebContent/WEB-INF/applicationContext.xml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/jakarta-ee/student-web-app/WebContent/WEB-INF/spring-servlet.xml b/jakarta-ee/student-web-app/WebContent/WEB-INF/spring-servlet.xml new file mode 100644 index 0000000..0b08de9 --- /dev/null +++ b/jakarta-ee/student-web-app/WebContent/WEB-INF/spring-servlet.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + diff --git a/jakarta-ee/student-web-app/WebContent/WEB-INF/web.xml b/jakarta-ee/student-web-app/WebContent/WEB-INF/web.xml new file mode 100644 index 0000000..8c4259f --- /dev/null +++ b/jakarta-ee/student-web-app/WebContent/WEB-INF/web.xml @@ -0,0 +1,78 @@ + + + OpenLibertyApp + + + + contextConfigLocation + /WEB-INF/applicationContext.xml + + + + + org.springframework.web.context.ContextLoaderListener + + + + + spring + org.springframework.web.servlet.DispatcherServlet + + contextConfigLocation + /WEB-INF/spring-servlet.xml + + 1 + + + + spring + /app/* + + + + + IndexServlet + ca.on.gov.edu.coreft.IndexServlet + + + IndexServlet + / + + + + + AddStudentServlet + ca.on.gov.edu.coreft.AddStudentServlet + + + AddStudentServlet + /addStudent + + + + StudentProfileListServlet + ca.on.gov.edu.coreft.StudentProfileListServlet + + + StudentProfileListServlet + /studentProfileList + + + + DB Connection + jdbc/StudentDB + javax.sql.DataSource + Container + + + + Mail Session + mail/StudentMailSession + javax.mail.Session + Container + + + diff --git a/jakarta-ee/student-web-app/WebContent/add_student_profile.jsp b/jakarta-ee/student-web-app/WebContent/add_student_profile.jsp new file mode 100644 index 0000000..3c528cf --- /dev/null +++ b/jakarta-ee/student-web-app/WebContent/add_student_profile.jsp @@ -0,0 +1,77 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ page import="java.util.*" %> + + + Add Student Profile - Spring Framework + + + +
+
+

Add Student Profile

+

Spring Framework 5.3 Integration

+
+ + + + <% + String successMessage = (String) request.getAttribute("successMessage"); + String errorMessage = (String) request.getAttribute("errorMessage"); + if (successMessage != null) { + %> +
+ <%= successMessage %> +
+ <% } %> + + <% if (errorMessage != null) { %> +
+ <%= errorMessage %> +
+ <% } %> + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ +
+

Note: This form now uses Spring MVC for processing student data.

+
+
+ + diff --git a/jakarta-ee/student-web-app/WebContent/index.jsp b/jakarta-ee/student-web-app/WebContent/index.jsp new file mode 100644 index 0000000..028c8f8 --- /dev/null +++ b/jakarta-ee/student-web-app/WebContent/index.jsp @@ -0,0 +1,115 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ page import="ca.on.gov.edu.coreft.StudentProfile" %> +<%@ page import="java.util.List" %> + + + Student Management System - Spring Framework 5.3 + + + +
+

Student Management System

+

Powered by Spring Framework 5.3 and Open Liberty

+
+ + + + <% + String error = (String) request.getAttribute("error"); + String successMessage = (String) request.getAttribute("successMessage"); + String errorMessage = (String) request.getAttribute("errorMessage"); + + if (error != null) { + %> +
+ Error: <%= error %> +
+ <% } %> + + <% if (successMessage != null) { %> +
+ Success: <%= successMessage %> +
+ <% } %> + + <% if (errorMessage != null) { %> +
+ Error: <%= errorMessage %> +
+ <% } %> + +

Student Profiles (Direct JSP Access)

+ +
+ New! Spring Framework 5.3 has been integrated! Try the new Spring MVC pages: +
Spring Home Page - Full Spring MVC integration +
Spring Add Student - Modern form handling +

This page shows direct JSP access (traditional approach). +
+ + + + + + + + + + + + <% + @SuppressWarnings("unchecked") + List students = (List) request.getAttribute("students"); + if (students != null && !students.isEmpty()) { + for (StudentProfile student : students) { + %> + + + + + + + <% } + } else { %> + + + + <% } %> + +
IDNameEmailMajor
<%= student.getId() %><%= student.getName() %><%= student.getEmail() %><%= student.getMajor() %>
+ <% if (students != null) { %> + No student profiles found. + <% } else { %> + Loading student data... + <% } %> +
+ +
+

Technology Stack:

+ +
+ + diff --git a/jakarta-ee/student-web-app/WebContent/spring-add-student.jsp b/jakarta-ee/student-web-app/WebContent/spring-add-student.jsp new file mode 100644 index 0000000..31fb397 --- /dev/null +++ b/jakarta-ee/student-web-app/WebContent/spring-add-student.jsp @@ -0,0 +1,85 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ page import="java.util.*" %> + + + Add Student Profile - Spring Framework + + + +
+
+

Add Student Profile Spring MVC

+

Spring Framework 5.3 Integration

+
+ + + + <% + String successMessage = (String) request.getAttribute("successMessage"); + String errorMessage = (String) request.getAttribute("errorMessage"); + if (successMessage != null) { + %> +
+ <%= successMessage %> +
+ <% } %> + + <% if (errorMessage != null) { %> +
+ <%= errorMessage %> +
+ <% } %> + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ +
+

Spring MVC Integration:

+ +
+
+ + diff --git a/jakarta-ee/student-web-app/WebContent/spring-index.jsp b/jakarta-ee/student-web-app/WebContent/spring-index.jsp new file mode 100644 index 0000000..f6bec0e --- /dev/null +++ b/jakarta-ee/student-web-app/WebContent/spring-index.jsp @@ -0,0 +1,124 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ page import="ca.on.gov.edu.coreft.StudentProfile" %> +<%@ page import="java.util.List" %> + + + Student Management System - Spring Framework 5.3 + + + +
+

Student Management System Spring MVC

+

Powered by Spring Framework 5.3 and Open Liberty

+
+ + + + <% + String error = (String) request.getAttribute("error"); + String successMessage = (String) request.getAttribute("successMessage"); + String errorMessage = (String) request.getAttribute("errorMessage"); + + if (error != null) { + %> +
+ Error: <%= error %> +
+ <% } %> + + <% if (successMessage != null) { %> +
+ Success: <%= successMessage %> +
+ <% } %> + + <% if (errorMessage != null) { %> +
+ Error: <%= errorMessage %> +
+ <% } %> + +

Student Profiles (Spring MVC)

+ +
+ Spring Framework Integration: This page is powered by Spring Framework 5.3 with Spring MVC! +
URL Pattern: /app/* (Spring servlet mapping) +
+ + + + + + + + + + + + <% + @SuppressWarnings("unchecked") + List students = (List) request.getAttribute("students"); + if (students != null && !students.isEmpty()) { + for (StudentProfile student : students) { + %> + + + + + + + <% } + } else { %> + + + + <% } %> + +
IDNameEmailMajor
<%= student.getId() %><%= student.getName() %><%= student.getEmail() %><%= student.getMajor() %>
+ <% if (students != null) { %> + No student profiles found. + <% } else { %> + Loading student data... + <% } %> +
+ +
+

Spring Technology Stack:

+ + +

Available Endpoints:

+ +
+ + diff --git a/jakarta-ee/student-web-app/assets/arch.png b/jakarta-ee/student-web-app/assets/arch.png new file mode 100644 index 0000000..e697ca8 Binary files /dev/null and b/jakarta-ee/student-web-app/assets/arch.png differ diff --git a/jakarta-ee/student-web-app/assets/assessment-ant-to-maven-solution.png b/jakarta-ee/student-web-app/assets/assessment-ant-to-maven-solution.png new file mode 100644 index 0000000..474c1b2 Binary files /dev/null and b/jakarta-ee/student-web-app/assets/assessment-ant-to-maven-solution.png differ diff --git a/jakarta-ee/student-web-app/assets/assessment-start.png b/jakarta-ee/student-web-app/assets/assessment-start.png new file mode 100644 index 0000000..303c775 Binary files /dev/null and b/jakarta-ee/student-web-app/assets/assessment-start.png differ diff --git a/jakarta-ee/student-web-app/assets/convert-ant-to-maven.png b/jakarta-ee/student-web-app/assets/convert-ant-to-maven.png new file mode 100644 index 0000000..e91a348 Binary files /dev/null and b/jakarta-ee/student-web-app/assets/convert-ant-to-maven.png differ diff --git a/jakarta-ee/student-web-app/assets/student_profiles_list.png b/jakarta-ee/student-web-app/assets/student_profiles_list.png new file mode 100644 index 0000000..b886637 Binary files /dev/null and b/jakarta-ee/student-web-app/assets/student_profiles_list.png differ diff --git a/jakarta-ee/student-web-app/build.properties b/jakarta-ee/student-web-app/build.properties new file mode 100644 index 0000000..cdcfdbb --- /dev/null +++ b/jakarta-ee/student-web-app/build.properties @@ -0,0 +1,26 @@ +# Build configuration properties +# You can override these properties by creating a local.properties file +# or by setting system properties when running Ant + +# Source and build directories +src.dir=src +web.dir=WebContent +build.dir=build +dist.dir=dist + +# Application name and WAR file +app.name=OpenLibertyApp +war.file=${dist.dir}/${app.name}.war + +# Library directories +lib.dir=lib +compile.lib.dir=compile-lib +webcontent.lib.dir=WebContent/WEB-INF/lib + +# External library directories (can be overridden) +dev.lib.dir=${user.home}/lib/dev +mysql.lib.dir=${user.home}/lib/mysql + +# Java compilation settings +java.source=11 +java.target=11 diff --git a/jakarta-ee/student-web-app/build.xml b/jakarta-ee/student-web-app/build.xml new file mode 100644 index 0000000..fa47eb8 --- /dev/null +++ b/jakarta-ee/student-web-app/build.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jakarta-ee/student-web-app/database/create_table.sql b/jakarta-ee/student-web-app/database/create_table.sql new file mode 100644 index 0000000..98c5588 --- /dev/null +++ b/jakarta-ee/student-web-app/database/create_table.sql @@ -0,0 +1,8 @@ + DROP TABLE IF EXISTS student_profiles; + + CREATE TABLE student_profiles ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255), + email VARCHAR(255), + major VARCHAR(255) + ); \ No newline at end of file diff --git a/jakarta-ee/student-web-app/doc/architecture.md b/jakarta-ee/student-web-app/doc/architecture.md new file mode 100644 index 0000000..4e47490 --- /dev/null +++ b/jakarta-ee/student-web-app/doc/architecture.md @@ -0,0 +1,142 @@ +## Architecture + +This is a Java EE web application running on Open Liberty with a hybrid architecture that supports both traditional servlets and Spring MVC. The application manages student profiles with CRUD operations. + +![Architecture Diagram](../assets/arch.png) + +### System Architecture + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Web Tier (Presentation) │ +├─────────────────────────────────────────────────────────────────┤ +│ Browser Client │ +│ ├── HTTP/HTTPS Requests │ +│ └── REST API Calls │ +└─────────────────┬───────────────────────────────────────────────┘ + │ +┌─────────────────▼───────────────────────────────────────────────┐ +│ Open Liberty Server │ +│ (Java EE Container) │ +├─────────────────────────────────────────────────────────────────┤ +│ Web Container (Servlet 4.0, JSP 2.3) │ +│ ├── Traditional Servlets │ +│ │ ├── IndexServlet (/) │ +│ │ ├── AddStudentServlet (/addStudent) │ +│ │ └── StudentProfileListServlet (/studentProfileList) │ +│ │ │ +│ ├── Spring MVC Framework │ +│ │ ├── DispatcherServlet (/app/*) │ +│ │ ├── StudentController │ +│ │ └── AddStudentController │ +│ │ │ +│ └── JSP Views │ +│ ├── index.jsp │ +│ ├── spring-index.jsp │ +│ └── add_student_profile.jsp │ +└─────────────────┬───────────────────────────────────────────────┘ + │ +┌─────────────────▼───────────────────────────────────────────────┐ +│ Business Logic Tier │ +├─────────────────────────────────────────────────────────────────┤ +│ Spring Services │ +│ ├── StudentService (@Service) │ +│ │ ├── getAllStudents() │ +│ │ └── saveStudent() │ +│ │ │ +│ └── Domain Models │ +│ └── StudentProfile (Entity) │ +└─────────────────┬───────────────────────────────────────────────┘ + │ +┌─────────────────▼───────────────────────────────────────────────┐ +│ Data Access Tier │ +├─────────────────────────────────────────────────────────────────┤ +│ iBATIS/MyBatis Integration │ +│ ├── MyBatisUtil (SqlMapClient Factory) │ +│ ├── sql-map-config.xml (Configuration) │ +│ └── Student_SqlMap.xml (SQL Mappings) │ +│ │ │ +│ └── JNDI DataSource (jdbc/StudentDB) │ +└─────────────────┬───────────────────────────────────────────────┘ + │ +┌─────────────────▼───────────────────────────────────────────────┐ +│ Database Tier │ +├─────────────────────────────────────────────────────────────────┤ +│ MySQL Database │ +│ ├── studentdb Database │ +│ ├── Connection Pool │ +│ └── Student Tables │ +└─────────────────────────────────────────────────────────────────┘ +``` + + + +### Technology Stack + +**Application Server:** +- Open Liberty (Java EE 8 Web Profile) +- Features: servlet-4.0, jsp-2.3, jdbc-4.3, jndi-1.0, javaMail-1.6 + +**Web Framework:** +- Dual approach: Traditional Servlets + Spring MVC 5.3.39 +- JSP for view rendering +- JSON responses via Jackson + +**Data Access:** +- iBATIS/MyBatis for ORM +- JNDI DataSource with connection pooling +- MySQL Connector/J 8.0.33 + +**Database:** +- MySQL 8.0 +- Database: `studentdb` +- Connection pooling (2-10 connections) + +**Build & Deployment:** +- Apache Ant for build automation +- Docker containerization +- Docker Compose for multi-service deployment + +### Application Components + +#### Web Layer +1. **Traditional Servlets** - Handle direct HTTP requests + - `IndexServlet`: Root path handler with student data + - `AddStudentServlet`: Student creation endpoint + - `StudentProfileListServlet`: Student listing with JSON output + +2. **Spring MVC Controllers** - Modern web layer + - `StudentController`: RESTful student operations + - `AddStudentController`: Form-based student creation + +#### Service Layer +- `StudentService`: Business logic for student operations +- Dependency injection via Spring IoC container +- Transaction management (implicit via iBATIS) + +#### Data Layer +- `MyBatisUtil`: SqlMapClient factory and session management +- SQL mappings in XML files for database operations +- JNDI-managed DataSource for connection pooling + +#### Configuration +- `web.xml`: Servlet and Spring configuration +- `server.xml`: Open Liberty server configuration +- `sql-map-config.xml`: iBATIS/MyBatis configuration +- Spring context files for dependency injection + +### Key Features +- **Hybrid Architecture**: Supports both traditional servlets and Spring MVC +- **JSON API**: REST endpoints with Jackson serialization +- **Connection Pooling**: Efficient database connection management +- **Containerized Deployment**: Docker and Docker Compose support +- **Security**: Transport security and application security features +- **Mail Integration**: JavaMail support for email functionality + +### Data Flow +1. Client sends HTTP request to Open Liberty +2. Request routed to appropriate servlet or Spring controller +3. Controller/Servlet calls service layer for business logic +4. Service layer uses MyBatis to execute database operations +5. Results formatted as HTML (JSP) or JSON response +6. Response sent back to client diff --git a/jakarta-ee/student-web-app/doc/getting-started.md b/jakarta-ee/student-web-app/doc/getting-started.md new file mode 100644 index 0000000..e996a0a --- /dev/null +++ b/jakarta-ee/student-web-app/doc/getting-started.md @@ -0,0 +1,57 @@ +## Getting Started + +### Prerequisites +- **Java 11** or higher +- **Apache Ant** (for building) +- **Docker & Docker Compose** (recommended) + +### Quick Start (Docker - Recommended) + +1. **Clone and navigate to the project:** + ```bash + cd student-web-app + ``` + +2. **Run setup script:** + ```bash + # Linux/Mac + ./setup-docker.sh + + # Windows + setup-docker.bat + ``` + +3. **Start the application:** + ```bash + docker-compose up --build + ``` + +4. **Access the application:** + - Home page: http://localhost:9080/ + +### Manual Setup (Without Docker) + +See [maunual setup](manual-setup.md). + +### Verification + +Test the application with: +```bash +# Add a student +curl -X POST "http://localhost:9080/app/add-student" \ + -d "name=Alice Johnson" \ + -d "email=alice@example.com" \ + -d "major=Biology" + +# View students +curl http://localhost:9080/studentProfileList +# or +# curl http://localhost:9080/app/students +``` + +**Student Profiles List View:** +Access the pages from browser: http://localhost:9080/ + +
+ Student Profiles List +
diff --git a/jakarta-ee/student-web-app/doc/manual-setup.md b/jakarta-ee/student-web-app/doc/manual-setup.md new file mode 100644 index 0000000..70595ce --- /dev/null +++ b/jakarta-ee/student-web-app/doc/manual-setup.md @@ -0,0 +1,28 @@ +1. **Install dependencies:** + - Java 11+ + - Apache Ant + - MySQL 8.0 + +2. **Setup database:** + ```bash + # Create database and user + mysql -u root -p + CREATE DATABASE studentdb; + CREATE USER 'student'@'localhost' IDENTIFIED BY 'studentpass'; + GRANT ALL PRIVILEGES ON studentdb.* TO 'student'@'localhost'; + + # Run table creation script + mysql -u student -p studentdb < database/create_table.sql + ``` + +3. **Configure MySQL connector:** + ```bash + mkdir -p mysql-connector + # Download MySQL Connector/J 8.0.33 to mysql-connector/mysql-connector-j-8.0.33.jar + ``` + +4. **Build and deploy:** + ```bash + ant clean war + # Deploy OpenLibertyApp.war to your Open Liberty server + ``` \ No newline at end of file diff --git a/jakarta-ee/student-web-app/docker-compose.yml b/jakarta-ee/student-web-app/docker-compose.yml new file mode 100644 index 0000000..b10e0f4 --- /dev/null +++ b/jakarta-ee/student-web-app/docker-compose.yml @@ -0,0 +1,45 @@ +services: + mysql: + image: mysql:8.0 + container_name: student-mysql + environment: + MYSQL_ROOT_PASSWORD: rootpassword + MYSQL_DATABASE: studentdb + MYSQL_USER: student + MYSQL_PASSWORD: studentpass + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + - ./database/create_table.sql:/docker-entrypoint-initdb.d/create_table.sql + networks: + - student-network + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] + timeout: 20s + retries: 10 + + app: + build: . + container_name: student-app + ports: + - "9080:9080" + - "9443:9443" + environment: + - JDBC_URL=jdbc:mysql://mysql:3306/studentdb?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC + - DB_USER=student + - DB_PASSWORD=studentpass + volumes: + - ./mysql-connector:/opt/ol/wlp/usr/shared/resources/mysql + networks: + - student-network + depends_on: + mysql: + condition: service_healthy + +volumes: + mysql_data: + +networks: + student-network: + driver: bridge diff --git a/jakarta-ee/student-web-app/liberty_config/server-docker.env b/jakarta-ee/student-web-app/liberty_config/server-docker.env new file mode 100644 index 0000000..1ce0270 --- /dev/null +++ b/jakarta-ee/student-web-app/liberty_config/server-docker.env @@ -0,0 +1,14 @@ +SESSION_COOKIE_NAME=student-app-session +CLONE_ID=docker-clone-id +SESSION_TIMEOUT=30m + +# Library paths for Docker environment +DEV_LIB_DIR=/opt/ol/wlp/usr/shared/resources/lib +MYSQL_LIB_DIR=/opt/ol/wlp/usr/shared/resources/mysql + +# Database configuration (will be overridden by docker-compose environment variables) +JDBC_URL=jdbc:mysql://mysql:3306/studentdb?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC +DB_USER=student +DB_PASSWORD=studentpass + +LTPA_SFA_EXPIRATION=3600 diff --git a/jakarta-ee/student-web-app/liberty_config/server-docker.xml b/jakarta-ee/student-web-app/liberty_config/server-docker.xml new file mode 100644 index 0000000..40601fa --- /dev/null +++ b/jakarta-ee/student-web-app/liberty_config/server-docker.xml @@ -0,0 +1,46 @@ + + + localConnector-1.0 + servlet-4.0 + jsp-2.3 + transportSecurity-1.0 + jaxws-2.2 + jdbc-4.3 + jndi-1.0 + javaMail-1.6 + appSecurity-3.0 + springBoot-2.0 + webProfile-8.0 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jakarta-ee/student-web-app/resources/applicationContext-service.xml b/jakarta-ee/student-web-app/resources/applicationContext-service.xml new file mode 100644 index 0000000..ee7b879 --- /dev/null +++ b/jakarta-ee/student-web-app/resources/applicationContext-service.xml @@ -0,0 +1,16 @@ + + + + + + + + classpath:ca/on/gov/edu/coreft/persistence/xml/ucm_schema.properties + + + + + diff --git a/jakarta-ee/student-web-app/resources/ca/on/gov/edu/msfaa/shared/persistence/xml/Student_SqlMap.xml b/jakarta-ee/student-web-app/resources/ca/on/gov/edu/msfaa/shared/persistence/xml/Student_SqlMap.xml new file mode 100644 index 0000000..68ef700 --- /dev/null +++ b/jakarta-ee/student-web-app/resources/ca/on/gov/edu/msfaa/shared/persistence/xml/Student_SqlMap.xml @@ -0,0 +1,17 @@ + + + + + + + + + + INSERT INTO student_profiles (name, email, major) VALUES (#name#, #email#, #major#) + + + \ No newline at end of file diff --git a/jakarta-ee/student-web-app/resources/log4j.properties b/jakarta-ee/student-web-app/resources/log4j.properties new file mode 100644 index 0000000..546ca5c --- /dev/null +++ b/jakarta-ee/student-web-app/resources/log4j.properties @@ -0,0 +1,15 @@ +# Log4j Configuration +log4j.rootLogger=INFO, stdout, rolling + +# Console appender configuration +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c - %m%n + +# File appender configuration +log4j.appender.rolling=org.apache.log4j.RollingFileAppender +log4j.appender.rolling.File=/logs/applog/osap/coreft1617/coreft1617sfa.log +log4j.appender.rolling.MaxFileSize=100000KB +log4j.appender.rolling.MaxBackupIndex=10 +log4j.appender.rolling.layout=org.apache.log4j.PatternLayout +log4j.appender.rolling.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c - %m%n \ No newline at end of file diff --git a/jakarta-ee/student-web-app/resources/sql-map-config.xml b/jakarta-ee/student-web-app/resources/sql-map-config.xml new file mode 100644 index 0000000..f884914 --- /dev/null +++ b/jakarta-ee/student-web-app/resources/sql-map-config.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/jakarta-ee/student-web-app/setup-docker.bat b/jakarta-ee/student-web-app/setup-docker.bat new file mode 100644 index 0000000..8a74e17 --- /dev/null +++ b/jakarta-ee/student-web-app/setup-docker.bat @@ -0,0 +1,64 @@ +@echo off +echo Setting up Docker environment for Student Web App... + +REM Create mysql-connector directory if it doesn't exist +if not exist "mysql-connector" mkdir mysql-connector + +REM Create compile-lib directory if it doesn't exist +if not exist "compile-lib" mkdir compile-lib + +REM Download compile-time dependencies if not present +if not exist "compile-lib\servlet-api.jar" ( + echo Downloading Servlet API... + curl -L -o compile-lib\servlet-api.jar https://repo1.maven.org/maven2/javax/servlet/javax.servlet-api/4.0.1/javax.servlet-api-4.0.1.jar + + if %errorlevel% equ 0 ( + echo Servlet API downloaded successfully + ) else ( + echo Failed to download Servlet API + ) +) + +if not exist "compile-lib\javamail-api.jar" ( + echo Downloading JavaMail API... + curl -L -o compile-lib\javamail-api.jar https://repo1.maven.org/maven2/javax/mail/javax.mail-api/1.6.2/javax.mail-api-1.6.2.jar + + if %errorlevel% equ 0 ( + echo JavaMail API downloaded successfully + ) else ( + echo Failed to download JavaMail API + ) +) + +REM Check if MySQL Connector/J exists +if not exist "mysql-connector\mysql-connector-j-8.0.33.jar" ( + echo Downloading MySQL Connector/J... + curl -L -o mysql-connector\mysql-connector-j-8.0.33.jar https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.33/mysql-connector-j-8.0.33.jar + + if %errorlevel% equ 0 ( + echo MySQL Connector/J downloaded successfully + ) else ( + echo Failed to download MySQL Connector/J. Please download it manually: + echo URL: https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.33/mysql-connector-j-8.0.33.jar + echo Save it to: mysql-connector\mysql-connector-j-8.0.33.jar + ) +) else ( + echo MySQL Connector/J already exists +) + +REM Build the application +echo Building application... +ant clean war + +if %errorlevel% equ 0 ( + echo Application built successfully + echo. + echo Ready to run with Docker! + echo Use: docker-compose up --build + echo. + echo Once running, access the application at: + echo - Student: http://localhost:9080/ +) else ( + echo Failed to build application + exit /b 1 +) diff --git a/jakarta-ee/student-web-app/setup-docker.sh b/jakarta-ee/student-web-app/setup-docker.sh new file mode 100755 index 0000000..652a969 --- /dev/null +++ b/jakarta-ee/student-web-app/setup-docker.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# Setup script for Docker environment + +echo "Setting up Docker environment for Student Web App..." + +# Create mysql-connector directory if it doesn't exist +mkdir -p mysql-connector + +# Create compile-lib directory if it doesn't exist +mkdir -p compile-lib + +# Download compile-time dependencies if not present +if [ ! -f "compile-lib/servlet-api.jar" ]; then + echo "Downloading Servlet API..." + curl -L -o compile-lib/servlet-api.jar \ + https://repo1.maven.org/maven2/javax/servlet/javax.servlet-api/4.0.1/javax.servlet-api-4.0.1.jar + + if [ $? -eq 0 ]; then + echo "Servlet API downloaded successfully" + else + echo "Failed to download Servlet API" + fi +fi + +if [ ! -f "compile-lib/javamail-api.jar" ]; then + echo "Downloading JavaMail API..." + curl -L -o compile-lib/javamail-api.jar \ + https://repo1.maven.org/maven2/javax/mail/javax.mail-api/1.6.2/javax.mail-api-1.6.2.jar + + if [ $? -eq 0 ]; then + echo "JavaMail API downloaded successfully" + else + echo "Failed to download JavaMail API" + fi +fi + +# Download MySQL Connector/J if not present +if [ ! -f "mysql-connector/mysql-connector-j-8.0.33.jar" ]; then + echo "Downloading MySQL Connector/J..." + curl -L -o mysql-connector/mysql-connector-j-8.0.33.jar \ + https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/8.0.33/mysql-connector-j-8.0.33.jar + + if [ $? -eq 0 ]; then + echo "MySQL Connector/J downloaded successfully" + else + echo "Failed to download MySQL Connector/J. Please download it manually:" + echo "URL: https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/8.0.33/mysql-connector-j-8.0.33.jar" + echo "Save it to: mysql-connector/mysql-connector-j-8.0.33.jar" + fi +else + echo "MySQL Connector/J already exists" +fi + +# Build the application +echo "Building application..." +ant clean war + +if [ $? -eq 0 ]; then + echo "Application built successfully" + echo "" + echo "Ready to run with Docker!" + echo "Use: docker-compose up --build" + echo "" + echo "Once running, access the application at:" + echo "- Student: http://localhost:9080/" +else + echo "Failed to build application" + exit 1 +fi diff --git a/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/AddStudentServlet.java b/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/AddStudentServlet.java new file mode 100644 index 0000000..784d0f2 --- /dev/null +++ b/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/AddStudentServlet.java @@ -0,0 +1,107 @@ +package ca.on.gov.edu.coreft; + +import ca.on.gov.edu.coreft.util.MyBatisUtil; +import com.ibatis.sqlmap.client.SqlMapSession; +import org.apache.log4j.Logger; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.mail.Message; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; + +public class AddStudentServlet extends HttpServlet { + + private static final Logger logger = Logger.getLogger(AddStudentServlet.class); + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + logger.info("Displaying add student form"); + // Forward to the add student form JSP + request.getRequestDispatcher("/add_student_profile.jsp").forward(request, response); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + String name = request.getParameter("name"); + String email = request.getParameter("email"); + String major = request.getParameter("major"); + boolean success = false; + String errorMsg = null; + SqlMapSession session = null; + + try { + logger.info("Starting to add student: name=" + name + ", email=" + email + ", major=" + major); + session = MyBatisUtil.getSqlMapClient().openSession(); + session.startTransaction(); + + Map params = new HashMap<>(); + params.put("name", name); + params.put("email", email); + params.put("major", major); + + session.insert("com.azure.sample.StudentMapper.addStudent", params); + session.commitTransaction(); + success = true; + + logger.info("Student added successfully, sending email to: " + email); + // Send email notification + sendEmail(email, name); + + } catch (Exception e) { + logger.error("Error adding student: " + e.getMessage(), e); + errorMsg = e.getMessage(); + if (session != null) { + try { + session.endTransaction(); + } catch (Exception rollbackEx) { + logger.error("Error ending transaction: " + rollbackEx.getMessage(), rollbackEx); + } + } + } finally { + if (session != null) { + try { + session.close(); + } catch (Exception e) { + logger.error("Error closing session: " + e.getMessage(), e); + } + } + } + + if (success) { + logger.info("Redirecting to HelloServlet after successful add."); + response.setContentType("text/html"); + response.getWriter().write("

Student added successfully!

"); + response.getWriter().write("

View All Student Profiles

"); + response.getWriter().write("

Add Another Student

"); + } else { + logger.warn("Add student failed: " + errorMsg); + request.setAttribute("errorMsg", errorMsg != null ? errorMsg : "Failed to add student."); + request.getRequestDispatcher("/add_student_profile.jsp").forward(request, response); + } + } + + private void sendEmail(String to, String name) throws Exception { + logger.info("Preparing to send email to: " + to); + // Lookup mail session from JNDI (configured in server.xml) + Context ctx = new InitialContext(); + Session session = (Session) ctx.lookup("java:comp/env/mail/StudentMailSession"); + Message msg = new MimeMessage(session); + msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to, false)); + msg.setSubject("Welcome, " + name + "!"); + msg.setText("Dear " + name + ",\n\nYour student profile has been created successfully.\n\nRegards,\nAdmin"); + Transport.send(msg); + logger.info("Email sent to: " + to); + } +} diff --git a/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/IndexServlet.java b/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/IndexServlet.java new file mode 100644 index 0000000..8f8574f --- /dev/null +++ b/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/IndexServlet.java @@ -0,0 +1,55 @@ +package ca.on.gov.edu.coreft; + +import ca.on.gov.edu.coreft.util.MyBatisUtil; +import com.ibatis.sqlmap.client.SqlMapSession; +import org.apache.log4j.Logger; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + +/** + * Servlet to handle the root path and populate index.jsp with student data + */ +public class IndexServlet extends HttpServlet { + + private static final Logger logger = Logger.getLogger(IndexServlet.class); + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + logger.info("Processing request for index page with student data"); + + SqlMapSession session = null; + try { + // Get student data + session = MyBatisUtil.getSqlMapClient().openSession(); + List students = (List) session.queryForList("com.azure.sample.StudentMapper.listStudent"); + + // Set attributes for the JSP + request.setAttribute("students", students); + logger.info("Successfully loaded " + students.size() + " students for index page"); + + } catch (Exception ex) { + logger.error("Error loading students for index page: " + ex.getMessage(), ex); + // Set error message and empty list + request.setAttribute("error", "Unable to load student data: " + ex.getMessage()); + request.setAttribute("students", null); + } finally { + if (session != null) { + try { + session.close(); + } catch (Exception e) { + logger.error("Error closing session: " + e.getMessage(), e); + } + } + } + + // Forward to the JSP + request.getRequestDispatcher("/index.jsp").forward(request, response); + } +} diff --git a/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/StudentProfile.java b/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/StudentProfile.java new file mode 100644 index 0000000..34290bc --- /dev/null +++ b/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/StudentProfile.java @@ -0,0 +1,120 @@ +package ca.on.gov.edu.coreft; + +import java.io.Serializable; + +/** + * Entity class representing a student profile. + * Contains basic information about a student including ID, name, email, and major. + */ +public class StudentProfile implements Serializable { + + private static final long serialVersionUID = 1L; + + private int id; + private String name; + private String email; + private String major; + + /** + * Default constructor for StudentProfile. + */ + public StudentProfile() { + } + + /** + * Constructor with all fields. + * + * @param id the student ID + * @param name the student name + * @param email the student email + * @param major the student major + */ + public StudentProfile(int id, String name, String email, String major) { + this.id = id; + this.name = name; + this.email = email; + this.major = major; + } + + /** + * Gets the student ID. + * + * @return the student ID + */ + public int getId() { + return id; + } + + /** + * Sets the student ID. + * + * @param id the student ID to set + */ + public void setId(int id) { + this.id = id; + } + + /** + * Gets the student name. + * + * @return the student name + */ + public String getName() { + return name; + } + + /** + * Sets the student name. + * + * @param name the student name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * Gets the student email. + * + * @return the student email + */ + public String getEmail() { + return email; + } + + /** + * Sets the student email. + * + * @param email the student email to set + */ + public void setEmail(String email) { + this.email = email; + } + + /** + * Gets the student major. + * + * @return the student major + */ + public String getMajor() { + return major; + } + + /** + * Sets the student major. + * + * @param major the student major to set + */ + public void setMajor(String major) { + this.major = major; + } + + @Override + public String toString() { + return "StudentProfile{" + + "id=" + id + + ", name='" + name + '\'' + + ", email='" + email + '\'' + + ", major='" + major + '\'' + + '}'; + } +} diff --git a/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/StudentProfileListServlet.java b/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/StudentProfileListServlet.java new file mode 100644 index 0000000..a8ee5c5 --- /dev/null +++ b/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/StudentProfileListServlet.java @@ -0,0 +1,71 @@ +package ca.on.gov.edu.coreft; + +import ca.on.gov.edu.coreft.util.MyBatisUtil; +import com.ibatis.sqlmap.client.SqlMapSession; +import org.apache.log4j.Logger; +import org.codehaus.jackson.map.ObjectMapper; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class StudentProfileListServlet extends HttpServlet { + + private static final Logger logger = Logger.getLogger(StudentProfileListServlet.class); + + private final ObjectMapper objectMapper; + + public StudentProfileListServlet() { + objectMapper = new ObjectMapper(); + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + logger.info("Start to list student profile list"); + response.setContentType("text/html;charset=UTF-8"); + + try (PrintWriter out = response.getWriter()) { + out.println("Student Profile List"); + out.println("

Student Profile List

"); + + SqlMapSession session = null; + try { + session = MyBatisUtil.getSqlMapClient().openSession(); + + @SuppressWarnings("unchecked") + List students = (List) session.queryForList("com.azure.sample.StudentMapper.listStudent"); + + out.println(""); + for (StudentProfile student : students) { + out.println("" + + "" + + "" + + ""); + } + out.println("
IDNameEmailMajor
" + student.getId() + "" + student.getName() + "" + student.getEmail() + "" + student.getMajor() + "
"); + out.println("


"); + out.println(objectMapper.writeValueAsString(students)); + + } catch (Exception ex) { + logger.error("Error retrieving student list: " + ex.getMessage(), ex); + out.println("

Error: " + ex.getMessage() + "

"); + throw new RuntimeException(ex); + } finally { + if (session != null) { + try { + session.close(); + } catch (Exception e) { + logger.error("Error closing session: " + e.getMessage(), e); + } + } + } + out.println(""); + } + } +} diff --git a/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/controller/AddStudentController.java b/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/controller/AddStudentController.java new file mode 100644 index 0000000..17bce81 --- /dev/null +++ b/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/controller/AddStudentController.java @@ -0,0 +1,58 @@ +package ca.on.gov.edu.coreft.controller; + +import ca.on.gov.edu.coreft.StudentProfile; +import ca.on.gov.edu.coreft.service.StudentService; +import org.apache.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +@Controller +public class AddStudentController { + + private static final Logger logger = Logger.getLogger(AddStudentController.class); + + @Autowired + private StudentService studentService; + + @GetMapping("/add-student") + public String showAddStudentForm() { + return "spring-add-student"; + } + + @PostMapping("/add-student") + public String addStudent( + @RequestParam("name") String name, + @RequestParam("email") String email, + @RequestParam("major") String major, + RedirectAttributes redirectAttributes) { + + logger.info("Adding new student: " + name + ", " + email + ", " + major); + + try { + // Save the student to the database using StudentService + boolean success = studentService.saveStudent(name, email, major); + + if (success) { + logger.info("Student saved successfully: " + name); + redirectAttributes.addFlashAttribute("successMessage", + "Student " + name + " has been added successfully!"); + } else { + logger.warn("Failed to save student: " + name); + redirectAttributes.addFlashAttribute("errorMessage", + "Failed to save student. Please try again."); + } + + } catch (Exception e) { + logger.error("Error adding student: " + e.getMessage(), e); + redirectAttributes.addFlashAttribute("errorMessage", + "Error adding student: " + e.getMessage()); + } + + return "redirect:/app/"; + } +} diff --git a/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/controller/StudentController.java b/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/controller/StudentController.java new file mode 100644 index 0000000..27800f9 --- /dev/null +++ b/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/controller/StudentController.java @@ -0,0 +1,55 @@ +package ca.on.gov.edu.coreft.controller; + +import ca.on.gov.edu.coreft.StudentProfile; +import ca.on.gov.edu.coreft.service.StudentService; +import org.apache.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +import java.util.List; + +@Controller +public class StudentController { + + private static final Logger logger = Logger.getLogger(StudentController.class); + + @Autowired + private StudentService studentService; + + @GetMapping("/") + public String index(Model model) { + logger.info("Handling index page request"); + + try { + List students = studentService.getAllStudents(); + model.addAttribute("students", students); + logger.info("Added " + students.size() + " students to model"); + } catch (Exception e) { + logger.error("Error loading students for index page: " + e.getMessage(), e); + // Add empty list in case of error + model.addAttribute("students", List.of()); + model.addAttribute("error", "Unable to load student data: " + e.getMessage()); + } + + return "spring-index"; + } + + @GetMapping("/students") + public String listStudents(Model model) { + logger.info("Handling students list request"); + + try { + List students = studentService.getAllStudents(); + model.addAttribute("students", students); + logger.info("Added " + students.size() + " students to model"); + } catch (Exception e) { + logger.error("Error loading students: " + e.getMessage(), e); + model.addAttribute("students", List.of()); + model.addAttribute("error", "Unable to load student data: " + e.getMessage()); + } + + return "spring-index"; + } +} diff --git a/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/filter/CommonHttpServletFilter.java b/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/filter/CommonHttpServletFilter.java new file mode 100644 index 0000000..28a327a --- /dev/null +++ b/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/filter/CommonHttpServletFilter.java @@ -0,0 +1,70 @@ +package ca.on.gov.edu.coreft.filter; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +/** + * Filter to extract real client IP address from HTTP headers. + * Handles X-Forwarded-For and X-Client-IP headers for proper client identification + * in load-balanced environments. + */ +public class CommonHttpServletFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // Filter initialization logic, if needed + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + if (request instanceof HttpServletRequest) { + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + + // Extract real client IP from headers + String realClientIP = extractRealClientIP(httpServletRequest); + + // Add the real client IP as a request attribute for downstream usage + httpServletRequest.setAttribute("RealClientIP", realClientIP); + } + + // Proceed with the filter chain + chain.doFilter(request, response); + } + + @Override + public void destroy() { + // Cleanup logic, if needed + } + + /** + * Extracts the real client IP address from HTTP headers. + * + * @param request the HTTP servlet request + * @return the real client IP address + */ + private String extractRealClientIP(HttpServletRequest request) { + // Check X-Forwarded-For header first + String xForwardedFor = request.getHeader("X-Forwarded-For"); + if (xForwardedFor != null && !xForwardedFor.isEmpty()) { + // X-Forwarded-For header can contain a comma-separated list of IPs + return xForwardedFor.split(",")[0].trim(); // Take the first IP + } + + // Check X-Client-IP header + String xClientIP = request.getHeader("X-Client-IP"); + if (xClientIP != null && !xClientIP.isEmpty()) { + return xClientIP; // Use X-Client-IP directly if available + } + + // Fallback to the remote address as last resort + return request.getRemoteAddr(); + } +} diff --git a/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/service/StudentService.java b/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/service/StudentService.java new file mode 100644 index 0000000..91817ed --- /dev/null +++ b/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/service/StudentService.java @@ -0,0 +1,88 @@ +package ca.on.gov.edu.coreft.service; + +import ca.on.gov.edu.coreft.StudentProfile; +import ca.on.gov.edu.coreft.util.MyBatisUtil; +import com.ibatis.sqlmap.client.SqlMapSession; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class StudentService { + + private static final Logger logger = Logger.getLogger(StudentService.class); + + public List getAllStudents() { + logger.info("Getting all students from database"); + SqlMapSession session = null; + List students = new ArrayList<>(); + + try { + session = MyBatisUtil.getSqlMapClient().openSession(); + students = (List) session.queryForList("com.azure.sample.StudentMapper.listStudent"); + logger.info("Retrieved " + students.size() + " students"); + } catch (Exception ex) { + logger.error("Error retrieving students: " + ex.getMessage(), ex); + // Return empty list in case of error + students = new ArrayList<>(); + } finally { + if (session != null) { + try { + session.close(); + } catch (Exception e) { + logger.error("Error closing session: " + e.getMessage(), e); + } + } + } + + return students; + } + + public boolean saveStudent(String name, String email, String major) { + logger.info("Saving student to database: " + name + ", " + email + ", " + major); + SqlMapSession session = null; + boolean success = false; + + try { + session = MyBatisUtil.getSqlMapClient().openSession(); + session.startTransaction(); + + // Create parameter map for the insert operation + Map parameters = new HashMap<>(); + parameters.put("name", name); + parameters.put("email", email); + parameters.put("major", major); + + // Execute the insert + session.insert("com.azure.sample.StudentMapper.addStudent", parameters); + session.commitTransaction(); + + logger.info("Student saved successfully: " + name); + success = true; + + } catch (Exception ex) { + logger.error("Error saving student: " + ex.getMessage(), ex); + if (session != null) { + try { + session.endTransaction(); + } catch (Exception rollbackEx) { + logger.error("Error ending transaction: " + rollbackEx.getMessage(), rollbackEx); + } + } + } finally { + if (session != null) { + try { + session.close(); + } catch (Exception e) { + logger.error("Error closing session: " + e.getMessage(), e); + } + } + } + + return success; + } +} diff --git a/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/util/MyBatisUtil.java b/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/util/MyBatisUtil.java new file mode 100644 index 0000000..4848211 --- /dev/null +++ b/jakarta-ee/student-web-app/src/ca/on/gov/edu/coreft/util/MyBatisUtil.java @@ -0,0 +1,37 @@ +package ca.on.gov.edu.coreft.util; + +import com.ibatis.common.resources.Resources; +import com.ibatis.sqlmap.client.SqlMapClient; +import com.ibatis.sqlmap.client.SqlMapClientBuilder; + +import java.io.Reader; + +public class MyBatisUtil { + private static SqlMapClient sqlMapClient; + private static Exception initializationException; + + static { + try { + System.out.println("Initializing MyBatisUtil..."); + Reader reader = Resources.getResourceAsReader("sql-map-config.xml"); + sqlMapClient = SqlMapClientBuilder.buildSqlMapClient(reader); + System.out.println("SqlMapClient initialized successfully!"); + + } catch (Exception e) { + initializationException = e; + System.err.println("Failed to initialize SqlMapClient: " + e.getMessage()); + e.printStackTrace(); + } + } + + public static SqlMapClient getSqlMapClient() { + if (sqlMapClient == null) { + throw new RuntimeException("SqlMapClient was not properly initialized. " + + (initializationException != null ? + "Initialization error: " + initializationException.getMessage() : + "Unknown initialization error"), + initializationException); + } + return sqlMapClient; + } +} \ No newline at end of file