diff --git a/.gitignore b/.gitignore index 1de5659..4b34f0c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -target \ No newline at end of file +target +Help.md \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 01/README.md b/Week 04/Lecture 07/Assignment 01/README.md new file mode 100644 index 0000000..d03c603 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 01/README.md @@ -0,0 +1,286 @@ +# πŸ‘©πŸ»β€πŸ« Lecture 07 - Spring Core +> This repository is created as a part of assignment for Lecture 07 - Spring Core + +## 🌱 Assignment 01 - Dependency Injection and Bean Annotations (Part 1) +### πŸ€” Task 1 - Advantages and Drawbacks of Dependency Injection (DI) +#### πŸ“Œ What is Dependency Injection? +Dependency Injection is a design pattern used in software development to **achieve Inversion of Control (IoC)** between classes and their dependencies. Instead of a class creating its own dependencies (or dependent objects), DI allows those dependencies to be injected from an external source. This promotes loose coupling, improves code maintainability, and facilitates testing. + +#### ❓ How Dependency Injection Works +To understand DI, let's look at a simple analogy and then dive into a practical example. + +Imagine you are a coffee shop owner. Instead of the coffee shop making its own coffee beans (dependency), it gets coffee beans delivered from a supplier. If you want to change the type of beans, you just switch the supplier, without changing anything inside the coffee shop. + +In programming, this means that a class (like the coffee shop) receives its dependencies (like coffee beans) from an external source rather than creating them itself. + +#### βœ… Advantages of Dependency Injection +1. **Decoupling of Components** + + DI promotes loose coupling between components. Instead of a class creating its dependencies, they are provided externally. This allows components to be developed, tested, and maintained independently of one another. + + **Example**: + ```java + // Without DI + public class Car { + private Engine engine = new Engine(); // Tightly coupled to Engine class + } + + // With DI + public class Car { + private Engine engine; + public Car(Engine engine) { + this.engine = engine; // Engine is injected, allowing for different implementations + } + } + ``` + In the DI example, `Car` can work with any implementation of `Engine`, not just a specific one. + +2. **Ease of Testing** + + DI makes it easier to substitute dependencies with mock objects, facilitating unit testing. + + **Example**: + ```java + // Car class with DI + public class Car { + private Engine engine; + public Car(Engine engine) { + this.engine = engine; + } + public void start() { + engine.run(); + } + } + + // Mock Engine for testing + public class MockEngine extends Engine { + public void run() { + // Mock behavior for testing + } + } + + // Test + Car car = new Car(new MockEngine()); + car.start(); // Uses mock engine + ``` + +3. **Flexibility and Configurability** + + DI allows the system to be easily reconfigured or extended by changing the external configuration, without modifying the application code. + + **Example**: + - **XML Configuration** + + ```xml + + ``` + - **Java Configuration** + + ```java + @Bean + public Engine engine() { + return new DieselEngine(); + } + ``` + +4. **Improved Readability and Maintainability** + + Dependencies are clearly visible in the constructor or setter methods, making the code more readable and easier to maintain. + + **Example**: + ```java + public class Service { + private Repository repository; + public Service(Repository repository) { + this.repository = repository; // Dependency is visible here + } + } + ``` + It’s clear from the constructor that `Service` depends on `Repository`. + +5. **Centralized Dependency Management** + + DI frameworks often allow centralized management of dependencies through configuration files or classes, improving control over dependency lifecycle and configuration. + + **Example**: + ```java + @Configuration + public class AppConfig { + @Bean + public Service service() { + return new Service(repository()); + } + + @Bean + public Repository repository() { + return new RepositoryImpl(); + } + } + ``` + All dependencies are managed in `AppConfig`, simplifying changes. + +#### ❌ **Drawbacks of Dependency Injection** + +1. **Steep Learning Curve** + + DI introduces new concepts and patterns that can be difficult for beginners to grasp, especially when using complex DI frameworks. + + **Example**: + Understanding concepts like bean scopes, lifecycle callbacks, or proxying in Spring can be challenging for newcomers. + +2. **Configuration Overhead** + + Extensive configuration can be required, especially in XML-based DI frameworks, leading to verbosity and potential errors. + + **Example**: + ```xml + + + + + + ``` + This overhead is often mitigated by using annotations, but XML can still be verbose. + +3. **Performance Considerations** + + DI frameworks introduce a performance overhead due to reflection or dynamic proxies used to resolve and inject dependencies. + + **Example**: + Creating a proxy for a class or initializing beans lazily can introduce performance hits compared to direct instantiation. + +4. **Difficulty in Debugging**: + + Errors in DI configuration or dependency resolution can be hard to debug because they occur outside the normal control flow. + + **Example**: + Misconfigured beans or missing dependencies might only become evident at runtime, and the stack trace can be less informative. + +5. **Overhead for Simple Applications**: + + DI can be overkill for small applications where the benefits of loose coupling and testability are outweighed by the complexity it introduces. + + **Example**: + For a simple command-line application or script, manually managing dependencies might be simpler and more straightforward than setting up a DI framework. + +**Dependency Injection** provides significant benefits like decoupling, testability, and flexibility but comes with its own set of challenges like complexity, configuration overhead, and potential performance issues. The choice to use DI should consider the size and complexity of the project, team expertise, and specific needs for modularity and maintainability. + +--- + +### πŸ” Task 2 - Create `Employee` Class and Convert XML Bean Declaration to Java Configuration +In this task, i need to: +1. Create a class `Employee`. +2. Use Java configuration to replace XML-based configuration for beans. +3. Use constructor-based injection. + +#### 🐾 Step-by-Step Explanation +1. Create the `Employee` and `EmployeeWork` Classes + - `Employee` class will have attributes like `id`, `name`, `age`, and an `EmployeeWork` instance. + - `EmployeeWork` class will contain a method that represents work. +2. Convert XML Configuration to Java Configuration + - Define beans using `@Configuration` and `@Bean` annotations. + - Use constructor-based injection to inject dependencies. + +#### ➑️ Configuration Conversion +**Original XML Configuration** +```xml + + + + + + + + +``` + +**Equivalent Java Configuration** +```java +package com.helen.demo.config; + +import com.helen.demo.entity.Employee; +import com.helen.demo.EmployeeWork; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AppConfig { + + @Bean + public EmployeeWork employeeWork() { + return new EmployeeWork(); + } + + @Bean + public Employee employee() { + return new Employee("GL", employeeWork()); + } +} +``` + +But since on the slide is mentioned that the `Employee` class will have attribute `id`, `name`, and `class`, some classes need to be modified. + +#### πŸ‘¨β€πŸ’» Implementation +1. [**`Employee` Class**](/Week%2004/Lecture%2007/Assignment%2001/lecture_7/src/main/java/com/example/lecture_7/entity/Employee.java) + - **Constructor Injection**: The constructor of `Employee` takes `id`, `name`, `age`, and `EmployeeWork` as parameters. This ensures that all these dependencies are provided when the `Employee` object is created. +2. [**`EmployeeWork` Class**](/Week%2004/Lecture%2007/Assignment%2001/lecture_7/src/main/java/com/example/lecture_7/EmployeeWork.java) + - **Simple Dependency**: This class has a single method, `work()`, which prints a message. It represents a task that an employee performs. +3. [**`AppConfig` Class**](/Week%2004/Lecture%2007/Assignment%2001/lecture_7/src/main/java/com/example/lecture_7/config/AppConfig.java) + - **Java Configuration**: This class is annotated with `@Configuration`, indicating that it contains bean definitions. + - **Bean Methods**: + - **`employeeWork()`**: Defines the `EmployeeWork` bean. + - **`employee()`**: Defines the `Employee` bean and injects the `EmployeeWork` bean using the constructor. It also sets values for `id`, `name`, and `age`. +4. [**`Lecture7Application` Class**](/Week%2004/Lecture%2007/Assignment%2001/lecture_7/src/main/java/com/example/lecture_7/Lecture7Application.java) + - **Main Method**: Initializes the Spring context using `AnnotationConfigApplicationContext` and retrieves the `Employee` bean. It then calls `employee.working()`, demonstrating that the `Employee` bean has been correctly instantiated with its dependencies injected. + +#### βš™οΈ How to run the program +1. Go to the `lecture_7` directory by using this command + ```bash + $ cd lecture_7 + ``` +2. Make sure you have maven installed on your computer, use `mvn -v` to check the version. +3. If you are using windows, you can run the program by using this command. + ```bash + $ ./run.bat + ``` + And if you are using Linux, you can run the program by using this command. + ```bash + $ chmod +x run.sh + $ ./run.sh + ``` + +If all the instruction is well executed, the result will be something like this. + +![Screenshot](img/result.png) + +--- + +### πŸ’‰ Task 3 - Setter-Based Dependency Injection Using `@Configuration` +#### 🐾 Step-by-Step Explanation +1. Modify the `Employee` Class + - Add setter methods for the `name` and `employeeWork` fields. + - Ensure there is a no-argument constructor. +2. Update `AppConfig` for Setter Injection + + Use setters in the bean configuration methods to inject dependencies. + +#### πŸ‘¨β€πŸ’» Updated Code +1. [**`Employee` Class**](/Week%2004/Lecture%2007/Assignment%2001/lecture_7/src/main/java/com/example/lecture_7/entity/Employee.java) + - Modified to include setters (`setId`, `setName`, `setAge` and `setEmployeeWork`) for setting the `id`, `name`, `age`, and `employeeWork` dependencies. + - A no-argument constructor is added to allow the creation of the object without immediately needing dependencies. +2. [**`AppConfig` Class**](/Week%2004/Lecture%2007/Assignment%2001/lecture_7/src/main/java/com/example/lecture_7/config/AppConfig.java) + - Uses setter methods to inject dependencies into the `Employee` bean after its creation. + - Instantiates the `Employee` object and then sets the `id`, `name`, `age`, and `employeeWork` properties using the respective setters. + +#### βš™οΈ How to run the program +You can use guide on how to run the program like the previous task, and the result must be showing the same thing + +![Screenshot](img/result.png) + +#### πŸ”‘ Key Differences Between Constructor and Setter Injection +1. **Constructor Injection** + - Dependencies are provided when the object is created, making it impossible to create the object without its dependencies. + - Generally preferred for mandatory dependencies, ensuring the object is always in a valid state. +2. **Setter Injection** + - Dependencies can be provided after the object is created, allowing the object to be created in an incomplete state. + - Useful for optional dependencies or when dependencies are not known at creation time. \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 01/img/result.png b/Week 04/Lecture 07/Assignment 01/img/result.png new file mode 100644 index 0000000..a33d76c Binary files /dev/null and b/Week 04/Lecture 07/Assignment 01/img/result.png differ diff --git a/Week 04/Lecture 07/Assignment 01/lecture_7/.gitignore b/Week 04/Lecture 07/Assignment 01/lecture_7/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/Week 04/Lecture 07/Assignment 01/lecture_7/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/Week 04/Lecture 07/Assignment 01/lecture_7/.mvn/wrapper/maven-wrapper.properties b/Week 04/Lecture 07/Assignment 01/lecture_7/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..8f96f52 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 01/lecture_7/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip diff --git a/Week 04/Lecture 07/Assignment 01/lecture_7/mvnw b/Week 04/Lecture 07/Assignment 01/lecture_7/mvnw new file mode 100644 index 0000000..d7c358e --- /dev/null +++ b/Week 04/Lecture 07/Assignment 01/lecture_7/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/Week 04/Lecture 07/Assignment 01/lecture_7/mvnw.cmd b/Week 04/Lecture 07/Assignment 01/lecture_7/mvnw.cmd new file mode 100644 index 0000000..6f779cf --- /dev/null +++ b/Week 04/Lecture 07/Assignment 01/lecture_7/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/Week 04/Lecture 07/Assignment 01/lecture_7/pom.xml b/Week 04/Lecture 07/Assignment 01/lecture_7/pom.xml new file mode 100644 index 0000000..7c20fa1 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 01/lecture_7/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.1 + + + com.example + lecture_7 + 0.0.1-SNAPSHOT + lecture_7 + Demo project for Spring Boot + + + + + + + + + + + + + + + 21 + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/Week 04/Lecture 07/Assignment 01/lecture_7/run.bat b/Week 04/Lecture 07/Assignment 01/lecture_7/run.bat new file mode 100644 index 0000000..117cdac --- /dev/null +++ b/Week 04/Lecture 07/Assignment 01/lecture_7/run.bat @@ -0,0 +1,3 @@ +@echo off +echo Building the project with Maven... +mvn clean install && java -jar target/lecture_7-0.0.1-SNAPSHOT.jar \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 01/lecture_7/run.sh b/Week 04/Lecture 07/Assignment 01/lecture_7/run.sh new file mode 100644 index 0000000..127b819 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 01/lecture_7/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +echo Building the project with Maven... +mvn clean install && java -jar target/lecture_7-0.0.1-SNAPSHOT.jar \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 01/lecture_7/src/main/java/com/example/lecture_7/EmployeeWork.java b/Week 04/Lecture 07/Assignment 01/lecture_7/src/main/java/com/example/lecture_7/EmployeeWork.java new file mode 100644 index 0000000..cfd1b58 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 01/lecture_7/src/main/java/com/example/lecture_7/EmployeeWork.java @@ -0,0 +1,7 @@ +package com.example.lecture_7; + +public class EmployeeWork { + public void work() { + System.out.println("Working ..."); + } +} diff --git a/Week 04/Lecture 07/Assignment 01/lecture_7/src/main/java/com/example/lecture_7/Lecture7Application.java b/Week 04/Lecture 07/Assignment 01/lecture_7/src/main/java/com/example/lecture_7/Lecture7Application.java new file mode 100644 index 0000000..e2642b2 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 01/lecture_7/src/main/java/com/example/lecture_7/Lecture7Application.java @@ -0,0 +1,16 @@ +package com.example.lecture_7; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import com.example.lecture_7.config.AppConfig; +import com.example.lecture_7.entity.Employee; + +public class Lecture7Application { + public static void main(String[] args) { + @SuppressWarnings("resource") + ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); + Employee employee = context.getBean(Employee.class); + employee.working(); + } +} diff --git a/Week 04/Lecture 07/Assignment 01/lecture_7/src/main/java/com/example/lecture_7/config/AppConfig.java b/Week 04/Lecture 07/Assignment 01/lecture_7/src/main/java/com/example/lecture_7/config/AppConfig.java new file mode 100644 index 0000000..88e1b4c --- /dev/null +++ b/Week 04/Lecture 07/Assignment 01/lecture_7/src/main/java/com/example/lecture_7/config/AppConfig.java @@ -0,0 +1,34 @@ +package com.example.lecture_7.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.example.lecture_7.EmployeeWork; +import com.example.lecture_7.entity.Employee; + +@Configuration +public class AppConfig { + + @Bean + public EmployeeWork employeeWork() { + return new EmployeeWork(); + } + + // Constructor Injection + // uncomment this and comment the Setter Injection implementation to demo + /* @Bean + public Employee employee(EmployeeWork employeeWork) { + return new Employee("101", "John Doe", 30, employeeWork); + } */ + + // Setter Injection + @Bean + public Employee employee(EmployeeWork employeeWork) { + Employee employee = new Employee(); + employee.setId("101"); + employee.setName("John Doe"); + employee.setAge(30); + employee.setEmployeeWork(employeeWork); + return employee; + } +} diff --git a/Week 04/Lecture 07/Assignment 01/lecture_7/src/main/java/com/example/lecture_7/entity/Employee.java b/Week 04/Lecture 07/Assignment 01/lecture_7/src/main/java/com/example/lecture_7/entity/Employee.java new file mode 100644 index 0000000..f677b5a --- /dev/null +++ b/Week 04/Lecture 07/Assignment 01/lecture_7/src/main/java/com/example/lecture_7/entity/Employee.java @@ -0,0 +1,64 @@ +package com.example.lecture_7.entity; +import com.example.lecture_7.EmployeeWork; + +public class Employee { + private String id; + private String name; + private int age; + private EmployeeWork employeeWork; + + // No-argument constructor + public Employee() {} + + // Constructor for DI + public Employee(String id, String name, int age, EmployeeWork employeeWork) { + this.id = id; + this.name = name; + this.age = age; + this.employeeWork = employeeWork; + } + + public void working() { + System.out.println("Employee ID: " + id); + System.out.println("Employee Name: " + name); + System.out.println("Employee Age: " + age); + employeeWork.work(); + } + + // Getters and setters (optional, depending on your needs) + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + + public EmployeeWork getEmployeeWork() { + return employeeWork; + } + + // Setter for ID + public void setId(String id) { + this.id = id; + } + + // Setter for name + public void setName(String name) { + this.name = name; + } + + // Setter for age + public void setAge(int age) { + this.age = age; + } + + // Setter for EmployeeWork + public void setEmployeeWork(EmployeeWork employeeWork) { + this.employeeWork = employeeWork; + } +} diff --git a/Week 04/Lecture 07/Assignment 01/lecture_7/src/main/resources/application.properties b/Week 04/Lecture 07/Assignment 01/lecture_7/src/main/resources/application.properties new file mode 100644 index 0000000..6157c0e --- /dev/null +++ b/Week 04/Lecture 07/Assignment 01/lecture_7/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=lecture_7 diff --git a/Week 04/Lecture 07/Assignment 02/README.md b/Week 04/Lecture 07/Assignment 02/README.md new file mode 100644 index 0000000..26cf3ed --- /dev/null +++ b/Week 04/Lecture 07/Assignment 02/README.md @@ -0,0 +1,366 @@ +# πŸ‘©πŸ»β€πŸ« Lecture 07 - Spring Core +> This repository is created as a part of assignment for Lecture 07 - Spring Core + +## 🌱 Assignment 02 - Dependency Injection and Bean Annotations (Part 2) +### ✍️ Task 1 - Working with Annotations +**Objective**: Create `EmailService` interface and `EmailServiceImpl` class. Add a method for sending email. Create `EmployeeService` class and use `EmailService` with Dependency Injection (DI) to send emails to employees about their work. Demonstrate DI using constructor, field, and setter injection. + +#### πŸ’¬ Dependency Injection Methods +1. **Constructor Injection**: This method injects dependencies through the class constructor. It’s often preferred for its simplicity and for making dependencies explicit and immutable. +2. **Field Injection**: This method injects dependencies directly into the fields of the class. It’s less preferred because it hides dependencies and makes testing harder. +3. **Setter Injection**: This method injects dependencies through setter methods. It’s useful for optional dependencies or where dependencies can change during the object's lifecycle. + +#### πŸ‘¨β€πŸ’» Implementation +1. [**`EmailService` Interface**](/Week%2004/Lecture%2007/Assignment%2002/lecture_7/src/main/java/com/example/lecture_7/service/EmailService.java) + - **Purpose**: Define a contract for email sending functionality. + - **Methods**: Should at least include `sendEmail`. +2. [**`EmailServiceImpl` Class**](/Week%2004/Lecture%2007/Assignment%2002/lecture_7/src/main/java/com/example/lecture_7/service/EmailServiceImpl.java) + - **Purpose**: Implement the `EmailService` to provide actual email sending logic. + - **Annotations**: Use `@Service` to denote it as a Spring-managed service component. +3. [**`EmployeeServiceConstructor` Class**](/Week%2004/Lecture%2007/Assignment%2002/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceConstructor.java) + + The `EmailService` is injected via the constructor. This ensures that `EmployeeServiceConstructor` is properly instantiated with a valid `EmailService`. + - **Purpose**: Use `EmailService` to send notifications to employees. + - **Tasks**: Demonstrate DI through constructor injection. + - **Preferred**: Dependencies are immutable, making the class easier to test and understand. +4. [**`EmployeeServiceField` Class**](/Week%2004/Lecture%2007/Assignment%2002/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceField.java) + + The `@Autowired` annotation directly injects the `EmailService` into the field. This can make it less clear what dependencies the class needs. + - **Purpose**: Use `EmailService` to send notifications to employees. + - **Tasks**: Demonstrate DI through field injection. + - **Direct Injection**: Dependencies are directly injected into fields. + - **Less Preferred**: Less explicit, and harder to test as it requires Spring’s context to be loaded. +5. [**`EmployeeServiceSetter` Class**](/Week%2004/Lecture%2007/Assignment%2002/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceSetter.java) + + The `EmailService` is injected through a setter method. This allows flexibility in changing the `EmailService` dependency if needed. + - **Purpose**: Use `EmailService` to send notifications to employees. + - **Tasks**: Demonstrate DI through field injection. + - **Configurable**: Dependencies can be set or changed after object creation. + - **Use Case**: When dependencies are optional or can be changed. +6. [**`AppConfig` Class**](/Week%2004/Lecture%2007/Assignment%2002/lecture_7/src/main/java/com/example/lecture_7/config/AppConfig.java) + - **`@Configuration`**: This annotation is used to indicate that the class declares one or more `@Bean` methods and may be processed by the Spring container to generate bean definitions and service requests for those beans at runtime. + - **`@ComponentScan(basePackages = "com.example.lecture_7")`**: This annotation is used to specify the base packages to scan for Spring components. In this case, it tells Spring to scan the `com.example.lecture_7` package and its sub-packages for Spring components such as `@Component`, `@Service`, `@Repository`, and `@Controller`. Spring will then automatically register these components as beans in the application context. + - **Purpose**: `AppConfig` serves as a configuration class for the Spring application. It typically includes configuration of beans using `@Bean` methods, setup of Spring-specific configurations, and enabling component scanning to discover Spring components automatically. +7. [**`Lecture7Application` Class**](/Week%2004/Lecture%2007/Assignment%2002/lecture_7/src/main/java/com/example/lecture_7/Lecture7Application.java) + Serves as the main application for the Java Spring Boot. + +#### πŸ‘€ Testing +To test the `EmployeeService` classes and their interaction with `EmailService` in a Spring Boot application, we can place your test code in the `src/test/java` directory. This is the standard convention for Java testing in Spring Boot projects. + +The test is defined in [**`EmployeeServiceConstructorTest`**](/Week%2004/Lecture%2007/Assignment%2002/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceConstructorTest.java), [**`EmployeeServiceFieldTest`**](/Week%2004/Lecture%2007/Assignment%2002/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceFieldTest.java), and [**`EmployeeServiceSetterTest`**](/Week%2004/Lecture%2007/Assignment%2002/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceSetterTest.java) classes. + +Here is how to run all the tests +1. Go to the `lecture_7` directory by using this command + ```bash + $ cd lecture_7 + ``` +2. Make sure you have maven installed on your computer, use `mvn -v` to check the version. +3. Run this code to do the test + ```bash + $ mvn test + ``` + +If all the instruction is well executed, the test result will be something like this. + +![Screenshot](img/result.png) + +From the result, we know that all the test **executed successfully** and all of them **give the same output**. + +--- + +### πŸ”Ž Task 2 - Comparison of Dependency Injection Types + +#### 1️⃣ Constructor Injection +Constructor injection involves injecting dependencies through the class constructor. This is one of the most common and recommended approaches because it ensures that the class is initialized with all required dependencies right from the start. + +**Pros**: Dependencies are immutable and explicit. +**Cons**: Can be verbose with many dependencies. + +#### 2️⃣ Field Injection +Field injection involves injecting dependencies directly into class fields. This approach is simpler to implement but has some drawbacks, such as making dependencies less clear and hindering testability, especially with mocking frameworks. + +**Pros**: Simpler and cleaner in terms of code. +**Cons**: Hides dependencies, making the code less clear and harder to test. + +#### 3️⃣ Setter Injection +Setter injection involves injecting dependencies through setter methods. This approach allows dependencies to be set or changed after the object is constructed. It provides flexibility but can lead to partially initialized objects and is less suitable for required dependencies. + +**Pros**: Allows for optional dependencies and changing dependencies. +**Cons**: Dependencies are mutable, and it's less obvious which dependencies are required. + +#### πŸ“Œ Summary +**Constructor Injection**: Best practice, ensures dependencies are provided at object creation, promotes clear and testable code. +**Field Injection**: Simpler but leads to tighter coupling, harder to test in isolation. +**Setter Injection**: Flexible but can lead to mutable objects and harder to reason about dependencies. + +--- + +### πŸ”΄ Task 3 - Circular Dependency Injection +Circular Dependency Injection (CDI) occurs when two or more classes have dependencies on each other directly or indirectly, forming a cycle. This situation can create challenges for dependency injection frameworks because they rely on constructors or setters to inject dependencies, and a circular dependency prevents the straightforward creation of objects. + +#### πŸ’‘ Understanding Circular Dependency + +Consider two classes, `ClassA` and `ClassB`, where: + +- `ClassA` depends on `ClassB`. +- `ClassB` depends on `ClassA`. + +```java +public class ClassA { + private ClassB b; + + public ClassA(ClassB b) { + this.b = b; + } + // Methods using b +} + +public class ClassB { + private ClassA a; + + public ClassB(ClassA a) { + this.a = a; + } + // Methods using a +} +``` + +In this scenario: +- `ClassA` requires an instance of `ClassB`. +- `ClassB` requires an instance of `ClassA`. +- This forms a circular dependency because `ClassA` depends on `ClassB` and vice versa. + +#### 🀯 Challenges with Circular Dependency + +Dependency Injection frameworks typically construct objects using constructors or setters. However, with circular dependencies. +- **Constructor Injection:** Cannot be resolved because each class requires an instance of the other, causing a deadlock during object creation. +- **Setter Injection:** Faces similar issues if setters are used to inject dependencies after object creation. + +#### 🧩 Resolving Circular Dependency + +To resolve circular dependencies, dependency injection frameworks like Spring provide several solutions: + +1. **Constructor-Based Injection with `@Autowired`** + ```java + public class ClassA { + private ClassB b; + + @Autowired + public ClassA(ClassB b) { + this.b = b; + } + // Methods using b + } + + public class ClassB { + private ClassA a; + + @Autowired + public ClassB(ClassA a) { + this.a = a; + } + // Methods using a + } + ``` + + Use `@Autowired` on constructors to allow the framework to manage the creation order of beans. + +2. **Setter-Based Injection** + ```java + public class ClassA { + private ClassB b; + + @Autowired + public void setB(ClassB b) { + this.b = b; + } + // Methods using b + } + + public class ClassB { + private ClassA a; + + @Autowired + public void setA(ClassA a) { + this.a = a; + } + // Methods using a + } + ``` + + Use setter methods annotated with `@Autowired` to inject dependencies after objects are created. + +3. **Interface-Based Proxying:** + Dependency injection frameworks may use proxies or lazy initialization to break the circular dependency. + ```java + @Autowired + @Lazy + private B b; + ``` + Use `@Lazy` annotation to create a proxy for one of the beans. + +4. **Refactoring:** + Sometimes, refactoring the design to eliminate the circular dependency by introducing an intermediary interface or by rethinking class responsibilities can resolve the issue. + +#### πŸ€” Best Practices + +- **Prefer Constructor Injection:** Whenever possible, use constructor injection as it ensures dependencies are resolved at object creation time. +- **Avoid Circular Dependencies:** Design classes to minimize or eliminate circular dependencies, as they complicate testing and maintenance. +- **Use Dependency Injection Framework Features:** Leverage features provided by dependency injection frameworks to manage circular dependencies, such as lazy initialization or proxies. + +--- + +### πŸ’― Task 4 - Some Annotations Explanation and Examples +#### `@Configuration` +**Purpose**: Marks a class as a source of bean definitions for the application context. + +**Example**: +```java +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AppConfig { + @Bean + public EmailService emailService() { + return new EmailServiceImpl(); + } +} +``` + +#### `@Bean` +**Purpose**: Indicates that a method produces a bean to be managed by the Spring container. + +**Example**: +```java +@Bean +public EmailService emailService() { + return new EmailServiceImpl(); +} +``` + +#### `@ComponentScan` +**Purpose**: Configures component scanning directives for use with `@Configuration` classes. + +**Example**: +```java +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan(basePackages = "com.example") +public class AppConfig { +} +``` + +#### `@Component` +**Purpose**: Indicates that a class is a Spring component. + +**Example**: +```java +import org.springframework.stereotype.Component; + +@Component +public class EmailServiceImpl implements EmailService { + // Implementation +} +``` + +#### `@Service` +**Purpose**: Indicates that a class is a service component in the business layer. + +**Example**: +```java +import org.springframework.stereotype.Service; + +@Service +public class EmployeeService { + // Implementation +} +``` + +#### `@Repository` +**Purpose**: Indicates that a class is a data repository and provides an abstraction of data access. + +**Example**: +```java +import org.springframework.stereotype.Repository; + +@Repository +public class EmployeeRepository { + // Implementation +} +``` + +#### `@Autowired` +**Purpose**: Enables automatic injection of dependencies. + +**Example**: +```java +@Autowired +private EmailService emailService; +``` + +#### `@Scope` +**Purpose**: Specifies the scope of a bean (singleton, prototype, etc.). + +**Example**: +```java +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Component +@Scope("prototype") +public class EmailServiceImpl implements EmailService { + // Implementation +} +``` + +#### `@Qualifier` +**Purpose**: Disambiguates injection when multiple beans of the same type exist. + +**Example**: +```java +@Autowired +@Qualifier("emailServiceImpl") +private EmailService emailService; +``` + +#### `@PropertySource` and `@Value` +**Purpose**: Allows for externalizing property values into a properties file. + +**Example**: +```java +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +@Configuration +@PropertySource("classpath:application.properties") +public class AppConfig { + @Value("${email.service.url}") + private String emailServiceUrl; +} +``` + +#### `@PreDestroy` and `@PostConstruct` +**Purpose**: Used for lifecycle callback methods to release resources before bean destruction or perform initialization after bean creation. + +**Example**: +```java +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + +@Component +public class EmailServiceImpl implements EmailService { + @PostConstruct + public void init() { + // Initialization code + } + + @PreDestroy + public void cleanup() { + // Cleanup code + } +} +``` \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 02/img/result.png b/Week 04/Lecture 07/Assignment 02/img/result.png new file mode 100644 index 0000000..49399cb Binary files /dev/null and b/Week 04/Lecture 07/Assignment 02/img/result.png differ diff --git a/Week 04/Lecture 07/Assignment 02/lecture_7/.gitignore b/Week 04/Lecture 07/Assignment 02/lecture_7/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/Week 04/Lecture 07/Assignment 02/lecture_7/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/Week 04/Lecture 07/Assignment 02/lecture_7/.mvn/wrapper/maven-wrapper.properties b/Week 04/Lecture 07/Assignment 02/lecture_7/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..8f96f52 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 02/lecture_7/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip diff --git a/Week 04/Lecture 07/Assignment 02/lecture_7/mvnw b/Week 04/Lecture 07/Assignment 02/lecture_7/mvnw new file mode 100644 index 0000000..d7c358e --- /dev/null +++ b/Week 04/Lecture 07/Assignment 02/lecture_7/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/Week 04/Lecture 07/Assignment 02/lecture_7/mvnw.cmd b/Week 04/Lecture 07/Assignment 02/lecture_7/mvnw.cmd new file mode 100644 index 0000000..6f779cf --- /dev/null +++ b/Week 04/Lecture 07/Assignment 02/lecture_7/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/Week 04/Lecture 07/Assignment 02/lecture_7/pom.xml b/Week 04/Lecture 07/Assignment 02/lecture_7/pom.xml new file mode 100644 index 0000000..691b0c5 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 02/lecture_7/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.1 + + + com.example + lecture_7 + 1.0-SNAPSHOT + lecture_7 + Demo project for Spring Boot + + + + + + + + + + + + + + + 21 + + + + org.springframework.boot + spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.mockito + mockito-core + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + -XX:+EnableDynamicAgentLoading + + + + + + diff --git a/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/Lecture7Application.java b/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/Lecture7Application.java new file mode 100644 index 0000000..4fa65a3 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/Lecture7Application.java @@ -0,0 +1,13 @@ +package com.example.lecture_7; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Lecture7Application { + + public static void main(String[] args) { + SpringApplication.run(Lecture7Application.class, args); + } +} + diff --git a/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/config/AppConfig.java b/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/config/AppConfig.java new file mode 100644 index 0000000..4960e51 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/config/AppConfig.java @@ -0,0 +1,7 @@ +package com.example.lecture_7.config; + +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AppConfig { +} diff --git a/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/service/EmailService.java b/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/service/EmailService.java new file mode 100644 index 0000000..33d04f9 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/service/EmailService.java @@ -0,0 +1,5 @@ +package com.example.lecture_7.service; + +public interface EmailService { + void sendEmail(String to, String subject, String body); +} \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/service/EmailServiceImpl.java b/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/service/EmailServiceImpl.java new file mode 100644 index 0000000..71485b7 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/service/EmailServiceImpl.java @@ -0,0 +1,14 @@ +package com.example.lecture_7.service; + +import org.springframework.stereotype.Service; + +@Service +public class EmailServiceImpl implements EmailService { + @Override + public void sendEmail(String to, String subject, String body) { + // Simulate email sending (in real scenarios, integrate with email server) + System.out.println("Sending email to " + to); + System.out.println("Subject: " + subject); + System.out.println("Body: " + body); + } +} diff --git a/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceConstructor.java b/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceConstructor.java new file mode 100644 index 0000000..4f8677c --- /dev/null +++ b/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceConstructor.java @@ -0,0 +1,18 @@ +package com.example.lecture_7.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class EmployeeServiceConstructor { + private final EmailService emailService; + + @Autowired + public EmployeeServiceConstructor(EmailService emailService) { + this.emailService = emailService; + } + + public void notifyEmployee(String email, String subject, String body) { + emailService.sendEmail(email, subject, body); + } +} diff --git a/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceField.java b/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceField.java new file mode 100644 index 0000000..5f8bca9 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceField.java @@ -0,0 +1,14 @@ +package com.example.lecture_7.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class EmployeeServiceField { + @Autowired + private EmailService emailService; + + public void notifyEmployee(String email, String subject, String body) { + emailService.sendEmail(email, subject, body); + } +} \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceSetter.java b/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceSetter.java new file mode 100644 index 0000000..6b6ca90 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceSetter.java @@ -0,0 +1,18 @@ +package com.example.lecture_7.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class EmployeeServiceSetter { + private EmailService emailService; + + @Autowired + public void setEmailService(EmailService emailService) { + this.emailService = emailService; + } + + public void notifyEmployee(String email, String subject, String body) { + emailService.sendEmail(email, subject, body); + } +} diff --git a/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/resources/application.properties b/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/resources/application.properties new file mode 100644 index 0000000..6157c0e --- /dev/null +++ b/Week 04/Lecture 07/Assignment 02/lecture_7/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=lecture_7 diff --git a/Week 04/Lecture 07/Assignment 02/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceConstructorTest.java b/Week 04/Lecture 07/Assignment 02/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceConstructorTest.java new file mode 100644 index 0000000..7a3f50a --- /dev/null +++ b/Week 04/Lecture 07/Assignment 02/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceConstructorTest.java @@ -0,0 +1,43 @@ +package com.example.lecture_7.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; + +public class EmployeeServiceConstructorTest { + + private EmployeeServiceConstructor employeeService; + + @Mock + private EmailService emailService; + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + employeeService = new EmployeeServiceConstructor(emailService); + + // Custom behavior for emailService.sendEmail + doAnswer(invocation -> { + Object[] args = invocation.getArguments(); + System.out.println("Sending email to " + args[0]); + System.out.println("Subject: " + args[1]); + System.out.println("Body: " + args[2]); + return null; + }).when(emailService).sendEmail(anyString(), anyString(), anyString()); + } + + @Test + public void testNotifyEmployee() { + String email = "employee@example.com"; + String subject = "Subject"; + String body = "Body"; + + employeeService.notifyEmployee(email, subject, body); + + // Output is printed by the custom behavior + } +} diff --git a/Week 04/Lecture 07/Assignment 02/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceFieldTest.java b/Week 04/Lecture 07/Assignment 02/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceFieldTest.java new file mode 100644 index 0000000..e4b9921 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 02/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceFieldTest.java @@ -0,0 +1,44 @@ +package com.example.lecture_7.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; + +public class EmployeeServiceFieldTest { + + @InjectMocks + private EmployeeServiceField employeeService; + + @Mock + private EmailService emailService; + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + + // Custom behavior for emailService.sendEmail + doAnswer(invocation -> { + Object[] args = invocation.getArguments(); + System.out.println("Sending email to " + args[0]); + System.out.println("Subject: " + args[1]); + System.out.println("Body: " + args[2]); + return null; + }).when(emailService).sendEmail(anyString(), anyString(), anyString()); + } + + @Test + public void testNotifyEmployee() { + String email = "employee@example.com"; + String subject = "Subject"; + String body = "Body"; + + employeeService.notifyEmployee(email, subject, body); + + // Output is printed by the custom behavior + } +} \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 02/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceSetterTest.java b/Week 04/Lecture 07/Assignment 02/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceSetterTest.java new file mode 100644 index 0000000..8e8aa4c --- /dev/null +++ b/Week 04/Lecture 07/Assignment 02/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceSetterTest.java @@ -0,0 +1,44 @@ +package com.example.lecture_7.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; + +public class EmployeeServiceSetterTest { + + private EmployeeServiceSetter employeeService; + + @Mock + private EmailService emailService; + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + employeeService = new EmployeeServiceSetter(); + employeeService.setEmailService(emailService); + + // Custom behavior for emailService.sendEmail + doAnswer(invocation -> { + Object[] args = invocation.getArguments(); + System.out.println("Sending email to " + args[0]); + System.out.println("Subject: " + args[1]); + System.out.println("Body: " + args[2]); + return null; + }).when(emailService).sendEmail(anyString(), anyString(), anyString()); + } + + @Test + public void testNotifyEmployee() { + String email = "employee@example.com"; + String subject = "Subject"; + String body = "Body"; + + employeeService.notifyEmployee(email, subject, body); + + // Output is printed by the custom behavior + } +} \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 03/README.md b/Week 04/Lecture 07/Assignment 03/README.md new file mode 100644 index 0000000..51a531f --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/README.md @@ -0,0 +1,328 @@ +# πŸ‘©πŸ»β€πŸ« Lecture 07 - Spring Core +> This repository is created as a part of assignment for Lecture 07 - Spring Core + +## 🌱 Assignment 03 - Dependency Injection and Bean Annotations (Part 3) +### 🎯 Task 1 - Add Bean Scopes (Singleton, Prototype) to Assignment 2 +**Bean scopes** in Spring determine the lifecycle and visibility of beans within the Spring container. Here, we’ll focus on: +- **Singleton Scope**: A single instance of the bean is created and shared across the entire application. +- **Prototype Scope**: A new instance of the bean is created each time it is requested. + +#### πŸ‘¨β€πŸ’» Implementation +1. [**`EmailServiceImpl` Interface**](/Week%2004/Lecture%2007/Assignment%2003/lecture_7/src/main/java/com/example/lecture_7/service/EmailServiceImpl.java) + + `EmailServiceImpl` is already a singleton by default, but we'll explicitly annotate it for clarity and print its hash code when the `sendEmail` method is called. + +2. [**`EmployeeServiceConstructor` Class**](/Week%2004/Lecture%2007/Assignment%2003/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceConstructor.java) + + We'll modify the `EmployeeServiceConstructor` class to demonstrate the prototype scope. + - The `@Scope("prototype")` annotation makes each request for `EmployeeServiceConstructor` create a new instance. + - The `notifyEmployee` method will print the hash code of the current `EmployeeServiceConstructor` instance, demonstrating that different instances are created for each call. + +3. [**`Lecture7Application` Class**](/Week%2004/Lecture%2007/Assignment%2003/lecture_7/src/main/java/com/example/lecture_7/Lecture7Application.java) + + We’ll print the hash codes to demonstrate that `EmailService` behaves as singleton and `EmployeeServiceConstructor` behaves as a prototype. + +#### βš™οΈ How to run the program +1. Go to the `lecture_7` directory by using this command + ```bash + $ cd lecture_7 + ``` +2. Make sure you have maven installed on your computer, use `mvn -v` to check the version. +3. If you are using windows, you can run the program by using this command. + ```bash + $ ./run.bat + ``` + And if you are using Linux, you can run the program by using this command. + ```bash + $ chmod +x run.sh + $ ./run.sh + ``` + +If all the instruction is well executed, the result will be something like this. + +![Screenshot](img/result.png) + +#### πŸ“ Summary +1. **Singleton Scope**: Demonstrated by `EmailServiceImpl`, which is a singleton by default. The same instance is used across the application. +2. **Prototype Scope**: Demonstrated by `EmployeeServiceConstructor`, which creates a new instance each time it is requested. + +This clear differentiation shows how different scopes manage bean instances, and printing hash codes helps in verifying the behavior. + +--- + +### πŸ•ΉοΈ Task 2 - Create a Controller and Test Request Scope +We will create a simple Spring MVC controller that uses a **request-scoped** bean. We'll demonstrate how this scope works in the context of HTTP requests by printing out information about the bean instance for each request. This will illustrate that a new bean instance is created for each HTTP request. + +#### πŸ€” **Understanding Request Scope** +**Request Scope**: In the Spring context, a bean defined with the request scope will have a new instance created for each HTTP request. This is commonly used in web applications to handle user-specific data during the request processing. + +#### 🐾 **Implementation Steps** + +1. **Create a Request-Scoped Bean**: Define a bean that has the request scope. +2. **Create a Controller**: Use this bean in a Spring MVC controller to demonstrate its behavior. +3. **Test the Request Scope**: Use a simple client or browser to make HTTP requests and observe the bean's behavior. + +#### πŸ‘¨β€πŸ’» **Implementation** + +1. **`RequestScopedBean.java`**(Request-Scoped Bean) + + We’ll define a bean that will be instantiated for each HTTP request. We’ll print its hash code to verify that a new instance is created for each request. + + The code is implemented on [this file](/lecture_7_1/src/main/java/com/example/lecture_7_1/controller/RequestController.java) + + Here is the explanation on what the code actually done. + - The `@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)` annotation inject a proxy into your controller instead of the actual bean, allowing proper request scope resolution. + - The `handleRequest` method prints the hash code of the instance, helping us verify that a new instance is created for each request. + +2. **`RequestController.java`** (Controller) + + Create a controller that will use the `RequestScopedBean` to handle HTTP requests. + + The code is implemented on [this file](/lecture_7_1/src/main/java/com/example/lecture_7_1/service/RequestScopedBean.java) + + Here is the explanation on what the code actually done. + - The `@RestController` annotation makes this class a REST controller that can handle HTTP requests. + - The `@GetMapping("/testRequest")` annotation maps HTTP GET requests to the `testRequestScope` method. + - This method calls `handleRequest` on `RequestScopedBean` and returns a message, while the `RequestScopedBean` prints its hash code to the console. + - `ObjectFactory` from Spring’s `BeanFactory` is used to lazily resolve the request-scoped bean. + +3. **`Lecture71Application.java`** (Main Application) + + This class is used as the main program. In this case we need to ensure your application is configured to scan for the `controller` and `service` packages. + + The code is implemented on [this file](/lecture_7_1/src/main/java/com/example/lecture_7_1/Lecture71Application.java) + + Here is the explanation on what the code actually done. + - Ensure `@SpringBootApplication` is used to automatically configure and scan components. + - The application will now handle HTTP requests to `/testRequest`. + +#### βš™οΈ **Testing Request Scope** + +1. **Run the Application**: Start the Spring Boot application. + 1. Go to the `lecture_7_1` directory by using this command + ```bash + $ cd lecture_7_1 + ``` + 2. Make sure you have maven installed on your computer, use `mvn -v` to check the version. + 3. If you are using windows, you can run the program by using this command. + ```bash + $ ./run.bat + ``` + And if you are using Linux, you can run the program by using this command. + ```bash + $ chmod +x run.sh + $ ./run.sh + ``` + +2. **Access the Endpoint**: Use a browser or a tool like `curl` or Postman to access `http://localhost:8080/testRequest`. In this section i use the curl one with commdn like this + ```bash + $ curl http://localhost:8080/testRequest + ``` + +If you are doing well, the result will be something like this. + +![Screenshot](img/result2.png) + +Each request to `/testRequest` creates a new instance of `RequestScopedBean`, verified by different hash codes. + +#### πŸ“ **Summary** + +1. **Created `RequestScopedBean`**: + - Defined with request scope, so a new instance is created for each HTTP request. + - Prints its hash code to the console to verify instance creation. + +2. **Created `RequestController`**: + - Handles HTTP requests and uses `RequestScopedBean` to demonstrate the request scope. + - Maps to the `/testRequest` endpoint. + +3. **Tested the Request Scope**: + - Verified new instances for each HTTP request using hash codes. + +This setup effectively demonstrates the request scope in a Spring application, where each HTTP request receives a new bean instance, useful for handling request-specific data or operations. + +--- + +### ❓ Task 3 - How to Inject Prototype Bean into Singleton Bean? +First, we need to understand a common issue in dependency injection within the Spring Framework and how to resolve it. + +#### ❗ **Problem Overview** + +- **Prototype Scope**: Each time a prototype-scoped bean is requested, a new instance is created. +- **Singleton Scope**: A single instance of the bean is created and shared throughout the application's lifecycle. + +When a prototype bean is injected into a singleton bean directly, only a single instance of the prototype bean is created and shared within the singleton bean. This defeats the purpose of the prototype scope. + +To solve this issue, there are several methods to ensure that a new instance of the prototype bean is created each time it is needed within a singleton bean: + +1. **Using `ObjectFactory` or `Provider`**: The simplest approach using Spring’s built-in `ObjectFactory` or Java’s `Provider` from `javax.inject`. +2. **Using `@Lookup` Method Injection**: A method annotated with `@Lookup` will be overridden by the container to return a new instance of a prototype bean. +3. **Using Application Context**: Manually retrieving the bean from the application context. + +#### 1️⃣ **1. Using `ObjectFactory` or `Provider`** + +**Implementation Using `ObjectFactory`** + +**Create `PrototypeBean`** + +```java +package com.example.service; + +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Component +@Scope("prototype") +public class PrototypeBean { + public void doSomething() { + System.out.println("PrototypeBean instance hash: " + this.hashCode()); + } +} +``` + +**Modify `SingletonBean`** + +```java +package com.example.service; + +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class SingletonBean { + private final ObjectFactory prototypeBeanFactory; + + @Autowired + public SingletonBean(ObjectFactory prototypeBeanFactory) { + this.prototypeBeanFactory = prototypeBeanFactory; + } + + public void usePrototypeBean() { + PrototypeBean prototypeBean = prototypeBeanFactory.getObject(); + prototypeBean.doSomething(); + } +} +``` + +`ObjectFactory` is used to lazily resolve a new instance of `PrototypeBean` each time it’s called. + +#### 2️⃣ **2. Using `@Lookup` Method Injection** + +**Modify `SingletonBean`** + +```java +package com.example.service; + +import org.springframework.beans.factory.annotation.Lookup; +import org.springframework.stereotype.Component; + +@Component +public class SingletonBean { + + @Lookup + public PrototypeBean getPrototypeBean() { + // Spring will override this method to return a new PrototypeBean instance + return null; + } + + public void usePrototypeBean() { + PrototypeBean prototypeBean = getPrototypeBean(); + prototypeBean.doSomething(); + } +} +``` + +`@Lookup` annotation tells Spring to override the method to return a new instance of `PrototypeBean`. + +#### 3️⃣ **3. Using Application Context** + +**Modify `SingletonBean`** + +```java +package com.example.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +@Component +public class SingletonBean { + + private final ApplicationContext applicationContext; + + @Autowired + public SingletonBean(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + public void usePrototypeBean() { + PrototypeBean prototypeBean = applicationContext.getBean(PrototypeBean.class); + prototypeBean.doSomething(); + } +} +``` + +`applicationContext.getBean(PrototypeBean.class)` retrieves a new instance of `PrototypeBean` each time it’s called. + +**Usage Example** + +```java +package com.example.controller; + +import com.example.service.SingletonBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class SingletonController { + + private final SingletonBean singletonBean; + + @Autowired + public SingletonController(SingletonBean singletonBean) { + this.singletonBean = singletonBean; + } + + @GetMapping("/usePrototype") + public String usePrototype() { + singletonBean.usePrototypeBean(); + return "Check the console for the PrototypeBean instance hash code."; + } +} +``` + +#### πŸ“ **Summary** + +- **Using `ObjectFactory` or `Provider`**: Provides a factory to create new instances when needed, ensuring prototype behavior within a singleton. +- **Using `@Lookup` Method Injection**: Uses Spring’s method injection to return new prototype instances each time the method is called. +- **Using Application Context**: Retrieves prototype beans directly from the application context, creating a new instance each time. + +--- + +### πŸ€·πŸ»β€β™‚οΈ Task 4 - Difference between `BeanFactory` and `ApplicationContext` + +#### ✨ **Overview** + +- **`BeanFactory`**: The root interface for accessing the Spring container. It provides basic functionality to manage beans. +- **`ApplicationContext`**: An extension of `BeanFactory` that adds more enterprise-specific functionality, including event propagation, declarative mechanisms to create a bean, and a more extensive means to work with the container. + +#### πŸ” **Detailed Differences** +| Feature | `BeanFactory` | `ApplicationContext` | +|------------------------------------|----------------------------------------|-------------------------------------------| +| Basic Dependency Injection Container | Provides the basic mechanism to manage beans. You typically use `getBean` to retrieve beans, and it only instantiates a bean when it’s requested. | Provides all the features of `BeanFactory` and more. It initializes all singleton beans at startup by default, which can be overridden. | +| Bean Instantiation on Demand | Yes | No (Eager Initialization by default) | +| Event Propagation | Does not support event propagation. | Supports event propagation, allowing beans to publish and listen to application events. | +| Internationalization (i18n) | Does not support internationalization directly. | Provides support for internationalization (i18n), allowing messages to be resolved in different locales. | +| ApplicationContext Aware Beans | Does not support `ApplicationContext` aware beans. | Supports `ApplicationContext` aware beans, allowing them to access the `ApplicationContext` itself. | +| Autowiring | Yes | Yes | +| BeanPostProcessor Support | Limited support for `BeanPostProcessor`. | Full support for `BeanPostProcessor`, allowing for custom modifications of new bean instances. | +| Built-in Bean Scopes | Singleton, Prototype | Singleton, Prototype, Request, Session | +| Integration with Web Applications | Basic support for managing beans, not designed for web integration. | Rich support for web applications, with features like `WebApplicationContext`, session-scoped beans, and request-scoped beans. | +| Environment Abstraction | Does not provide environment abstraction. | Provides environment abstraction, allowing you to manage profiles, properties, and other environment configurations. | +| Lifecycle Management | Provides basic lifecycle management with `InitializingBean` and `DisposableBean`. | Offers full lifecycle management with additional support for custom lifecycle events and integration with `ApplicationListener`. | + +### πŸ“ **Summary** + +- **`BeanFactory`** is lightweight, primarily used for simple DI and is the root interface. +- **`ApplicationContext`** extends `BeanFactory`, providing more advanced and enterprise-specific features, including event handling, i18n, lifecycle management, and integration with web applications. \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 03/img/result.png b/Week 04/Lecture 07/Assignment 03/img/result.png new file mode 100644 index 0000000..1bd8f22 Binary files /dev/null and b/Week 04/Lecture 07/Assignment 03/img/result.png differ diff --git a/Week 04/Lecture 07/Assignment 03/img/result2.png b/Week 04/Lecture 07/Assignment 03/img/result2.png new file mode 100644 index 0000000..2fce2dc Binary files /dev/null and b/Week 04/Lecture 07/Assignment 03/img/result2.png differ diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7/.gitignore b/Week 04/Lecture 07/Assignment 03/lecture_7/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7/.mvn/wrapper/maven-wrapper.properties b/Week 04/Lecture 07/Assignment 03/lecture_7/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..8f96f52 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7/mvnw b/Week 04/Lecture 07/Assignment 03/lecture_7/mvnw new file mode 100644 index 0000000..d7c358e --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7/mvnw.cmd b/Week 04/Lecture 07/Assignment 03/lecture_7/mvnw.cmd new file mode 100644 index 0000000..6f779cf --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7/pom.xml b/Week 04/Lecture 07/Assignment 03/lecture_7/pom.xml new file mode 100644 index 0000000..691b0c5 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.1 + + + com.example + lecture_7 + 1.0-SNAPSHOT + lecture_7 + Demo project for Spring Boot + + + + + + + + + + + + + + + 21 + + + + org.springframework.boot + spring-boot-starter + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.mockito + mockito-core + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + -XX:+EnableDynamicAgentLoading + + + + + + diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7/run.bat b/Week 04/Lecture 07/Assignment 03/lecture_7/run.bat new file mode 100644 index 0000000..0c84c22 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7/run.bat @@ -0,0 +1,3 @@ +@echo off +echo Building the project with Maven... +mvn clean install && java -jar target/lecture_7_1-0.0.1-SNAPSHOT.jar \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7/run.sh b/Week 04/Lecture 07/Assignment 03/lecture_7/run.sh new file mode 100644 index 0000000..626a9d2 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +echo Building the project with Maven... +mvn clean install && java -jar target/lecture_7-1.0-SNAPSHOT.jar \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/Lecture7Application.java b/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/Lecture7Application.java new file mode 100644 index 0000000..34be384 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/Lecture7Application.java @@ -0,0 +1,41 @@ +package com.example.lecture_7; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; + +import com.example.lecture_7.service.EmailService; +import com.example.lecture_7.service.EmployeeServiceConstructor; + +@SpringBootApplication +public class Lecture7Application implements CommandLineRunner { + + private final ApplicationContext context; + + public Lecture7Application(ApplicationContext context) { + this.context = context; + } + + public static void main(String[] args) { + SpringApplication.run(Lecture7Application.class, args); + } + + @Override + public void run(String... args) throws Exception { + System.out.println("Testing Singleton Scope for EmailServiceImpl:"); + EmailService emailService1 = context.getBean(EmailService.class); + EmailService emailService2 = context.getBean(EmailService.class); + + emailService1.sendEmail("singleton@example.com", "Singleton Test", "Testing Singleton Scope"); + emailService2.sendEmail("singleton@example.com", "Singleton Test", "Testing Singleton Scope"); + + System.out.println("\nTesting Prototype Scope for EmployeeServiceConstructor:"); + EmployeeServiceConstructor employeeServiceConstructor1 = context.getBean(EmployeeServiceConstructor.class); + EmployeeServiceConstructor employeeServiceConstructor2 = context.getBean(EmployeeServiceConstructor.class); + + employeeServiceConstructor1.notifyEmployee("employee1@example.com", "Prototype Test 1", "Testing Prototype Scope 1"); + employeeServiceConstructor2.notifyEmployee("employee2@example.com", "Prototype Test 2", "Testing Prototype Scope 2"); + } +} + diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/config/AppConfig.java b/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/config/AppConfig.java new file mode 100644 index 0000000..d5461f3 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/config/AppConfig.java @@ -0,0 +1,9 @@ +package com.example.lecture_7.config; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan(basePackages = "com.example.lecture_7") +public class AppConfig { +} \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/service/EmailService.java b/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/service/EmailService.java new file mode 100644 index 0000000..33d04f9 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/service/EmailService.java @@ -0,0 +1,5 @@ +package com.example.lecture_7.service; + +public interface EmailService { + void sendEmail(String to, String subject, String body); +} \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/service/EmailServiceImpl.java b/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/service/EmailServiceImpl.java new file mode 100644 index 0000000..afb233c --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/service/EmailServiceImpl.java @@ -0,0 +1,17 @@ +package com.example.lecture_7.service; + +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; + +@Service +@Scope("singleton") +public class EmailServiceImpl implements EmailService { + @Override + public void sendEmail(String to, String subject, String body) { + // Simulate email sending (in real scenarios, integrate with email server) + System.out.println("Sending email to " + to); + System.out.println("Subject: " + subject); + System.out.println("Body: " + body); + System.out.println("EmailServiceImpl instance hash: " + this.hashCode()); + } +} diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceConstructor.java b/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceConstructor.java new file mode 100644 index 0000000..8d3d06b --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceConstructor.java @@ -0,0 +1,21 @@ +package com.example.lecture_7.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; + +@Service +@Scope("prototype") +public class EmployeeServiceConstructor { + private final EmailService emailService; + + @Autowired + public EmployeeServiceConstructor(EmailService emailService) { + this.emailService = emailService; + } + + public void notifyEmployee(String email, String subject, String body) { + emailService.sendEmail(email, subject, body); + System.out.println("EmployeeServiceConstructor instance hash: " + this.hashCode()); + } +} diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceField.java b/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceField.java new file mode 100644 index 0000000..5f8bca9 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceField.java @@ -0,0 +1,14 @@ +package com.example.lecture_7.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class EmployeeServiceField { + @Autowired + private EmailService emailService; + + public void notifyEmployee(String email, String subject, String body) { + emailService.sendEmail(email, subject, body); + } +} \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceSetter.java b/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceSetter.java new file mode 100644 index 0000000..6b6ca90 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/java/com/example/lecture_7/service/EmployeeServiceSetter.java @@ -0,0 +1,18 @@ +package com.example.lecture_7.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class EmployeeServiceSetter { + private EmailService emailService; + + @Autowired + public void setEmailService(EmailService emailService) { + this.emailService = emailService; + } + + public void notifyEmployee(String email, String subject, String body) { + emailService.sendEmail(email, subject, body); + } +} diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/resources/application.properties b/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/resources/application.properties new file mode 100644 index 0000000..6157c0e --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=lecture_7 diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceConstructorTest.java b/Week 04/Lecture 07/Assignment 03/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceConstructorTest.java new file mode 100644 index 0000000..7a3f50a --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceConstructorTest.java @@ -0,0 +1,43 @@ +package com.example.lecture_7.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; + +public class EmployeeServiceConstructorTest { + + private EmployeeServiceConstructor employeeService; + + @Mock + private EmailService emailService; + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + employeeService = new EmployeeServiceConstructor(emailService); + + // Custom behavior for emailService.sendEmail + doAnswer(invocation -> { + Object[] args = invocation.getArguments(); + System.out.println("Sending email to " + args[0]); + System.out.println("Subject: " + args[1]); + System.out.println("Body: " + args[2]); + return null; + }).when(emailService).sendEmail(anyString(), anyString(), anyString()); + } + + @Test + public void testNotifyEmployee() { + String email = "employee@example.com"; + String subject = "Subject"; + String body = "Body"; + + employeeService.notifyEmployee(email, subject, body); + + // Output is printed by the custom behavior + } +} diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceFieldTest.java b/Week 04/Lecture 07/Assignment 03/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceFieldTest.java new file mode 100644 index 0000000..e4b9921 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceFieldTest.java @@ -0,0 +1,44 @@ +package com.example.lecture_7.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; + +public class EmployeeServiceFieldTest { + + @InjectMocks + private EmployeeServiceField employeeService; + + @Mock + private EmailService emailService; + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + + // Custom behavior for emailService.sendEmail + doAnswer(invocation -> { + Object[] args = invocation.getArguments(); + System.out.println("Sending email to " + args[0]); + System.out.println("Subject: " + args[1]); + System.out.println("Body: " + args[2]); + return null; + }).when(emailService).sendEmail(anyString(), anyString(), anyString()); + } + + @Test + public void testNotifyEmployee() { + String email = "employee@example.com"; + String subject = "Subject"; + String body = "Body"; + + employeeService.notifyEmployee(email, subject, body); + + // Output is printed by the custom behavior + } +} \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceSetterTest.java b/Week 04/Lecture 07/Assignment 03/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceSetterTest.java new file mode 100644 index 0000000..8e8aa4c --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7/src/test/java/com/example/lecture_7/service/EmployeeServiceSetterTest.java @@ -0,0 +1,44 @@ +package com.example.lecture_7.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; + +public class EmployeeServiceSetterTest { + + private EmployeeServiceSetter employeeService; + + @Mock + private EmailService emailService; + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + employeeService = new EmployeeServiceSetter(); + employeeService.setEmailService(emailService); + + // Custom behavior for emailService.sendEmail + doAnswer(invocation -> { + Object[] args = invocation.getArguments(); + System.out.println("Sending email to " + args[0]); + System.out.println("Subject: " + args[1]); + System.out.println("Body: " + args[2]); + return null; + }).when(emailService).sendEmail(anyString(), anyString(), anyString()); + } + + @Test + public void testNotifyEmployee() { + String email = "employee@example.com"; + String subject = "Subject"; + String body = "Body"; + + employeeService.notifyEmployee(email, subject, body); + + // Output is printed by the custom behavior + } +} \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7_1/.gitignore b/Week 04/Lecture 07/Assignment 03/lecture_7_1/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7_1/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7_1/.mvn/wrapper/maven-wrapper.properties b/Week 04/Lecture 07/Assignment 03/lecture_7_1/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..8f96f52 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7_1/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7_1/mvnw b/Week 04/Lecture 07/Assignment 03/lecture_7_1/mvnw new file mode 100644 index 0000000..d7c358e --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7_1/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7_1/mvnw.cmd b/Week 04/Lecture 07/Assignment 03/lecture_7_1/mvnw.cmd new file mode 100644 index 0000000..6f779cf --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7_1/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7_1/pom.xml b/Week 04/Lecture 07/Assignment 03/lecture_7_1/pom.xml new file mode 100644 index 0000000..8b7e3f7 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7_1/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.1 + + + com.example + lecture_7_1 + 0.0.1-SNAPSHOT + lecture_7_1 + Demo project for Spring Boot + + + + + + + + + + + + + + + 21 + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-web-services + + + org.projectlombok + lombok + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7_1/run.bat b/Week 04/Lecture 07/Assignment 03/lecture_7_1/run.bat new file mode 100644 index 0000000..1c63f72 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7_1/run.bat @@ -0,0 +1,3 @@ +@echo off +echo Building the project with Maven... +mvn clean install && java -jar target/lecture_7-1.0-SNAPSHOT.jar \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7_1/run.sh b/Week 04/Lecture 07/Assignment 03/lecture_7_1/run.sh new file mode 100644 index 0000000..fa82162 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7_1/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +echo Building the project with Maven... +mvn clean install && java -jar target/lecture_7_1-0.0.1-SNAPSHOT.jar \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7_1/src/main/java/com/example/lecture_7_1/Lecture71Application.java b/Week 04/Lecture 07/Assignment 03/lecture_7_1/src/main/java/com/example/lecture_7_1/Lecture71Application.java new file mode 100644 index 0000000..dd4a7c8 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7_1/src/main/java/com/example/lecture_7_1/Lecture71Application.java @@ -0,0 +1,13 @@ +package com.example.lecture_7_1; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Lecture71Application { + + public static void main(String[] args) { + SpringApplication.run(Lecture71Application.class, args); + } + +} diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7_1/src/main/java/com/example/lecture_7_1/controller/RequestController.java b/Week 04/Lecture 07/Assignment 03/lecture_7_1/src/main/java/com/example/lecture_7_1/controller/RequestController.java new file mode 100644 index 0000000..8fdc90d --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7_1/src/main/java/com/example/lecture_7_1/controller/RequestController.java @@ -0,0 +1,26 @@ +package com.example.lecture_7_1.controller; + +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.example.lecture_7_1.service.RequestScopedBean; + +@RestController +public class RequestController { + + private final ObjectFactory requestScopedBeanFactory; + + @Autowired + public RequestController(ObjectFactory requestScopedBeanFactory) { + this.requestScopedBeanFactory = requestScopedBeanFactory; + } + + @GetMapping("/testRequest") + public String testRequestScope() { + RequestScopedBean requestScopedBean = requestScopedBeanFactory.getObject(); + requestScopedBean.handleRequest(); + return "Check the console for the RequestScopedBean hash code."; + } +} \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7_1/src/main/java/com/example/lecture_7_1/service/RequestScopedBean.java b/Week 04/Lecture 07/Assignment 03/lecture_7_1/src/main/java/com/example/lecture_7_1/service/RequestScopedBean.java new file mode 100644 index 0000000..603c3d3 --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7_1/src/main/java/com/example/lecture_7_1/service/RequestScopedBean.java @@ -0,0 +1,15 @@ +package com.example.lecture_7_1.service; + +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.stereotype.Component; +import org.springframework.web.context.WebApplicationContext; + +@Component +@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS) +public class RequestScopedBean { + + public void handleRequest() { + System.out.println("RequestScopedBean instance hash: " + this.hashCode()); + } +} \ No newline at end of file diff --git a/Week 04/Lecture 07/Assignment 03/lecture_7_1/src/main/resources/application.properties b/Week 04/Lecture 07/Assignment 03/lecture_7_1/src/main/resources/application.properties new file mode 100644 index 0000000..7e09f2c --- /dev/null +++ b/Week 04/Lecture 07/Assignment 03/lecture_7_1/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=lecture_7_1 diff --git a/Week 04/Lecture 08/Assignment 01/README.md b/Week 04/Lecture 08/Assignment 01/README.md new file mode 100644 index 0000000..f23c662 --- /dev/null +++ b/Week 04/Lecture 08/Assignment 01/README.md @@ -0,0 +1,112 @@ +# πŸ‘©πŸ»β€πŸ« Lecture 08 - Spring Boot +> This repository is created as a part of assignment for Lecture 08 - Spring Boot + +## ✍ Assignment 01 - Create Spring Boot Project +### 🎯 **Create a Spring Boot Project** + +In this case i'm using Spring Initializr to init my Spring Boot project. Here is how i initialize my Spring Boot project. + +1. **Visit Spring Initializr:** Go to [start.spring.io](https://start.spring.io). + +2. **Configure the Project:** + - **Project:** Select `Maven Project` or `Gradle Project` (choose Maven in this case). + - **Language:** Choose `Java`. + - **Spring Boot:** Select the latest stable version (i choose 3.3.1 in this case). + - **Project Metadata:** + - **Group:** e.g., `com.example` + - **Artifact:** e.g., `lecture_8_1` + - **Name:** e.g., `lecture_8_1` + - **Description:** e.g., `Demo project for Spring Boot` + - **Package name:** e.g., `com.example.lecture_8_1` + - **Packaging:** Choose `Jar`. + - **Java Version:** Choose the appropriate version (i choose `21` in this case). + +3. **Add Dependencies:** Add the necessary dependencies for our project. Common ones include: + - `Spring Web` for web applications. + - `Spring Data JPA` for data access. + - `H2 Database` for an in-memory database (optional). + - `Thymeleaf` for templating (optional). + - `Spring Boot DevTools` for live reload (optional). + +4. **Generate the Project:** Click `Generate` to download a ZIP file. + +5. **Extract the ZIP:** Unzip the downloaded file to the preferred directory. + +The project structure must be looking something like this. +```bash +lecture_8_1 +β”œβ”€β”€ .mvn/wrapper/ +β”‚ └── maven-wrapper.properties +β”œβ”€β”€ src/main/ +β”‚ β”œβ”€β”€ java/com/example/lecture_8_1/ +β”‚ β”‚ β”œβ”€β”€ DemoController.java +β”‚ β”‚ └── Lecture81Application.java +β”‚ └── resources/ +β”‚ └── application.properties +β”œβ”€β”€ .gitignore +β”œβ”€β”€ mvnw +β”œβ”€β”€ mvnw.cmd +└── pom.xml +``` + +### βš™οΈ **Run the Spring Boot Application Locally** + +In this case i'm using maven to run my project. Here is how i do that. + +1. **Open Terminal:** + + Navigate to the root directory of the project where the `pom.xml` file is located. + +2. **Run the Application:** + + Execute the following command: + ```bash + $ ./mvnw spring-boot:run + ``` + +3. **Access the Application:** + + Once the application starts, we can access it typically at [http://localhost:8080](http://localhost:8080). + +### πŸš€ **Verify the Application** + +Open the browser and navigate to [http://localhost:8080](http://localhost:8080) to see if our Spring Boot application is running. + +Here’s a simple code i create to start a Spring Boot application. + +[**`Lecture81Application.java`**](/Week%2004/Lecture%2008/Assignment%2001/lecture_8_1/src/main/java/com/example/lecture_8_1/Lecture81Application.java) +```java +package com.example.lecture_8_1; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Lecture81Application { + public static void main(String[] args) { + SpringApplication.run(Lecture81Application.class, args); + } +} +``` + +**`DemoController.java`** +```java +package com.example.demo; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class DemoController { + @GetMapping("/") + public String hello() { + return "Hello, World!"; + } +} +``` + +In this example, when we navigate to [http://localhost:8080](http://localhost:8080), we should see "Hello, World!" displayed. + +Here is the result showed. + +![Screenshot](img/demo.png) \ No newline at end of file diff --git a/Week 04/Lecture 08/Assignment 01/img/demo.png b/Week 04/Lecture 08/Assignment 01/img/demo.png new file mode 100644 index 0000000..675456d Binary files /dev/null and b/Week 04/Lecture 08/Assignment 01/img/demo.png differ diff --git a/Week 04/Lecture 08/Assignment 01/lecture_8_1/.gitignore b/Week 04/Lecture 08/Assignment 01/lecture_8_1/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/Week 04/Lecture 08/Assignment 01/lecture_8_1/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/Week 04/Lecture 08/Assignment 01/lecture_8_1/.mvn/wrapper/maven-wrapper.properties b/Week 04/Lecture 08/Assignment 01/lecture_8_1/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..8f96f52 --- /dev/null +++ b/Week 04/Lecture 08/Assignment 01/lecture_8_1/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip diff --git a/Week 04/Lecture 08/Assignment 01/lecture_8_1/mvnw b/Week 04/Lecture 08/Assignment 01/lecture_8_1/mvnw new file mode 100644 index 0000000..d7c358e --- /dev/null +++ b/Week 04/Lecture 08/Assignment 01/lecture_8_1/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/Week 04/Lecture 08/Assignment 01/lecture_8_1/mvnw.cmd b/Week 04/Lecture 08/Assignment 01/lecture_8_1/mvnw.cmd new file mode 100644 index 0000000..6f779cf --- /dev/null +++ b/Week 04/Lecture 08/Assignment 01/lecture_8_1/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/Week 04/Lecture 08/Assignment 01/lecture_8_1/pom.xml b/Week 04/Lecture 08/Assignment 01/lecture_8_1/pom.xml new file mode 100644 index 0000000..5a245df --- /dev/null +++ b/Week 04/Lecture 08/Assignment 01/lecture_8_1/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.1 + + + com.example + lecture_8_1 + 0.0.1-SNAPSHOT + lecture_8_1 + Demo project for Spring Boot + + + + + + + + + + + + + + + 21 + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + com.h2database + h2 + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/Week 04/Lecture 08/Assignment 01/lecture_8_1/src/main/java/com/example/lecture_8_1/DemoController.java b/Week 04/Lecture 08/Assignment 01/lecture_8_1/src/main/java/com/example/lecture_8_1/DemoController.java new file mode 100644 index 0000000..af5664c --- /dev/null +++ b/Week 04/Lecture 08/Assignment 01/lecture_8_1/src/main/java/com/example/lecture_8_1/DemoController.java @@ -0,0 +1,12 @@ +package com.example.lecture_8_1; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class DemoController { + @GetMapping("/") + public String hello() { + return "Hello, World!"; + } +} \ No newline at end of file diff --git a/Week 04/Lecture 08/Assignment 01/lecture_8_1/src/main/java/com/example/lecture_8_1/Lecture81Application.java b/Week 04/Lecture 08/Assignment 01/lecture_8_1/src/main/java/com/example/lecture_8_1/Lecture81Application.java new file mode 100644 index 0000000..e4c5618 --- /dev/null +++ b/Week 04/Lecture 08/Assignment 01/lecture_8_1/src/main/java/com/example/lecture_8_1/Lecture81Application.java @@ -0,0 +1,11 @@ +package com.example.lecture_8_1; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Lecture81Application { + public static void main(String[] args) { + SpringApplication.run(Lecture81Application.class, args); + } +} diff --git a/Week 04/Lecture 08/Assignment 01/lecture_8_1/src/main/resources/application.properties b/Week 04/Lecture 08/Assignment 01/lecture_8_1/src/main/resources/application.properties new file mode 100644 index 0000000..ef3b805 --- /dev/null +++ b/Week 04/Lecture 08/Assignment 01/lecture_8_1/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=lecture_8_1 diff --git a/Week 04/Lecture 08/Assignment 01/lecture_8_1/src/test/java/com/example/lecture_8_1/Lecture81ApplicationTests.java b/Week 04/Lecture 08/Assignment 01/lecture_8_1/src/test/java/com/example/lecture_8_1/Lecture81ApplicationTests.java new file mode 100644 index 0000000..28d9113 --- /dev/null +++ b/Week 04/Lecture 08/Assignment 01/lecture_8_1/src/test/java/com/example/lecture_8_1/Lecture81ApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.lecture_8_1; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class Lecture81ApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/Week 04/Lecture 08/Assignment 02/Lecture 08 - Assignment 02.postman_collection.json b/Week 04/Lecture 08/Assignment 02/Lecture 08 - Assignment 02.postman_collection.json new file mode 100644 index 0000000..4442fd6 --- /dev/null +++ b/Week 04/Lecture 08/Assignment 02/Lecture 08 - Assignment 02.postman_collection.json @@ -0,0 +1,100 @@ +{ + "info": { + "_postman_id": "859f23f0-952d-4e0d-b176-9816219a20e5", + "name": "Lecture 08 - Assignment 02", + "description": "This postman collection is created by Michael Leon as a part of assignment 02 for Lecture 05 - Basic Backend, Spring", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", + "_exporter_id": "34693283" + }, + "item": [ + { + "name": "Get All Employees", + "request": { + "method": "GET", + "header": [], + "url": "localhost:8080/api/v1/employee" + }, + "response": [] + }, + { + "name": "Get Employee By ID", + "request": { + "method": "GET", + "header": [], + "url": "localhost:8080/api/v1/employee/3ccd3c90-890e-41c4-9fa3-456f3d97f999" + }, + "response": [] + }, + { + "name": "Add New Employee", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"id\": \"b002747b-4cc3-4a81-bd7e-e3184da0410a\",\r\n \"name\": \"Michael Leon\",\r\n \"dob\": \"2003-12-18\",\r\n \"address\": \"Anytime anywhere\",\r\n \"department\": \"MOBILE\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/employee" + }, + "response": [] + }, + { + "name": "Edit Employee", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"Leon Michael\",\r\n \"dob\": \"2003-12-18\",\r\n \"address\": \"Anytime anywhere anyplace\",\r\n \"department\": \"MOBILE\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/employee/b002747b-4cc3-4a81-bd7e-e3184da0410a" + }, + "response": [] + }, + { + "name": "Delete Employee", + "request": { + "method": "DELETE", + "header": [], + "url": "localhost:8080/api/v1/employee/b002747b-4cc3-4a81-bd7e-e3184da0410a" + }, + "response": [] + }, + { + "name": "Get Employees by Department", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8080/api/v1/employee?department=QA", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "api", + "v1", + "employee" + ], + "query": [ + { + "key": "department", + "value": "QA" + } + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/Week 04/Lecture 08/Assignment 02/README.md b/Week 04/Lecture 08/Assignment 02/README.md new file mode 100644 index 0000000..9e8d037 --- /dev/null +++ b/Week 04/Lecture 08/Assignment 02/README.md @@ -0,0 +1,248 @@ +# πŸ‘©πŸ»β€πŸ« Lecture 08 - Spring Boot +> This repository is created as a part of assignment for Lecture 08 - Spring Boot + +## ✍ Assignment 02 - CRUD Project for Employee Management with JDBC Template +### πŸ› οΈ 1. Set Up Spring Boot Project + +Follow the steps to create a new Spring Boot project as i explained in the previous assignment. Ensure that we add `Spring Web`, `Spring Data JPA`, `Spring Boot DevTools`, and `MySQL Driver` as dependencies. + +### πŸ—„οΈ 2. Create Employee Table in MySQL + +Execute the following SQL script to create the database and the `employee` table in MySQL: + +```sql +-- Create the database +CREATE DATABASE week4_lecture8; + +-- Use the database +USE week4_lecture8; + +-- Create the employee table +CREATE TABLE employee ( + id VARCHAR(50) NOT NULL, + name VARCHAR(100) COLLATE utf8mb4_unicode_ci NOT NULL, + dob DATE NOT NULL, + address VARCHAR(255) NOT NULL, + department VARCHAR(100) NOT NULL, + PRIMARY KEY (id) +); + +-- Insert dummy data into the employee table +INSERT INTO employee (id, name, dob, address, department) VALUES +('1caa1b8e-678c-41a2-9d91-234f1d75f777', 'Alice Johnson', '1985-05-15', '123 Elm Street, Springfield', 'WEB'), +('2bff2b8f-789d-41b3-9e92-345f2d86f888', 'Bob Smith', '1979-12-22', '456 Oak Avenue, Springfield', 'SYSTEM'), +('3ccd3c90-890e-41c4-9fa3-456f3d97f999', 'Carol Davis', '1990-08-12', '789 Pine Road, Springfield', 'MOBILE'), +('4dde4d91-901f-41d5-9fb4-567f4e08faaa', 'David Wilson', '1988-07-04', '321 Maple Street, Springfield', 'QA'), +('5eef5e92-0120-41e6-9fc5-678f5e19fbbb', 'Eva Brown', '1992-03-28', '654 Birch Lane, Springfield', 'ADMIN'); +``` + +### βš™οΈ 3. Configure Data Source in `application.properties` + +Edit the [`src/main/resources/application.properties`](/Week%2004/Lecture%2008/Assignment%2002/lecture_8_2/src/main/resources/application.properties) file to include the MySQL data source configuration. + +```properties +spring.application.name=lecture_8_2 + +spring.datasource.url=jdbc:mysql://localhost:3308/week4_lecture8?allowPublicKeyRetrieval=true&useSSL=false +spring.datasource.username=root +spring.datasource.password=Michaeleon16606_ +spring.datasource.driver-class-name=com.mysql.jdbc.Driver + +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true +``` + +Here is the detail explanation. +- `spring.datasource.url` specifies the JDBC URL for MySQL database. +- `spring.datasource.username` and `spring.datasource.password` set the database credentials. +- `spring.datasource.driver-class-name` defines the JDBC driver class. +- `spring.jpa.hibernate.ddl-auto` specifies the DDL mode (`update` to create/update schema automatically). +- `spring.jpa.show-sql` enables logging of SQL statements. + +### πŸ“‹ 4. Create Employee Model + +Create a new Java class `Employee` in [`src/main/java/com/example/lecture_8_2/model/Employee.java`](/Week%2004/Lecture%2008/Assignment%2002/lecture_8_2/src/main/java/com/example/lecture_8_2/model/Employee.java) + +```java +package com.example.lecture_8_2.model; + +import java.io.Serializable; +import java.time.LocalDate; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Entity +@NoArgsConstructor +public class Employee implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + private String id; + private String name; + private LocalDate dob; + private String address; + private String department; + + public Employee(String id, String name, LocalDate dob, String address, String department) { + this.id = id; + this.name = name; + this.dob = dob; + this.address = address; + this.department = department; + } +} +``` + +### πŸ“¦ 5. Create Employee Repository + +Create a new Java class `EmployeeRepository` in [`src/main/java/com/example/lecture_8_2/repository/EmployeeRepository.java`](/Week%2004/Lecture%2008/Assignment%2002/lecture_8_2/src/main/java/com/example/lecture_8_2/repository/EmployeeRepository.java). + +Here is the explanation of each method on that class. + +1. **RowMapper Definition (`EMPLOYEE_ROW_MAPPER`):** + + Maps a row from the ResultSet to an Employee object. + +2. **Find All Employees (`findAll`):** + + Retrieves all employees from the `employee` table. + +3. **Find Employee by ID (`findById`):** + + - Retrieves an `employee` based on the provided ID. + - Uses `Optional` to handle the possibility of no result found. + +4. **Save New Employee (`save`):** + + Inserts a new employee into the employee table. + +5. **Update Existing Employee (`update`):** + + Updates an existing employee's details based on their ID. + +6. **Delete Employee by ID (`deleteById`):** + + Deletes an employee from the employee table based on their ID. + +7. **Find Employees by Department (`findByDepartmentId`):** + + Retrieves employees that belong to a specific department. + +### πŸ•ΉοΈ 6. Create Employee Controller + +Create a new Java class `EmployeeController` in [`src/main/java/com/example/lecture_8_2/controller/EmployeeController.java`](/Week%2004/Lecture%2008/Assignment%2002/lecture_8_2/src/main/java/com/example/lecture_8_2/controller/EmployeeController.java). + +**Explanation:** +- `@RestController` annotation marks this class as a controller for handling HTTP requests. +- `@RequestMapping("/api/v1/employee")` sets the base URL for all methods in this controller. +- HTTP methods `GET`, `POST`, `PUT`, and `DELETE` are used to handle respective CRUD operations. + +### πŸ›’οΈ 7. Create SQL Script for Table and Data + +You can also create an SQL file `schema.sql` in [`src/main/resources`](/Week%2004/Lecture%2008/Assignment%2002/lecture_8_2/src/main/resources/schema.sql) to create the table automatically on startup if using Spring Boot’s `DataSource` initialization feature: + +This script runs automatically if `spring.datasource.initialize=true` (which is the default). + +### πŸ•΅ 8. Testing the CRUD Application +#### 🌳 Project Structure +```bash +lecture_8_2 +β”œβ”€β”€ .mvn/wrapper/ +β”‚ └── maven-wrapper.properties +β”œβ”€β”€ src/main/ +β”‚ β”œβ”€β”€ java/com/example/lecture_8_2/ +β”‚ β”‚ β”œβ”€β”€ controller/ +β”‚ β”‚ β”‚ └── EmployeeController.java +β”‚ β”‚ β”œβ”€β”€ model/ +β”‚ β”‚ β”‚ └── Employee.java +β”‚ β”‚ β”œβ”€β”€ repository/ +β”‚ β”‚ β”‚ └── EmployeeRepository.java +β”‚ β”‚ └── Lecture82Application.java +β”‚ └── resources/ +β”‚ β”œβ”€β”€ schema.sql +β”‚ └── application.properties +β”œβ”€β”€ .gitignore +β”œβ”€β”€ mvnw +β”œβ”€β”€ mvnw.cmd +└── pom.xml +``` + +#### βš™οΈ **Run the Spring Boot Application Locally** + +In this case i'm using maven to run my project. Here is how i do that. + +1. **Open Terminal:** + + Navigate to the root directory of the project where the `pom.xml` file is located. + +2. **Run the Application:** + + Execute the following command: + ```bash + $ ./mvnw spring-boot:run + ``` + +3. **Access the Application:** + + Once the application starts, we can access it typically at [http://localhost:8080](http://localhost:8080). + +#### πŸš€ **Verify the Application** +Here is some result of the APIs created. +1. **Get All Employees** + `(GET /api/v1/employee)` + + ![Screenshot](img/api1.png) +2. **Get Employee By ID** + `(GET /api/v1/employee/3ccd3c90-890e-41c4-9fa3-456f3d97f999)` + + ![Screenshot](img/api2.png) +3. **Add New Employee** + `(POST /api/v1/employee)` + + Body (Raw): + ```json + { + "id": "b002747b-4cc3-4a81-bd7e-e3184da0410a", + "name": "Michael Leon", + "dob": "2003-12-18", + "address": "Anytime anywhere", + "department": "MOBILE" + } + ``` + + ![Screenshot](img/api3.png) + ![Screenshot](img/api32.png) +4. **Edit Employee** + `(PUT /api/v1/employee/b002747b-4cc3-4a81-bd7e-e3184da0410a)` + + Body (Raw): + ```json + { + "name": "Leon Michael", + "dob": "2003-12-18", + "address": "Anytime anywhere anyplace", + "department": "MOBILE" + } + ``` + + ![Screenshot](img/api4.png) + ![Screenshot](img/api42.png) +5. **Delete Employee** + `(DELETE /api/v1/employee/b002747b-4cc3-4a81-bd7e-e3184da0410a)` + + ![Screenshot](img/api5.png) + ![Screenshot](img/api52.png) +6. **Get Employees by Department** + `(GET /api/v1/employee?department=QA)` + + ![Screenshot](img/api6.png) + +#### πŸ“¬ Postman Collection +Here is the [postman collection](/Week%2004/Lecture%2008/Assignment%2002/Lecture%2008%20-%20Assignment%2002.postman_collection.json) you can use to demo the API functionality. \ No newline at end of file diff --git a/Week 04/Lecture 08/Assignment 02/img/api1.png b/Week 04/Lecture 08/Assignment 02/img/api1.png new file mode 100644 index 0000000..81ef5e7 Binary files /dev/null and b/Week 04/Lecture 08/Assignment 02/img/api1.png differ diff --git a/Week 04/Lecture 08/Assignment 02/img/api2.png b/Week 04/Lecture 08/Assignment 02/img/api2.png new file mode 100644 index 0000000..b53bd4e Binary files /dev/null and b/Week 04/Lecture 08/Assignment 02/img/api2.png differ diff --git a/Week 04/Lecture 08/Assignment 02/img/api3.png b/Week 04/Lecture 08/Assignment 02/img/api3.png new file mode 100644 index 0000000..df9fb47 Binary files /dev/null and b/Week 04/Lecture 08/Assignment 02/img/api3.png differ diff --git a/Week 04/Lecture 08/Assignment 02/img/api32.png b/Week 04/Lecture 08/Assignment 02/img/api32.png new file mode 100644 index 0000000..f26ec26 Binary files /dev/null and b/Week 04/Lecture 08/Assignment 02/img/api32.png differ diff --git a/Week 04/Lecture 08/Assignment 02/img/api4.png b/Week 04/Lecture 08/Assignment 02/img/api4.png new file mode 100644 index 0000000..7c1b6a3 Binary files /dev/null and b/Week 04/Lecture 08/Assignment 02/img/api4.png differ diff --git a/Week 04/Lecture 08/Assignment 02/img/api42.png b/Week 04/Lecture 08/Assignment 02/img/api42.png new file mode 100644 index 0000000..0566545 Binary files /dev/null and b/Week 04/Lecture 08/Assignment 02/img/api42.png differ diff --git a/Week 04/Lecture 08/Assignment 02/img/api5.png b/Week 04/Lecture 08/Assignment 02/img/api5.png new file mode 100644 index 0000000..e911b35 Binary files /dev/null and b/Week 04/Lecture 08/Assignment 02/img/api5.png differ diff --git a/Week 04/Lecture 08/Assignment 02/img/api52.png b/Week 04/Lecture 08/Assignment 02/img/api52.png new file mode 100644 index 0000000..71f958f Binary files /dev/null and b/Week 04/Lecture 08/Assignment 02/img/api52.png differ diff --git a/Week 04/Lecture 08/Assignment 02/img/api6.png b/Week 04/Lecture 08/Assignment 02/img/api6.png new file mode 100644 index 0000000..87f2ede Binary files /dev/null and b/Week 04/Lecture 08/Assignment 02/img/api6.png differ diff --git a/Week 04/Lecture 08/Assignment 02/lecture_8_2/.gitignore b/Week 04/Lecture 08/Assignment 02/lecture_8_2/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/Week 04/Lecture 08/Assignment 02/lecture_8_2/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/Week 04/Lecture 08/Assignment 02/lecture_8_2/.mvn/wrapper/maven-wrapper.properties b/Week 04/Lecture 08/Assignment 02/lecture_8_2/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..8f96f52 --- /dev/null +++ b/Week 04/Lecture 08/Assignment 02/lecture_8_2/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip diff --git a/Week 04/Lecture 08/Assignment 02/lecture_8_2/mvnw b/Week 04/Lecture 08/Assignment 02/lecture_8_2/mvnw new file mode 100644 index 0000000..d7c358e --- /dev/null +++ b/Week 04/Lecture 08/Assignment 02/lecture_8_2/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/Week 04/Lecture 08/Assignment 02/lecture_8_2/mvnw.cmd b/Week 04/Lecture 08/Assignment 02/lecture_8_2/mvnw.cmd new file mode 100644 index 0000000..6f779cf --- /dev/null +++ b/Week 04/Lecture 08/Assignment 02/lecture_8_2/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/Week 04/Lecture 08/Assignment 02/lecture_8_2/pom.xml b/Week 04/Lecture 08/Assignment 02/lecture_8_2/pom.xml new file mode 100644 index 0000000..f7f86b4 --- /dev/null +++ b/Week 04/Lecture 08/Assignment 02/lecture_8_2/pom.xml @@ -0,0 +1,85 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.1 + + + com.example + lecture_8_2 + 0.0.1-SNAPSHOT + lecture_8_2 + Demo project for Spring Boot + + + + + + + + + + + + + + + 21 + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-web-services + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + com.mysql + mysql-connector-j + runtime + + + mysql + mysql-connector-java + 8.0.33 + + + org.projectlombok + lombok + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/main/java/com/example/lecture_8_2/Lecture82Application.java b/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/main/java/com/example/lecture_8_2/Lecture82Application.java new file mode 100644 index 0000000..37f29ac --- /dev/null +++ b/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/main/java/com/example/lecture_8_2/Lecture82Application.java @@ -0,0 +1,11 @@ +package com.example.lecture_8_2; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Lecture82Application { + public static void main(String[] args) { + SpringApplication.run(Lecture82Application.class, args); + } +} diff --git a/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/main/java/com/example/lecture_8_2/controller/EmployeeController.java b/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/main/java/com/example/lecture_8_2/controller/EmployeeController.java new file mode 100644 index 0000000..ba516ce --- /dev/null +++ b/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/main/java/com/example/lecture_8_2/controller/EmployeeController.java @@ -0,0 +1,127 @@ +package com.example.lecture_8_2.controller; + +import java.util.List; +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.example.lecture_8_2.model.Employee; +import com.example.lecture_8_2.repository.EmployeeRepository; + +import lombok.AllArgsConstructor; + +@RestController +@RequestMapping("/api/v1/employee") +@AllArgsConstructor +public class EmployeeController { + + @Autowired + private final EmployeeRepository employeeRepository; + + /** + * This method retrieves employees from the database. + * If a department query parameter is provided, it filters employees by department. + * + * @param department Optional query parameter to filter employees by department. + * @return ResponseEntity> - A response entity containing a list of employees. + * If the list is empty, it returns a HTTP status code 204 (No Content). + * If the operation is successful, it returns a HTTP status code 200 (OK) with the list of employees. + */ + @GetMapping + public ResponseEntity> listAllEmployee(@RequestParam(value = "department", required = false) String departmentId) { + List employees; + + if (departmentId != null && !departmentId.isEmpty()) { + employees = employeeRepository.findByDepartmentId(departmentId); + } else { + employees = employeeRepository.findAll(); + } + + if (employees.isEmpty()) { + return ResponseEntity.noContent().build(); + } + + return ResponseEntity.ok(employees); + } + + /** + * This method retrieves an employee from the database by its id. + * + * @param id The unique identifier of the employee. + * @return ResponseEntity - A response entity containing the employee if found, or a 404 Not Found status code if not found. + */ + @GetMapping(value = "/{id}") + public ResponseEntity findEmployeeById(@PathVariable("id") String id) { + Optional employeeOpt= employeeRepository.findById(id); + if(employeeOpt.isPresent()) { + return ResponseEntity.ok(employeeOpt.get()); + } + return ResponseEntity.notFound().build(); + } + + /** + * This method saves an employee to the database. + * + * @param employee The employee object to be saved. + * @return ResponseEntity - A response entity containing the saved employee. + * If the employee already exists in the database, it returns a HTTP status code 400 (Bad Request). + */ + @PostMapping + public ResponseEntity saveEmployee(@RequestBody Employee employee) { + Optional employeeOpt = employeeRepository.findById(employee.getId()); + if(employeeOpt.isPresent()) { + return ResponseEntity.badRequest().build(); + } + return ResponseEntity.ok(employeeRepository.save(employee)); + } + + /** + * This method updates an employee in the database by its id. + * + * @param id The unique identifier of the employee. + * @param employeeForm The updated employee information. + * @return ResponseEntity - A response entity containing the updated employee if found, or a 404 Not Found status code if not found. + */ + @PutMapping(value = "/{id}") + public ResponseEntity updateEmployee(@PathVariable(value = "id") String id, + @RequestBody Employee employeeForm) { + Optional employeeOpt = employeeRepository.findById(id); + if(employeeOpt.isPresent()) { + Employee employee = employeeOpt.get(); + employee.setName(employeeForm.getName()); + employee.setDob(employeeForm.getDob()); + employee.setAddress(employeeForm.getAddress()); + employee.setDepartment(employeeForm.getDepartment()); + + Employee updatedEmployee = employeeRepository.update(employee); + return ResponseEntity.ok(updatedEmployee); + } + return ResponseEntity.notFound().build(); + } + + /** + * This method deletes an employee from the database by its id. + * + * @param id The unique identifier of the employee to be deleted. + * @return ResponseEntity - A response entity containing the deleted employee if found, or a 404 Not Found status code if not found. + */ + @DeleteMapping(value = "/{id}") + public ResponseEntity deleteEmployee(@PathVariable(value = "id") String id) { + Optional employeeOpt = employeeRepository.findById(id); + if(employeeOpt.isPresent()) { + employeeRepository.deleteById(employeeOpt.get().getId()); + return ResponseEntity.ok().build(); + } + return ResponseEntity.notFound().build(); + } +} \ No newline at end of file diff --git a/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/main/java/com/example/lecture_8_2/model/Employee.java b/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/main/java/com/example/lecture_8_2/model/Employee.java new file mode 100644 index 0000000..85ed1ed --- /dev/null +++ b/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/main/java/com/example/lecture_8_2/model/Employee.java @@ -0,0 +1,34 @@ +package com.example.lecture_8_2.model; + +import java.io.Serializable; +import java.time.LocalDate; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Entity +@NoArgsConstructor +public class Employee implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + private String id; + private String name; + private LocalDate dob; + private String address; + private String department; + + public Employee(String id, String name, LocalDate dob, String address, String department) { + this.id = id; + this.name = name; + this.dob = dob; + this.address = address; + this.department = department; + } +} \ No newline at end of file diff --git a/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/main/java/com/example/lecture_8_2/repository/EmployeeRepository.java b/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/main/java/com/example/lecture_8_2/repository/EmployeeRepository.java new file mode 100644 index 0000000..99cfc3f --- /dev/null +++ b/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/main/java/com/example/lecture_8_2/repository/EmployeeRepository.java @@ -0,0 +1,76 @@ +package com.example.lecture_8_2.repository; + +import java.sql.ResultSet; +import java.util.List; +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +import com.example.lecture_8_2.model.Employee; + +@Repository +public class EmployeeRepository { + + @Autowired + private JdbcTemplate jdbcTemplate; + + // RowMapper to map ResultSet to Employee + private static final RowMapper EMPLOYEE_ROW_MAPPER = (ResultSet rs, int rowNum) -> { + Employee employee = new Employee(); + employee.setId(rs.getString("id")); + employee.setName(rs.getString("name")); + employee.setDob(rs.getDate("dob").toLocalDate()); + employee.setAddress(rs.getString("address")); + employee.setDepartment(rs.getString("department")); + return employee; + }; + + // Find all employees + public List findAll() { + String sql = "SELECT * FROM employee"; + return jdbcTemplate.query(sql, EMPLOYEE_ROW_MAPPER); + } + + // Find employee by ID + public Optional findById(String id) { + String sql = "SELECT * FROM employee WHERE id = ?"; + try { + Employee employee = jdbcTemplate.queryForObject(sql, EMPLOYEE_ROW_MAPPER, id); + return Optional.ofNullable(employee); + } catch (DataAccessException e) { + return Optional.empty(); + } + } + + // Save new employee + public Employee save(Employee employee) { + String sql = "INSERT INTO employee (id, name, dob, address, department) VALUES (?, ?, ?, ?, ?)"; + jdbcTemplate.update(sql, employee.getId(), employee.getName(), employee.getDob(), + employee.getAddress(), employee.getDepartment()); + return employee; + } + + // Update existing employee + public Employee update(Employee employee) { + String sql = "UPDATE employee SET name = ?, dob = ?, address = ?, department = ? WHERE id = ?"; + jdbcTemplate.update(sql, employee.getName(), employee.getDob(), + employee.getAddress(), employee.getDepartment(), employee.getId()); + return employee; + } + + // Delete employee by ID + public void deleteById(String id) { + String sql = "DELETE FROM employee WHERE id = ?"; + jdbcTemplate.update(sql, id); + } + + // Find employees by department + public List findByDepartmentId(String department) { + String sql = "SELECT * FROM employee WHERE department = ?"; + return jdbcTemplate.query(sql, EMPLOYEE_ROW_MAPPER, department); + } +} \ No newline at end of file diff --git a/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/main/resources/application.properties b/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/main/resources/application.properties new file mode 100644 index 0000000..d945d2d --- /dev/null +++ b/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/main/resources/application.properties @@ -0,0 +1,9 @@ +spring.application.name=lecture_8_2 + +spring.datasource.url=jdbc:mysql://localhost:3308/week4_lecture8?allowPublicKeyRetrieval=true&useSSL=false +spring.datasource.username=root +spring.datasource.password=Michaeleon16606_ +spring.datasource.driver-class-name=com.mysql.jdbc.Driver + +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true \ No newline at end of file diff --git a/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/main/resources/schema.sql b/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/main/resources/schema.sql new file mode 100644 index 0000000..e7d6cfc --- /dev/null +++ b/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/main/resources/schema.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS employee ( + id VARCHAR(50) NOT NULL, + name VARCHAR(100) COLLATE utf8mb4_unicode_ci NOT NULL, + dob DATE NOT NULL, + address VARCHAR(255) NOT NULL, + department VARCHAR(100) NOT NULL, + PRIMARY KEY (id) +); \ No newline at end of file diff --git a/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/test/java/com/example/lecture_8_2/Lecture82ApplicationTests.java b/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/test/java/com/example/lecture_8_2/Lecture82ApplicationTests.java new file mode 100644 index 0000000..615c272 --- /dev/null +++ b/Week 04/Lecture 08/Assignment 02/lecture_8_2/src/test/java/com/example/lecture_8_2/Lecture82ApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.lecture_8_2; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class Lecture82ApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/Week 04/Lecture 08/Assignment 03/Lecture 08 - Assignment 03.postman_collection.json b/Week 04/Lecture 08/Assignment 03/Lecture 08 - Assignment 03.postman_collection.json new file mode 100644 index 0000000..7a06e2b --- /dev/null +++ b/Week 04/Lecture 08/Assignment 03/Lecture 08 - Assignment 03.postman_collection.json @@ -0,0 +1,109 @@ +{ + "info": { + "_postman_id": "b86c9309-f939-464c-96d1-ef8938f15793", + "name": "Lecture 08 - Assignment 03", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", + "_exporter_id": "34693283" + }, + "item": [ + { + "name": "Get All Employees From DS1", + "request": { + "method": "GET", + "header": [], + "url": "localhost:8080/api/v1/employee/ds1" + }, + "response": [] + }, + { + "name": "Get All Employees From DS2", + "request": { + "method": "GET", + "header": [], + "url": "localhost:8080/api/v1/employee/ds2" + }, + "response": [] + }, + { + "name": "Get Employee By ID From DS1", + "request": { + "method": "GET", + "header": [], + "url": "localhost:8080/api/v1/employee/ds1/3ccd3c90-890e-41c4-9fa3-456f3d97f999" + }, + "response": [] + }, + { + "name": "Get Employee By ID From DS2", + "request": { + "method": "GET", + "header": [], + "url": "localhost:8080/api/v1/employee/ds2/3ccd3c90-890e-41c4-9fa3-456f3d97f999" + }, + "response": [] + }, + { + "name": "Add New Employee", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"id\": \"b002747b-4cc3-4a81-bd7e-e3184da0410a\",\r\n \"name\": \"Michael Leon\",\r\n \"dob\": \"2003-12-18\",\r\n \"address\": \"Anytime anywhere\",\r\n \"department\": \"MOBILE\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/employee" + }, + "response": [] + }, + { + "name": "Edit Employee", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"Leon Michael\",\r\n \"dob\": \"2003-12-18\",\r\n \"address\": \"Anytime anywhere anyplace\",\r\n \"department\": \"MOBILE\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/employee/b002747b-4cc3-4a81-bd7e-e3184da0410a" + }, + "response": [] + }, + { + "name": "Delete Employee", + "request": { + "method": "DELETE", + "header": [], + "url": "localhost:8080/api/v1/employee/b002747b-4cc3-4a81-bd7e-e3184da0410a" + }, + "response": [] + }, + { + "name": "Add New Employee Fail", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"id\": \"b002747b-4cc3-4a81-bd7e-e3184da0410a\",\r\n \"name\": \"Michael Leon\",\r\n \"dob\": \"2003-12-18\",\r\n \"address\": \"Anytime anywhere\",\r\n \"department\": \"MOBILE\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "localhost:8080/api/v1/employee/fail" + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/Week 04/Lecture 08/Assignment 03/README.md b/Week 04/Lecture 08/Assignment 03/README.md new file mode 100644 index 0000000..52df514 --- /dev/null +++ b/Week 04/Lecture 08/Assignment 03/README.md @@ -0,0 +1,314 @@ +# πŸ‘©πŸ»β€πŸ« Lecture 08 - Spring Boot +> This repository is created as a part of assignment for Lecture 08 - Spring Boot + +## πŸ’Ύ Assignment 03 - Multiple Datasources and Transactions +### πŸ”„ 1. Change DataSource to Use Bean Configuration + +#### πŸ“£ **Update `application.properties` to use Multiple DataSource Configuration** + +We need to add properties for each data source in the `application.properties`. + +```properties +spring.application.name=lecture_8_2 + +# DataSource 1 +spring.datasource1.jdbc-url=jdbc:mysql://localhost:3308/week4_lecture8?allowPublicKeyRetrieval=true&useSSL=false +spring.datasource1.username=root +spring.datasource1.password=Michaeleon16606_ +spring.datasource1.driver-class-name=com.mysql.jdbc.Driver + +# DataSource 2 +spring.datasource2.jdbc-url=jdbc:mysql://localhost:3308/week4_lecture8_clone?allowPublicKeyRetrieval=true&useSSL=false +spring.datasource2.username=root +spring.datasource2.password=Michaeleon16606_ +spring.datasource2.driver-class-name=com.mysql.cj.jdbc.Driver + +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true +``` + +#### πŸ”§ **Create DataSource Configuration Class** + +Create a new Java configuration class to define the `DataSource` bean. Define each `DataSource` as a separate bean in a configuration class. It is defined on [this file](/Week%2004/Lecture%2008/Assignment%2003/lecture_8_2/src/main/java/com/example/lecture_8_2/config/DataSourceConfig.java) + +#### 🀝 **Dependencies** + +Make sure to have the following dependencies in the `pom.xml`: + +```xml + + org.springframework.boot + spring-boot-starter-data-jdbc + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + org.springframework.data + spring-data-commons + 3.2.0 + +``` + +--- + +### 🧾 2. Handle Transactions for Insert/Update + +#### πŸ€” What is a Transaction? + +In database terms, a **transaction** is a sequence of operations performed as a single logical unit of work. The key properties of a transaction are encapsulated in the ACID acronym: + +- **Atomicity**: Ensures that all operations within a transaction are completed; if not, the transaction is aborted, and no changes are applied. +- **Consistency**: Ensures that the database moves from one consistent state to another consistent state. +- **Isolation**: Ensures that transactions are executed in isolation from one another. +- **Durability**: Ensures that once a transaction has been committed, it will remain so, even in the event of a system failure. + +#### πŸ”’ Transaction in the Context of Multiple Data Sources + +When working with multiple data sources, transactions become more complex because we need to ensure consistency across different databases. This is typically known as a **distributed transaction**. Here’s how it applies: + +1. **Distributed Transactions**: If we need to update the same `Employee` entity in two different databases and want to ensure that either both updates succeed or both fail, we need a distributed transaction. + +2. **ACID in Distributed Transactions**: + - **Atomicity**: Both databases should either commit the transaction or roll back in case of any failure. + - **Consistency**: Each database should reflect a consistent state post-transaction. + - **Isolation**: Changes made in one transaction should not affect another until the transaction is complete. + - **Durability**: Once committed, changes should persist in both databases. + +#### πŸ‘¨πŸ»β€πŸ’» Implementing Transactions in Multiple Data Sources with Spring + +Spring provides ways to manage transactions, even across multiple data sources, using its transaction management abstractions. By **using `@Transactional` annotation** to methods in the service layer with, we ensure that they are executed within a transaction context. + +1. [**`DataSource` Configuration**](/Week%2004/Lecture%2008/Assignment%2003/lecture_8_2/src/main/java/com/example/lecture_8_2/config/DataSourceConfig.java) + + Ensure that the two data sources and their corresponding transaction managers are configured. + +2. [**Repository Layer**](/Week%2004/Lecture%2008/Assignment%2003/lecture_8_2/src/main/java/com/example/lecture_8_2/repository/EmployeeRepository.java) + + Ensure that the two data sources and their corresponding transaction managers are configured by modifying `EmployeeRepository` class. The repository layer will manage the transactions across both data sources. + +3. [**Service Layer**](/Week%2004/Lecture%2008/Assignment%2003/lecture_8_2/src/main/java/com/example/lecture_8_2/service/EmployeeService.java) + + This layer taking control on how the transaction for both datasources is handled gracefully. This service including `commit` and `rollback` mechanism over stransactions. + +4. [**Controller Layer**](/Week%2004/Lecture%2008/Assignment%2003/lecture_8_2/src/main/java/com/example/lecture_8_2/controller/EmployeeController.java) + Update the `EmployeeController` to use `EmployeeService` for transaction handling. + +#### Simulation +##### πŸ—„οΈ Create Another Employee Table in MySQL + +Execute the following SQL script to create the database and the `employee` table in MySQL on another database: + +```sql +-- Create the database +CREATE DATABASE week4_lecture8_clone; + +-- Use the database +USE week4_lecture8_clone; + +-- Create the employee table +CREATE TABLE employee ( + id VARCHAR(50) NOT NULL, + name VARCHAR(100) COLLATE utf8mb4_unicode_ci NOT NULL, + dob DATE NOT NULL, + address VARCHAR(255) NOT NULL, + department VARCHAR(100) NOT NULL, + PRIMARY KEY (id) +); + +-- Insert dummy data into the employee table +INSERT INTO employee (id, name, dob, address, department) VALUES +('1caa1b8e-678c-41a2-9d91-234f1d75f777', 'Alice Johnson', '1985-05-15', '123 Elm Street, Springfield', 'WEB'), +('2bff2b8f-789d-41b3-9e92-345f2d86f888', 'Bob Smith', '1979-12-22', '456 Oak Avenue, Springfield', 'SYSTEM'), +('3ccd3c90-890e-41c4-9fa3-456f3d97f999', 'Carol Davis', '1990-08-12', '789 Pine Road, Springfield', 'MOBILE'), +('4dde4d91-901f-41d5-9fb4-567f4e08faaa', 'David Wilson', '1988-07-04', '321 Maple Street, Springfield', 'QA'), +('5eef5e92-0120-41e6-9fc5-678f5e19fbbb', 'Eva Brown', '1992-03-28', '654 Birch Lane, Springfield', 'ADMIN'); +``` + +##### 🌳 Project Structure +```bash +lecture_8_2 +β”œβ”€β”€ .mvn/wrapper/ +β”‚ └── maven-wrapper.properties +β”œβ”€β”€ src/main/ +β”‚ β”œβ”€β”€ java/com/example/lecture_8_2/ +β”‚ β”‚ β”œβ”€β”€ config/ +β”‚ β”‚ β”‚ └── DataSourceConfig.java +β”‚ β”‚ β”œβ”€β”€ controller/ +β”‚ β”‚ β”‚ └── EmployeeController.java +β”‚ β”‚ β”œβ”€β”€ model/ +β”‚ β”‚ β”‚ └── Employee.java +β”‚ β”‚ β”œβ”€β”€ repository/ +β”‚ β”‚ β”‚ └── EmployeeRepository.java +β”‚ β”‚ β”œβ”€β”€ service/ +β”‚ β”‚ β”‚ └── EmployeeService.java +β”‚ β”‚ └── Lecture82Application.java +β”‚ └── resources/ +β”‚ β”œβ”€β”€ schema.sql +β”‚ └── application.properties +β”œβ”€β”€ .gitignore +β”œβ”€β”€ mvnw +β”œβ”€β”€ mvnw.cmd +└── pom.xml +``` + +##### βš™οΈ **Run the Spring Boot Application Locally** + +In this case i'm using maven to run my project. Here is how i do that. + +1. **Open Terminal:** + + Navigate to the root directory of the project where the `pom.xml` file is located. + +2. **Run the Application:** + + Execute the following command: + ```bash + $ ./mvnw spring-boot:run + ``` + +3. **Access the Application:** + + Once the application starts, we can access it typically at [http://localhost:8080](http://localhost:8080). + +##### πŸš€ **Verify the Application** +Here is some result of the APIs created. +1. **Get All Employees from DataSource 1** + `(GET /api/v1/employee/ds1)` + + ![Screenshot](img/api1.png) +2. **Get All Employees from DataSource 2** + `(GET /api/v1/employee/ds2)` + + ![Screenshot](img/api2.png) +3. **Get Employee By ID from DataSource 1** + `(GET /api/v1/employee/ds1/3ccd3c90-890e-41c4-9fa3-456f3d97f999)` + + ![Screenshot](img/api3.png) +4. **Get Employee By ID from DataSource 2** + `(GET /api/v1/employee/ds2/3ccd3c90-890e-41c4-9fa3-456f3d97f999)` + + ![Screenshot](img/api4.png) +5. **Add New Employee** + `(POST /api/v1/employee)` + + Body (Raw): + ```json + { + "id": "b002747b-4cc3-4a81-bd7e-e3184da0410a", + "name": "Michael Leon", + "dob": "2003-12-18", + "address": "Anytime anywhere", + "department": "MOBILE" + } + ``` + + ![Screenshot](img/api5.png) + + Result on DataSource 1 + + ![Screenshot](img/api52.png) + + Result on DataSource 2 + + ![Screenshot](img/api53.png) +6. **Edit Employee** + `(PUT /api/v1/employee/b002747b-4cc3-4a81-bd7e-e3184da0410a)` + + Body (Raw): + ```json + { + "name": "Leon Michael", + "dob": "2003-12-18", + "address": "Anytime anywhere anyplace", + "department": "MOBILE" + } + ``` + + ![Screenshot](img/api6.png) + + Result on DataSource 1 + + ![Screenshot](img/api62.png) + + Result on DataSource 2 + + ![Screenshot](img/api63.png) +7. **Delete Employee** + `(DELETE /api/v1/employee/b002747b-4cc3-4a81-bd7e-e3184da0410a)` + + ![Screenshot](img/api7.png) + + Result on DataSource 1 + + ![Screenshot](img/api72.png) + + Result on DataSource 2 + + ![Screenshot](img/api73.png) +8. **Add New Employee - Fail Transaction Simulation** + `(POST /api/v1/employee/fail)` + + This process is simulated by inserting to wrong field in the employee model on DataSource 2, which results failure and DataSource 1 needs to be rolled back. + + ![Screenshot](img/api8.png) + + Result on DataSource 1 + + ![Screenshot](img/api82.png) + + Result on DataSource 2 + + ![Screenshot](img/api83.png) + + Result on Console, inclusing 3 last transaction (insert, udpate, delete) + + ![Screenshot](img/api84.png) + +#### πŸ“¬ Postman Collection +Here is the [postman collection](/Week%2004/Lecture%2008/Assignment%2003/Lecture%2008%20-%20Assignment%2003.postman_collection.json) you can use to demo the API functionality. + +--- + +### πŸ’‘ 3. Research Lombok and Add to Project + +#### πŸ€” What is Lombok? +Lombok is a Java library that reduces boilerplate code in Java applications by automatically generating common methods such as getters, setters, equals, hashCode, toString, and constructors at compile time. This can significantly reduce the amount of code we need to write and maintain. + +#### πŸ“ Add Lombok Dependency + +Add Lombok to the `pom.xml`: + +```xml + + org.projectlombok + lombok + 1.18.28 + provided + +``` + +#### πŸ‘‰ Use Lombok Annotations + +Now we can simplify the `Employee` model by using Lombok annotations like `@Data`, `@NoArgsConstructor`, and `@AllArgsConstructor`. + +The implementation is written on [this file](/Week%2004/Lecture%2008/Assignment%2003/lecture_8_2/src/main/java/com/example/lecture_8_2/model/Employee.java). + +We can also use `@RequiredArgsConstructor` on `EmployeeController` class to simplify the constructor injection. + +The implementation is written on [this file](/Week%2004/Lecture%2008/Assignment%2003/lecture_8_2/src/main/java/com/example/lecture_8_2/controller/EmployeeController.java). + +#### πŸ”Ž Detail on Lombok Annotations +Here are some common Lombok annotations: +- `@Getter` and `@Setter`: Generate getters and setters for the fields. +- `@ToString`: Generates a toString method. +- `@EqualsAndHashCode`: Generates equals and hashCode methods. +- `@NoArgsConstructor`: Generates a no-argument constructor. +- `@AllArgsConstructor`: Generates a constructor with one parameter for each field. +- `@RequiredArgsConstructor`: Generates a constructor for final fields. +- `@Builder`: Provides a builder pattern implementation + +Lombok annotations can also be combined with validation annotations from other libraries. For instance, if we're using javax.validation or jakarta.validation for bean validation, we can add annotations like `@NotNull`, `@Size`, etc., directly to the fields of the Lombok-managed classes. \ No newline at end of file diff --git a/Week 04/Lecture 08/Assignment 03/img/api1.png b/Week 04/Lecture 08/Assignment 03/img/api1.png new file mode 100644 index 0000000..448f419 Binary files /dev/null and b/Week 04/Lecture 08/Assignment 03/img/api1.png differ diff --git a/Week 04/Lecture 08/Assignment 03/img/api2.png b/Week 04/Lecture 08/Assignment 03/img/api2.png new file mode 100644 index 0000000..8ac912b Binary files /dev/null and b/Week 04/Lecture 08/Assignment 03/img/api2.png differ diff --git a/Week 04/Lecture 08/Assignment 03/img/api3.png b/Week 04/Lecture 08/Assignment 03/img/api3.png new file mode 100644 index 0000000..986c418 Binary files /dev/null and b/Week 04/Lecture 08/Assignment 03/img/api3.png differ diff --git a/Week 04/Lecture 08/Assignment 03/img/api4.png b/Week 04/Lecture 08/Assignment 03/img/api4.png new file mode 100644 index 0000000..bc69e4a Binary files /dev/null and b/Week 04/Lecture 08/Assignment 03/img/api4.png differ diff --git a/Week 04/Lecture 08/Assignment 03/img/api5.png b/Week 04/Lecture 08/Assignment 03/img/api5.png new file mode 100644 index 0000000..7c22f41 Binary files /dev/null and b/Week 04/Lecture 08/Assignment 03/img/api5.png differ diff --git a/Week 04/Lecture 08/Assignment 03/img/api52.png b/Week 04/Lecture 08/Assignment 03/img/api52.png new file mode 100644 index 0000000..85f2e38 Binary files /dev/null and b/Week 04/Lecture 08/Assignment 03/img/api52.png differ diff --git a/Week 04/Lecture 08/Assignment 03/img/api53.png b/Week 04/Lecture 08/Assignment 03/img/api53.png new file mode 100644 index 0000000..a1aa497 Binary files /dev/null and b/Week 04/Lecture 08/Assignment 03/img/api53.png differ diff --git a/Week 04/Lecture 08/Assignment 03/img/api6.png b/Week 04/Lecture 08/Assignment 03/img/api6.png new file mode 100644 index 0000000..7c56c24 Binary files /dev/null and b/Week 04/Lecture 08/Assignment 03/img/api6.png differ diff --git a/Week 04/Lecture 08/Assignment 03/img/api62.png b/Week 04/Lecture 08/Assignment 03/img/api62.png new file mode 100644 index 0000000..953e113 Binary files /dev/null and b/Week 04/Lecture 08/Assignment 03/img/api62.png differ diff --git a/Week 04/Lecture 08/Assignment 03/img/api63.png b/Week 04/Lecture 08/Assignment 03/img/api63.png new file mode 100644 index 0000000..cea3e3d Binary files /dev/null and b/Week 04/Lecture 08/Assignment 03/img/api63.png differ diff --git a/Week 04/Lecture 08/Assignment 03/img/api7.png b/Week 04/Lecture 08/Assignment 03/img/api7.png new file mode 100644 index 0000000..92d2b3f Binary files /dev/null and b/Week 04/Lecture 08/Assignment 03/img/api7.png differ diff --git a/Week 04/Lecture 08/Assignment 03/img/api72.png b/Week 04/Lecture 08/Assignment 03/img/api72.png new file mode 100644 index 0000000..28a85f3 Binary files /dev/null and b/Week 04/Lecture 08/Assignment 03/img/api72.png differ diff --git a/Week 04/Lecture 08/Assignment 03/img/api73.png b/Week 04/Lecture 08/Assignment 03/img/api73.png new file mode 100644 index 0000000..ceea8c7 Binary files /dev/null and b/Week 04/Lecture 08/Assignment 03/img/api73.png differ diff --git a/Week 04/Lecture 08/Assignment 03/img/api8.png b/Week 04/Lecture 08/Assignment 03/img/api8.png new file mode 100644 index 0000000..37820e9 Binary files /dev/null and b/Week 04/Lecture 08/Assignment 03/img/api8.png differ diff --git a/Week 04/Lecture 08/Assignment 03/img/api82.png b/Week 04/Lecture 08/Assignment 03/img/api82.png new file mode 100644 index 0000000..ca70784 Binary files /dev/null and b/Week 04/Lecture 08/Assignment 03/img/api82.png differ diff --git a/Week 04/Lecture 08/Assignment 03/img/api83.png b/Week 04/Lecture 08/Assignment 03/img/api83.png new file mode 100644 index 0000000..eec17c7 Binary files /dev/null and b/Week 04/Lecture 08/Assignment 03/img/api83.png differ diff --git a/Week 04/Lecture 08/Assignment 03/img/api84.png b/Week 04/Lecture 08/Assignment 03/img/api84.png new file mode 100644 index 0000000..6ad49b4 Binary files /dev/null and b/Week 04/Lecture 08/Assignment 03/img/api84.png differ diff --git a/Week 04/Lecture 08/Assignment 03/lecture_8_2/.gitignore b/Week 04/Lecture 08/Assignment 03/lecture_8_2/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/Week 04/Lecture 08/Assignment 03/lecture_8_2/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/Week 04/Lecture 08/Assignment 03/lecture_8_2/.mvn/wrapper/maven-wrapper.properties b/Week 04/Lecture 08/Assignment 03/lecture_8_2/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..8f96f52 --- /dev/null +++ b/Week 04/Lecture 08/Assignment 03/lecture_8_2/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip diff --git a/Week 04/Lecture 08/Assignment 03/lecture_8_2/mvnw b/Week 04/Lecture 08/Assignment 03/lecture_8_2/mvnw new file mode 100644 index 0000000..d7c358e --- /dev/null +++ b/Week 04/Lecture 08/Assignment 03/lecture_8_2/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/Week 04/Lecture 08/Assignment 03/lecture_8_2/mvnw.cmd b/Week 04/Lecture 08/Assignment 03/lecture_8_2/mvnw.cmd new file mode 100644 index 0000000..6f779cf --- /dev/null +++ b/Week 04/Lecture 08/Assignment 03/lecture_8_2/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/Week 04/Lecture 08/Assignment 03/lecture_8_2/pom.xml b/Week 04/Lecture 08/Assignment 03/lecture_8_2/pom.xml new file mode 100644 index 0000000..c16e89e --- /dev/null +++ b/Week 04/Lecture 08/Assignment 03/lecture_8_2/pom.xml @@ -0,0 +1,96 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.1 + + + com.example + lecture_8_2 + 0.0.1-SNAPSHOT + lecture_8_2 + Demo project for Spring Boot + + + + + + + + + + + + + + + 21 + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-web-services + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + com.mysql + mysql-connector-j + runtime + + + mysql + mysql-connector-java + 8.0.33 + + + org.projectlombok + lombok + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-data-jdbc + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + org.springframework.data + spring-data-commons + 3.2.0 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/java/com/example/lecture_8_2/Lecture82Application.java b/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/java/com/example/lecture_8_2/Lecture82Application.java new file mode 100644 index 0000000..37f29ac --- /dev/null +++ b/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/java/com/example/lecture_8_2/Lecture82Application.java @@ -0,0 +1,11 @@ +package com.example.lecture_8_2; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Lecture82Application { + public static void main(String[] args) { + SpringApplication.run(Lecture82Application.class, args); + } +} diff --git a/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/java/com/example/lecture_8_2/config/DataSourceConfig.java b/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/java/com/example/lecture_8_2/config/DataSourceConfig.java new file mode 100644 index 0000000..1b62ac1 --- /dev/null +++ b/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/java/com/example/lecture_8_2/config/DataSourceConfig.java @@ -0,0 +1,62 @@ +package com.example.lecture_8_2.config; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; + +import javax.sql.DataSource; + +@Configuration +public class DataSourceConfig { + + @Bean + @ConfigurationProperties("spring.datasource1") + public HikariConfig hikariConfig1() { + return new HikariConfig(); + } + + @Bean + public DataSource dataSource1() { + return new HikariDataSource(hikariConfig1()); + } + + @Bean + @Primary + public DataSourceTransactionManager transactionManager1(@Qualifier("dataSource1") DataSource dataSource1) { + return new DataSourceTransactionManager(dataSource1); + } + + @Bean + @ConfigurationProperties("spring.datasource2") + public HikariConfig hikariConfig2() { + return new HikariConfig(); + } + + @Bean + public DataSource dataSource2() { + return new HikariDataSource(hikariConfig2()); + } + + @Bean + public DataSourceTransactionManager transactionManager2(@Qualifier("dataSource2") DataSource dataSource2) { + return new DataSourceTransactionManager(dataSource2); + } + + @Bean + @Qualifier("jdbcTemplate1") + public JdbcTemplate jdbcTemplate1(@Qualifier("dataSource1") DataSource dataSource1) { + return new JdbcTemplate(dataSource1); + } + + @Bean + @Qualifier("jdbcTemplate2") + public JdbcTemplate jdbcTemplate2(@Qualifier("dataSource2") DataSource dataSource2) { + return new JdbcTemplate(dataSource2); + } +} \ No newline at end of file diff --git a/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/java/com/example/lecture_8_2/controller/EmployeeController.java b/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/java/com/example/lecture_8_2/controller/EmployeeController.java new file mode 100644 index 0000000..95353ab --- /dev/null +++ b/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/java/com/example/lecture_8_2/controller/EmployeeController.java @@ -0,0 +1,110 @@ +package com.example.lecture_8_2.controller; + +import java.util.List; +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.example.lecture_8_2.model.Employee; +import com.example.lecture_8_2.service.EmployeeService; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/api/v1/employee") +@RequiredArgsConstructor +public class EmployeeController { + + @Autowired + private EmployeeService employeeService; + + @GetMapping("/ds1") + public ResponseEntity> listAllEmployeeFromDataSource1() { + List employees = employeeService.findAllFromDS1(); + if (employees.isEmpty()) { + return ResponseEntity.noContent().build(); + } + return ResponseEntity.ok(employees); + } + + @GetMapping("/ds2") + public ResponseEntity> listAllEmployeeFromDataSource2() { + List employees = employeeService.findAllFromDS2(); + if (employees.isEmpty()) { + return ResponseEntity.noContent().build(); + } + return ResponseEntity.ok(employees); + } + + @GetMapping("/ds1/{id}") + public ResponseEntity findEmployeeByIdFromDataSource1(@PathVariable("id") String id) { + Optional employeeOpt = employeeService.findByIdFromDS1(id); + return employeeOpt.map(ResponseEntity::ok) + .orElseGet(() -> ResponseEntity.notFound().build()); + } + + @GetMapping("/ds2/{id}") + public ResponseEntity findEmployeeByIdFromDataSource2(@PathVariable("id") String id) { + Optional employeeOpt = employeeService.findByIdFromDS2(id); + return employeeOpt.map(ResponseEntity::ok) + .orElseGet(() -> ResponseEntity.notFound().build()); + } + + @PostMapping + public ResponseEntity insertEmployee(@RequestBody Employee employee) { + try { + Employee savedEmployee = employeeService.save(employee); + return ResponseEntity.ok(savedEmployee); + } catch (Exception e) { + System.out.println("Transaction failed. All succesful operations will be rolled back."); + System.out.println(e.getMessage()); + return ResponseEntity.badRequest().build(); + } + } + + @PostMapping("/fail") + public ResponseEntity insertEmployeeFail(@RequestBody Employee employee) { + try { + Employee savedEmployee = employeeService.saveFail(employee); + return ResponseEntity.ok(savedEmployee); + } catch (Exception e) { + System.out.println("Transaction failed. All succesful operations will be rolled back."); + System.out.println(e.getMessage()); + return ResponseEntity.badRequest().build(); + } + } + + @PutMapping("/{id}") + public ResponseEntity updateEmployee(@PathVariable("id") String id, @RequestBody Employee employeeForm) { + try { + employeeForm.setId(id); + Employee updatedEmployee = employeeService.update(employeeForm); + return ResponseEntity.ok(updatedEmployee); + } catch (Exception e) { + System.out.println("Transaction failed. All succesful operations will be rolled back."); + System.out.println(e.getMessage()); + return ResponseEntity.badRequest().build(); + } + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteEmployee(@PathVariable("id") String id) { + try { + employeeService.deleteById(id); + return ResponseEntity.ok().build(); + } catch (Exception e) { + System.out.println("Transaction failed. All succesful operations will be rolled back."); + System.out.println(e.getMessage()); + return ResponseEntity.badRequest().build(); + } + } +} \ No newline at end of file diff --git a/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/java/com/example/lecture_8_2/model/Employee.java b/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/java/com/example/lecture_8_2/model/Employee.java new file mode 100644 index 0000000..afee470 --- /dev/null +++ b/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/java/com/example/lecture_8_2/model/Employee.java @@ -0,0 +1,30 @@ +package com.example.lecture_8_2.model; + +import java.io.Serializable; +import java.time.LocalDate; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class Employee implements Serializable { + + private static final long serialVersionUID = 1L; + + private String id; + private String name; + private LocalDate dob; + private String address; + private String department; + + public Employee(String id, String name, LocalDate dob, String address, String department) { + this.id = id; + this.name = name; + this.dob = dob; + this.address = address; + this.department = department; + } +} \ No newline at end of file diff --git a/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/java/com/example/lecture_8_2/repository/EmployeeRepository.java b/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/java/com/example/lecture_8_2/repository/EmployeeRepository.java new file mode 100644 index 0000000..083aafb --- /dev/null +++ b/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/java/com/example/lecture_8_2/repository/EmployeeRepository.java @@ -0,0 +1,101 @@ +package com.example.lecture_8_2.repository; + +import java.sql.ResultSet; +import java.util.List; +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +import com.example.lecture_8_2.model.Employee; + +@Repository +public class EmployeeRepository { + + @Autowired + @Qualifier("jdbcTemplate1") + private JdbcTemplate jdbcTemplate1; + + @Autowired + @Qualifier("jdbcTemplate2") + private JdbcTemplate jdbcTemplate2; + + private static final RowMapper EMPLOYEE_ROW_MAPPER = (ResultSet rs, int rowNum) -> { + Employee employee = new Employee(); + employee.setId(rs.getString("id")); + employee.setName(rs.getString("name")); + employee.setDob(rs.getDate("dob").toLocalDate()); + employee.setAddress(rs.getString("address")); + employee.setDepartment(rs.getString("department")); + return employee; + }; + + public List findAllFromDS1() { + String sql = "SELECT * FROM employee"; + return jdbcTemplate1.query(sql, EMPLOYEE_ROW_MAPPER); + } + + public List findAllFromDS2() { + String sql = "SELECT * FROM employee"; + return jdbcTemplate2.query(sql, EMPLOYEE_ROW_MAPPER); + } + + public Optional findByIdFromDS1(String id) { + String sql = "SELECT * FROM employee WHERE id = ?"; + try { + Employee employee = jdbcTemplate1.queryForObject(sql, EMPLOYEE_ROW_MAPPER, id); + return Optional.ofNullable(employee); + } catch (DataAccessException e) { + return Optional.empty(); + } + } + + public Optional findByIdFromDS2(String id) { + String sql = "SELECT * FROM employee WHERE id = ?"; + try { + Employee employee = jdbcTemplate2.queryForObject(sql, EMPLOYEE_ROW_MAPPER, id); + return Optional.ofNullable(employee); + } catch (DataAccessException e) { + return Optional.empty(); + } + } + + public void saveToDS1(Employee employee) { + String sql = "INSERT INTO employee (id, name, dob, address, department) VALUES (?, ?, ?, ?, ?)"; + jdbcTemplate1.update(sql, employee.getId(), employee.getName(), employee.getDob(), employee.getAddress(), employee.getDepartment()); + } + + public void saveToDS2(Employee employee) { + String sql = "INSERT INTO employee (id, name, dob, address, department) VALUES (?, ?, ?, ?, ?)"; + jdbcTemplate2.update(sql, employee.getId(), employee.getName(), employee.getDob(), employee.getAddress(), employee.getDepartment()); + } + + public void saveToDS2Fail(Employee employee) { + String sql = "INSERT INTO employee (id, name, dateOfBirth, address, department) VALUES (?, ?, ?, ?, ?)"; + jdbcTemplate2.update(sql, employee.getId(), employee.getName(), employee.getDob(), employee.getAddress(), employee.getDepartment()); + } + + public void updateInDS1(Employee employee) { + String sql = "UPDATE employee SET name = ?, dob = ?, address = ?, department = ? WHERE id = ?"; + jdbcTemplate1.update(sql, employee.getName(), employee.getDob(), employee.getAddress(), employee.getDepartment(), employee.getId()); + } + + public void updateInDS2(Employee employee) { + String sql = "UPDATE employee SET name = ?, dob = ?, address = ?, department = ? WHERE id = ?"; + jdbcTemplate2.update(sql, employee.getName(), employee.getDob(), employee.getAddress(), employee.getDepartment(), employee.getId()); + } + + public void deleteFromDS1ById(String id) { + String sql = "DELETE FROM employee WHERE id = ?"; + jdbcTemplate1.update(sql, id); + } + + public void deleteFromDS2ById(String id) { + String sql = "DELETE FROM employee WHERE id = ?"; + jdbcTemplate2.update(sql, id); + } +} \ No newline at end of file diff --git a/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/java/com/example/lecture_8_2/service/EmployeeService.java b/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/java/com/example/lecture_8_2/service/EmployeeService.java new file mode 100644 index 0000000..2b8522a --- /dev/null +++ b/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/java/com/example/lecture_8_2/service/EmployeeService.java @@ -0,0 +1,173 @@ +package com.example.lecture_8_2.service; + +import com.example.lecture_8_2.model.Employee; +import com.example.lecture_8_2.repository.EmployeeRepository; + +import java.util.List; +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.stereotype.Service; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.support.DefaultTransactionDefinition; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.transaction.TransactionStatus; + +@Service +public class EmployeeService { + + @Autowired + private EmployeeRepository employeeRepository; + + @Autowired + private DataSourceTransactionManager transactionManager1; + + @Autowired + private DataSourceTransactionManager transactionManager2; + + public List findAllFromDS1() { + return employeeRepository.findAllFromDS1(); + } + + public List findAllFromDS2() { + return employeeRepository.findAllFromDS2(); + } + + public Optional findByIdFromDS1(String id) { + return employeeRepository.findByIdFromDS1(id); + } + + public Optional findByIdFromDS2(String id) { + return employeeRepository.findByIdFromDS2(id); + } + + public Employee save(Employee employee) { + TransactionStatus status1 = transactionManager1.getTransaction(new DefaultTransactionDefinition()); + TransactionStatus status2 = transactionManager2.getTransaction(new DefaultTransactionDefinition()); + + try { + // Save to both databases + employeeRepository.saveToDS1(employee); + employeeRepository.saveToDS2(employee); + + // Register post-commit callback + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + // Post-commit actions, e.g., logging or cache update + System.out.println("Transaction committed successfully."); + } + }); + + // Commit transactions + transactionManager1.commit(status1); + transactionManager2.commit(status2); + + return employee; + } catch (IllegalStateException | TransactionException e) { + // Rollback transactions in case of error + transactionManager1.rollback(status1); + transactionManager2.rollback(status2); + + throw e; + } + } + + public Employee saveFail(Employee employee) { + TransactionStatus status1 = transactionManager1.getTransaction(new DefaultTransactionDefinition()); + TransactionStatus status2 = transactionManager2.getTransaction(new DefaultTransactionDefinition()); + + try { + // Save to both databases + employeeRepository.saveToDS1(employee); + employeeRepository.saveToDS2Fail(employee); + + // Register post-commit callback + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + // Post-commit actions, e.g., logging or cache update + System.out.println("Transaction committed successfully."); + } + }); + + // Commit transactions + transactionManager1.commit(status1); + transactionManager2.commit(status2); + + return employee; + } catch (IllegalStateException | TransactionException e) { + // Rollback transactions in case of error + transactionManager1.rollback(status1); + transactionManager2.rollback(status2); + + throw e; + } + } + + public Employee update(Employee employee) { + TransactionStatus status1 = transactionManager1.getTransaction(new DefaultTransactionDefinition()); + TransactionStatus status2 = transactionManager2.getTransaction(new DefaultTransactionDefinition()); + + try { + // Update in both databases + employeeRepository.updateInDS1(employee); + employeeRepository.updateInDS2(employee); + + // Register post-commit callback + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + // Post-commit actions, e.g., logging or cache update + System.out.println("Transaction committed successfully."); + } + }); + + // Commit transactions + transactionManager1.commit(status1); + transactionManager2.commit(status2); + + return employee; + } catch (IllegalStateException | TransactionException e) { + // Rollback transactions in case of error + transactionManager1.rollback(status1); + transactionManager2.rollback(status2); + + throw e; + } + } + + public void deleteById(String id) { + TransactionStatus status1 = transactionManager1.getTransaction(new DefaultTransactionDefinition()); + TransactionStatus status2 = transactionManager2.getTransaction(new DefaultTransactionDefinition()); + + try { + // Delete from both databases + employeeRepository.deleteFromDS1ById(id); + employeeRepository.deleteFromDS2ById(id); + + // Register post-commit callback + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + // Post-commit actions, e.g., logging or cache update + System.out.println("Transaction committed successfully."); + } + }); + + // Commit transactions + transactionManager1.commit(status1); + transactionManager2.commit(status2); + + } catch (IllegalStateException | TransactionException e) { + // Rollback transactions in case of error + transactionManager1.rollback(status1); + transactionManager2.rollback(status2); + + throw e; + } + } +} + diff --git a/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/resources/application.properties b/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/resources/application.properties new file mode 100644 index 0000000..0994642 --- /dev/null +++ b/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/resources/application.properties @@ -0,0 +1,16 @@ +spring.application.name=lecture_8_2 + +# DataSource 1 +spring.datasource1.jdbc-url=jdbc:mysql://localhost:3308/week4_lecture8?allowPublicKeyRetrieval=true&useSSL=false +spring.datasource1.username=root +spring.datasource1.password=Michaeleon16606_ +spring.datasource1.driver-class-name=com.mysql.jdbc.Driver + +# DataSource 2 +spring.datasource2.jdbc-url=jdbc:mysql://localhost:3308/week4_lecture8_clone?allowPublicKeyRetrieval=true&useSSL=false +spring.datasource2.username=root +spring.datasource2.password=Michaeleon16606_ +spring.datasource2.driver-class-name=com.mysql.cj.jdbc.Driver + +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true \ No newline at end of file diff --git a/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/resources/schema.sql b/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/resources/schema.sql new file mode 100644 index 0000000..e7d6cfc --- /dev/null +++ b/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/main/resources/schema.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS employee ( + id VARCHAR(50) NOT NULL, + name VARCHAR(100) COLLATE utf8mb4_unicode_ci NOT NULL, + dob DATE NOT NULL, + address VARCHAR(255) NOT NULL, + department VARCHAR(100) NOT NULL, + PRIMARY KEY (id) +); \ No newline at end of file diff --git a/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/test/java/com/example/lecture_8_2/Lecture82ApplicationTests.java b/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/test/java/com/example/lecture_8_2/Lecture82ApplicationTests.java new file mode 100644 index 0000000..615c272 --- /dev/null +++ b/Week 04/Lecture 08/Assignment 03/lecture_8_2/src/test/java/com/example/lecture_8_2/Lecture82ApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.lecture_8_2; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class Lecture82ApplicationTests { + + @Test + void contextLoads() { + } + +}