diff --git a/01.Spring-Boot-Introduction-In-10-Steps/.gitignore b/01.Spring-Boot-Introduction-In-10-Steps/.gitignore new file mode 100644 index 0000000..2af7cef --- /dev/null +++ b/01.Spring-Boot-Introduction-In-10-Steps/.gitignore @@ -0,0 +1,24 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ \ No newline at end of file diff --git a/01.Spring-Boot-Introduction-In-10-Steps/.mvn/wrapper/maven-wrapper.jar b/01.Spring-Boot-Introduction-In-10-Steps/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..9cc84ea Binary files /dev/null and b/01.Spring-Boot-Introduction-In-10-Steps/.mvn/wrapper/maven-wrapper.jar differ diff --git a/01.Spring-Boot-Introduction-In-10-Steps/.mvn/wrapper/maven-wrapper.properties b/01.Spring-Boot-Introduction-In-10-Steps/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..c315043 --- /dev/null +++ b/01.Spring-Boot-Introduction-In-10-Steps/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip diff --git a/01.Spring-Boot-Introduction-In-10-Steps/code-21July2017.zip b/01.Spring-Boot-Introduction-In-10-Steps/code-21July2017.zip new file mode 100644 index 0000000..91201bc Binary files /dev/null and b/01.Spring-Boot-Introduction-In-10-Steps/code-21July2017.zip differ diff --git a/01.Spring-Boot-Introduction-In-10-Steps/mvnw b/01.Spring-Boot-Introduction-In-10-Steps/mvnw new file mode 100755 index 0000000..5bf251c --- /dev/null +++ b/01.Spring-Boot-Introduction-In-10-Steps/mvnw @@ -0,0 +1,225 @@ +#!/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 +# +# http://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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + 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" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +echo $MAVEN_PROJECTBASEDIR +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/01.Spring-Boot-Introduction-In-10-Steps/mvnw.cmd b/01.Spring-Boot-Introduction-In-10-Steps/mvnw.cmd new file mode 100644 index 0000000..019bd74 --- /dev/null +++ b/01.Spring-Boot-Introduction-In-10-Steps/mvnw.cmd @@ -0,0 +1,143 @@ +@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 http://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 Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/01.Spring-Boot-Introduction-In-10-Steps/notes.txt b/01.Spring-Boot-Introduction-In-10-Steps/notes.txt new file mode 100644 index 0000000..f7a73d3 --- /dev/null +++ b/01.Spring-Boot-Introduction-In-10-Steps/notes.txt @@ -0,0 +1,26 @@ +## Spring Boot + +### Goals +- Enable building production ready applications quickly +- Provide common non-functional features + - embedded servers + - metrics + - health checks + - externalized configuration + +### What Spring Boot is NOT! +- ZERO code generation +- Neither an application server nor a web server + +### Features +- Quick Starter Projects with Auto Configuration + - Web + - JPA +- Embedded Servers - Tomcat, Jetty or Undertow +- Production-ready features + - metrics and health checks + - externalized configuration + + +http://localhost:8080/books => Few hardcoded books + \ No newline at end of file diff --git a/01.Spring-Boot-Introduction-In-10-Steps/pom.xml b/01.Spring-Boot-Introduction-In-10-Steps/pom.xml new file mode 100644 index 0000000..ec0eb6d --- /dev/null +++ b/01.Spring-Boot-Introduction-In-10-Steps/pom.xml @@ -0,0 +1,111 @@ + + + 4.0.0 + + com.in28minutes.springboot.basics + springboot-in-10-steps + 0.0.1-SNAPSHOT + jar + + springboot-in-10-steps + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.BUILD-SNAPSHOT + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-devtools + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + diff --git a/01.Spring-Boot-Introduction-In-10-Steps/readme.md b/01.Spring-Boot-Introduction-In-10-Steps/readme.md new file mode 100644 index 0000000..95a38ae --- /dev/null +++ b/01.Spring-Boot-Introduction-In-10-Steps/readme.md @@ -0,0 +1,277 @@ +## First 10 Steps in Spring Boot + +- Step 1 : Introduction to Spring Boot - Goals and Important Features +- Step 2 : Developing Spring Applications before Spring Boot +- Step 3 : Using Spring Initializr to create a Spring Boot Application +- Step 4 : Creating a Simple REST Controller +- Step 5 : What is Spring Boot Auto Configuration? +- Step 6 : Spring Boot vs Spring vs Spring MVC +- Step 7 : Spring Boot Starter Projects - Starter Web and Starter JPA +- Step 8 : Overview of different Spring Boot Starter Projects +- Step 9 : Spring Boot Actuator +- Step 10 : Spring Boot Developer Tools +- Spring Boot - Conclusion + +## Complete Code Example + +### /notes.txt + +``` +Goals +Enable building production ready applications quickly +Provide common non-functional features +- embedded servers +- metrics +- health checks +- externalized configuration + +What Spring Boot is NOT! +ZERO code generation +Neither an application server nor a web server + +Features +Quick Starter Projects with Auto Configuration + - Web + - JPA +Embedded Servers - Tomcat, Jetty or Undertow +Production-ready features + - metrics and health checks + - externalized configuration + + +http://localhost:8080/books => Few hardcoded books + +``` +--- + +### /pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.springboot.basics + springboot-in-10-steps + 0.0.1-SNAPSHOT + jar + + springboot-in-10-steps + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.BUILD-SNAPSHOT + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-devtools + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + +``` +--- + +### /src/main/java/com/in28minutes/springboot/basics/springbootin10steps/Book.java + +```java +package com.in28minutes.springboot.basics.springbootin10steps; + +public class Book { + long id; + String name; + String author; + + public Book(long id, String name, String author) { + super(); + this.id = id; + this.name = name; + this.author = author; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getAuthor() { + return author; + } + + @Override + public String toString() { + return String.format("Book [id=%s, name=%s, author=%s]", id, name, author); + } + +} +``` +--- + +### /src/main/java/com/in28minutes/springboot/basics/springbootin10steps/BooksController.java + +```java +package com.in28minutes.springboot.basics.springbootin10steps; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class BooksController { + @GetMapping("/books") + public List getAllBooks() { + return Arrays.asList( + new Book(1l, "Mastering Spring 5.2", "Ranga Karanam")); + } +} +``` +--- + +### /src/main/java/com/in28minutes/springboot/basics/springbootin10steps/SpringbootIn10StepsApplication.java + +```java +package com.in28minutes.springboot.basics.springbootin10steps; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; + +@SpringBootApplication +public class SpringbootIn10StepsApplication { + + public static void main(String[] args) { + ApplicationContext applicationContext = + SpringApplication.run(SpringbootIn10StepsApplication.class, args); + + for (String name : applicationContext.getBeanDefinitionNames()) { + System.out.println(name); + } + } +} +``` +--- + +### /src/main/resources/application.properties + +```properties +#logging.level.org.springframework = DEBUG +management.security.enabled=false +``` +--- + +### /src/test/java/com/in28minutes/springboot/basics/springbootin10steps/SpringbootIn10StepsApplicationTests.java + +```java +package com.in28minutes.springboot.basics.springbootin10steps; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringbootIn10StepsApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- diff --git a/01.Spring-Boot-Introduction-In-10-Steps/src/main/java/com/in28minutes/springboot/basics/springbootin10steps/Book.java b/01.Spring-Boot-Introduction-In-10-Steps/src/main/java/com/in28minutes/springboot/basics/springbootin10steps/Book.java new file mode 100644 index 0000000..9bccf3b --- /dev/null +++ b/01.Spring-Boot-Introduction-In-10-Steps/src/main/java/com/in28minutes/springboot/basics/springbootin10steps/Book.java @@ -0,0 +1,32 @@ +package com.in28minutes.springboot.basics.springbootin10steps; + +public class Book { + long id; + String name; + String author; + + public Book(long id, String name, String author) { + super(); + this.id = id; + this.name = name; + this.author = author; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getAuthor() { + return author; + } + + @Override + public String toString() { + return String.format("Book [id=%s, name=%s, author=%s]", id, name, author); + } + +} diff --git a/01.Spring-Boot-Introduction-In-10-Steps/src/main/java/com/in28minutes/springboot/basics/springbootin10steps/BooksController.java b/01.Spring-Boot-Introduction-In-10-Steps/src/main/java/com/in28minutes/springboot/basics/springbootin10steps/BooksController.java new file mode 100644 index 0000000..bfdd7c8 --- /dev/null +++ b/01.Spring-Boot-Introduction-In-10-Steps/src/main/java/com/in28minutes/springboot/basics/springbootin10steps/BooksController.java @@ -0,0 +1,16 @@ +package com.in28minutes.springboot.basics.springbootin10steps; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class BooksController { + @GetMapping("/books") + public List getAllBooks() { + return Arrays.asList( + new Book(1l, "Mastering Spring 5.2", "Ranga Karanam")); + } +} \ No newline at end of file diff --git a/01.Spring-Boot-Introduction-In-10-Steps/src/main/java/com/in28minutes/springboot/basics/springbootin10steps/SpringbootIn10StepsApplication.java b/01.Spring-Boot-Introduction-In-10-Steps/src/main/java/com/in28minutes/springboot/basics/springbootin10steps/SpringbootIn10StepsApplication.java new file mode 100644 index 0000000..9ca4508 --- /dev/null +++ b/01.Spring-Boot-Introduction-In-10-Steps/src/main/java/com/in28minutes/springboot/basics/springbootin10steps/SpringbootIn10StepsApplication.java @@ -0,0 +1,19 @@ +package com.in28minutes.springboot.basics.springbootin10steps; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; + +@SpringBootApplication +public class SpringbootIn10StepsApplication { + + public static void main(String[] args) { + ApplicationContext applicationContext = + SpringApplication.run(SpringbootIn10StepsApplication.class, args); + + for (String name : applicationContext.getBeanDefinitionNames()) { + System.out.println(name); + } + } +} diff --git a/01.Spring-Boot-Introduction-In-10-Steps/src/main/resources/application.properties b/01.Spring-Boot-Introduction-In-10-Steps/src/main/resources/application.properties new file mode 100644 index 0000000..4b07b72 --- /dev/null +++ b/01.Spring-Boot-Introduction-In-10-Steps/src/main/resources/application.properties @@ -0,0 +1,2 @@ +#logging.level.org.springframework = DEBUG +management.security.enabled=false \ No newline at end of file diff --git a/01.Spring-Boot-Introduction-In-10-Steps/src/test/java/com/in28minutes/springboot/basics/springbootin10steps/SpringbootIn10StepsApplicationTests.java b/01.Spring-Boot-Introduction-In-10-Steps/src/test/java/com/in28minutes/springboot/basics/springbootin10steps/SpringbootIn10StepsApplicationTests.java new file mode 100644 index 0000000..6cd60dd --- /dev/null +++ b/01.Spring-Boot-Introduction-In-10-Steps/src/test/java/com/in28minutes/springboot/basics/springbootin10steps/SpringbootIn10StepsApplicationTests.java @@ -0,0 +1,16 @@ +package com.in28minutes.springboot.basics.springbootin10steps; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringbootIn10StepsApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/02.Spring-Boot-Web-Application/.DS_Store b/02.Spring-Boot-Web-Application/.DS_Store new file mode 100644 index 0000000..5172429 Binary files /dev/null and b/02.Spring-Boot-Web-Application/.DS_Store differ diff --git a/02.Spring-Boot-Web-Application/README.md b/02.Spring-Boot-Web-Application/README.md new file mode 100644 index 0000000..ead3000 --- /dev/null +++ b/02.Spring-Boot-Web-Application/README.md @@ -0,0 +1,133 @@ +# Your First Web Application with Spring Boot +Develop your first web application with Spring Boot in more than 25 steps +* [Installing Eclipse, Maven and Java](#installing-tools) +* [Course Overview](#course-overview) +* [About in28Minutes](#about-in28minutes) + - [Our Beliefs](#our-beliefs) + - [Our Approach](#our-approach) + - [Find Us](#useful-links) + - [Other Courses](#other-courses) + +## Installing Tools +- PDF : https://github.com/in28minutes/SpringIn28Minutes/blob/master/InstallationGuide-JavaEclipseAndMaven_v2.pdf +- Video : https://www.youtube.com/playlist?list=PLBBog2r6uMCSmMVTW_QmDLyASBvovyAO3 +- GIT Repository : https://github.com/in28minutes/getting-started-in-5-steps + +## Course Overview + +### Introduction +Developing your first Spring Boot Web Application is fun. + +Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”. We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need very little Spring configuration. + +In this course, you will learn the basics developing a Basic Todo Management Application using Spring Boot with Login and Logout functionalities. + +You will build the website step by step - in more than 25 steps. This course would be a perfect first step as an introduction to Java Web Application Development. + +You will be using Spring (Dependency Management), Spring MVC, Spring Boot, Spring Security (Authentication and Authorization), BootStrap (Styling Pages), Maven (dependencies management), Eclipse (IDE) and Tomcat Embedded Web Server. We will help you set up each one of these. + +You will learn about +- Basics of Spring Boot +- Basics of Autoconfiguration and Spring Boot Magic +- DispatcherServlet +- Basic Todo Management Application with Login/Logout +- Model, Controllers, ViewResolver and Filters +- Forms - DataBinding, Validation +- Annotation based approach - @RequestParam, @ModelAttribute, @SessionAttributes etc +- Bootstrap to style the page +- Spring Security +- Exception Handling + +### Step Wise Details +- Step 01: Basic Spring Boot Web Application Setup +- Step 02: First Spring MVC Controller, @ResponseBody, @Controller +- Step 03: Demystifying some of the Spring Boot magic +- Step 04: Redirect to Login JSP - LoginController, @ResponseBody and View Resolver +- Step 05: Show userid and password on the welcome page - ModelMap and @RequestParam +- Step 06: DispatcherServlet and Spring MVC Flow +- Step 07: Your First HTML form +- Step 08: Add hard-coded validation of userid and password +- Step 09: Magic of Spring +- Step 10: Create TodoController and list-todos view. Make TodoService a @Service and inject it. +- Step 11: Architecture of Web Applications +- Step 12: Session vs Model vs Request - @SessionAttributes +- Step 13: Add new todo +- Step 14: Display Todos in a table using JSTL Tags +- Step 15: Bootstrap for Page Formatting using webjars +- Step 16: Let's delete a Todo +- Step 17: Format Add Todo Page and Adding Basic HTML5 form validation +- Step 18: Introduce JSR 349 Validations using Hibernate Validator - First Command Bean. +- Step 19: Updating a todo +- Step 20: Let's add a Target Date for Todo - Use initBinder to Handle Date Fields +- Step 21: JSP Fragments and Navigation Bar +- Step 22: Preparing for Spring Security +- Step 23: Initial Spring Security Setup +- Step 24: Refactor and add Logout Functionality using Spring Security +- Step 25: Exception Handling + +--- + +- We do NOT interact with a Database in this Beginner’s Course. +- We will be building a traditional JSP based web application in this course. + +--- + +### Expectations +- You should know Java. You should understand usage of Annotations. +- You should understand the basics of Spring framework. +- You are NOT expected to have any experience with Eclipse or Maven. +- We will help you install Eclipse and get up and running with Maven. + +## Let's have some fun +- What are we waiting for? +- Let's have some fun building a web application Spring Boot in 25 Steps. +- I had fun creating this course and hope you would too. +- Thanks for your interest in Our Course + - I hope you’re as excited as I am! + - If you’re ready to learn more and sign up for the course, + - go ahead and hit that Enroll button, + - or take a test drive by using the Free Preview feature. +- See you in the course! + +## Getting Started +- Eclipse - https://courses.in28minutes.com/p/eclipse-tutorial-for-beginners +- Maven - https://courses.in28minutes.com/p/maven-tutorial-for-beginners-in-5-steps +- JUnit - https://courses.in28minutes.com/p/junit-tutorial-for-beginners +- Mockito - https://courses.in28minutes.com/p/mockito-for-beginner-in-5-steps + +## About in28Minutes +- At in28Minutes, we ask ourselves one question everyday. How do we help you learn effectively - that is more quickly and retain more of what you have learnt? +- We use Problem-Solution based Step-By-Step Hands-on Approach With Practical, Real World Application Examples. +- Our success on Udemy and Youtube (2 Million Views & 12K Subscribers) speaks volumes about the success of our approach. +- While our primary expertise is on Development, Design & Architecture Java & Related Frameworks (Spring, Struts, Hibernate) we are expanding into the front-end world (Bootstrap, JQuery, Angular JS). + +### Our Beliefs +- Best Courses are interactive and fun. +- Foundations for building high quality applications are best laid down while learning. + +### Our Approach +- Problem Solution based Step by Step Hands-on Learning +- Practical, Real World Application Examples. +- We use 80-20 Rule. We discuss 20% things used 80% of time in depth. We touch upon other things briefly equipping you with enough knowledge to find out more on your own. +- We will be developing a demo application in the course, which could be reused in your projects, saving hours of your effort. +- We love open source and therefore, All our code is open source too and available on Github. + +### Other Courses + +- [Check out all our courses with 100,000 Students](https://courses.in28minutes.com/courses) +- [25 Videos and Articles for Beginners on Spring Boot](http://www.springboottutorial.com/spring-boot-tutorials-for-beginners) +- Our Best Courses with 66,000 Students and 4,000 5-Star Ratings + * [Java Interview Guide : 200+ Interview Questions and Answers](https://www.udemy.com/java-interview-questions-and-answers/?couponCode=JAVA_INTER_GIT) + * [Mockito Tutorial : Learn mocking with 25 Junit Examples](https://www.udemy.com/mockito-tutorial-with-junit-examples/?couponCode=MOCKITO_GIT) + * [Java EE Made Easy - Patterns, Architecture and Frameworks](https://www.udemy.com/java-ee-design-patterns-architecture-and-frameworks/?couponCode=EEPATTERNS-GIT) + * [Spring MVC For Beginners : Build Java Web App in 25 Steps](https://www.udemy.com/spring-mvc-tutorial-for-beginners-step-by-step/?couponCode=SPRINGMVC-GIT) + * [JSP Servlets For Beginners : Build Java Web App in 25 Steps](https://www.udemy.com/learn-java-servlets-and-jsp-web-application-in-25-steps/?couponCode=JSPSRVLT-GIT) + * [Maven Tutorial - Manage Java Dependencies in 25 Steps](https://www.udemy.com/learn-maven-java-dependency-management-in-20-steps/?couponCode=MAVEN_GIT) + * [Java OOPS in 1 Hours](https://www.udemy.com/learn-object-oriented-programming-in-java/?couponCode=OOPS-GIT) + * [C Puzzle for Interview](https://www.udemy.com/c-puzzles-for-beginners/?couponCode=CPUZZLES-GIT) + +### Useful Links +- [Our Website](http://www.in28minutes.com) +- [Facebook](http://facebook.com/in28minutes) +- [Twitter](http://twitter.com/in28minutes) +- [Google Plus](https://plus.google.com/u/3/110861829188024231119) diff --git a/02.Spring-Boot-Web-Application/Step01.md b/02.Spring-Boot-Web-Application/Step01.md new file mode 100644 index 0000000..f877dad --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step01.md @@ -0,0 +1,117 @@ +## What You Will Learn during this Step: +- Lets create a simple web application using Spring Boot +- Lets run the Spring Boot Application +- There is a lot of magic happening in here! We will take a deep dive into the magic in Step 03. + +## Files List + +### pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-devtools + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +--- +### src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java + +```java +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringBootFirstWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootFirstWebApplication.class, args); + } +} +``` +--- +### src/main/resources/application.properties + +``` +``` +--- +### src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java + +```java +package com.in28minutes.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootFirstWebApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- +### todo.txt + +``` +Spring Boot Starter Parent +Spring Boot Starter Web +@SpringBootApplication +Auto Configuration +``` +--- diff --git a/02.Spring-Boot-Web-Application/Step02.md b/02.Spring-Boot-Web-Application/Step02.md new file mode 100644 index 0000000..ff0b70e --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step02.md @@ -0,0 +1,165 @@ +## What You Will Learn during this Step: +- @RequestMapping(value = "/login", method = RequestMethod.GET) +- http://localhost:8080/login +- Why @ResponseBody? +- Important of RequestMapping method +- How do web applications work? Request and Response + - Browser sends Http Request to Web Server + - Code in Web Server => Input:HttpRequest, Output: HttpResponse + - Web Server responds with Http Response + +## Useful Snippets and References +First Snippet +``` +@Controller +public class LoginController { + + @RequestMapping(value = "/login") + @ResponseBody + public String sayHello() { + return "Hello World dummy"; + } + +} +``` +### src/main/resources/application.properties + +``` +logging.level.org.springframework.web: DEBUG +``` + + +## Exercises +- Create another method with a different mapping returning some other text! + +## Files List + +### pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-devtools + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/LoginController.java + +```java +package com.in28minutes.springboot.web.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +public class LoginController { + + @RequestMapping("/login") + public String loginMessage(){ + return "Hello World"; + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java + +```java +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringBootFirstWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootFirstWebApplication.class, args); + } +} +``` +--- +### src/main/resources/application.properties + +``` +logging.level.org.springframework.web: DEBUG +``` +--- +### src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java + +```java +package com.in28minutes.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootFirstWebApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- +### todo.txt + +``` +Spring Boot Starter Parent +Spring Boot Starter Web +@SpringBootApplication +Auto Configuration +``` +--- diff --git a/02.Spring-Boot-Web-Application/Step03.md b/02.Spring-Boot-Web-Application/Step03.md new file mode 100644 index 0000000..11a6fb3 --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step03.md @@ -0,0 +1,6 @@ +## What You Will Learn during this Step: +- Demystifying some of the magic + - Spring Boot Starter Parent + - Spring Boot Starter Web + - Embedded Tomcat + - Dev Tools diff --git a/02.Spring-Boot-Web-Application/Step04.md b/02.Spring-Boot-Web-Application/Step04.md new file mode 100644 index 0000000..4cc131f --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step04.md @@ -0,0 +1,207 @@ +## What You Will Learn during this Step: +- Your First JSP + - There is a bit of setup before we get there! +- Introduction to View Resolver + +## Useful Snippets and References +First Snippet - /src/main/webapp/WEB-INF/jsp/login.jsp +``` + + +Yahoo!! + + +My First JSP!!! + + +``` + +Second Snippet - /src/main/resources/application.properties +``` +spring.mvc.view.prefix: /WEB-INF/jsp/ +spring.mvc.view.suffix: .jsp +logging.level.: DEBUG +``` + +Third Snippet : To enable jsp support in embedded tomcat server! +``` + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + +``` + +## Exercises +- Create a new jsp and a new controller method to redirect to it! +- Play around! + +## Files List + +### pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-devtools + runtime + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/LoginController.java + +```java +package com.in28minutes.springboot.web.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +public class LoginController { + + @RequestMapping("/login") + public String loginMessage(){ + return "login"; + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java + +```java +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringBootFirstWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootFirstWebApplication.class, args); + } +} +``` +--- +### src/main/resources/application.properties + +``` +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp +logging.level.org.springframework.web=DEBUG +``` +--- +### src/main/webapp/WEB-INF/jsp/login.jsp + +``` + + + +First Web Application + + + +My First JSP!! + + + +``` +--- +### src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java + +```java +package com.in28minutes.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootFirstWebApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- +### todo.txt + +``` +Spring Boot Starter Parent +Spring Boot Starter Web +@SpringBootApplication +Auto Configuration + +Dispatcher Servlet + +/login => "login" + +"login" => src/main/webapp/WEB-INF/jsp/login.jsp + + +Search for a view named "login" + + + +/login => LoginController +``` +--- diff --git a/02.Spring-Boot-Web-Application/Step05.md b/02.Spring-Boot-Web-Application/Step05.md new file mode 100644 index 0000000..4357f7c --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step05.md @@ -0,0 +1,186 @@ +## What You Will Learn during this Step: +- You first GET Parameter. +- Problem with using GET + +## Snippets +``` +ModelMap model +model.put("name", name); +My First JSP!!! My name is ${name} +``` + +## Files List + +### pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/LoginController.java + +```java +package com.in28minutes.springboot.web.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@Controller +public class LoginController { + + //Model + + @RequestMapping("/login") + public String loginMessage(@RequestParam String name, ModelMap model){ + model.put("name", name); + return "login"; + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java + +```java +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringBootFirstWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootFirstWebApplication.class, args); + } +} +``` +--- +### src/main/resources/application.properties + +``` +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp +logging.level.org.springframework.web=DEBUG +``` +--- +### src/main/webapp/WEB-INF/jsp/login.jsp + +``` + + + +First Web Application + + + +My First JSP!! Welcome ${name}! + + + +``` +--- +### src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java + +```java +package com.in28minutes.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootFirstWebApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- +### todo.txt + +``` +Spring Boot Starter Parent +Spring Boot Starter Web +@SpringBootApplication +Auto Configuration + +Dispatcher Servlet + +/login => "login" + +"login" => src/main/webapp/WEB-INF/jsp/login.jsp + + +Search for a view named "login" + + + +/login => LoginController +``` +--- diff --git a/02.Spring-Boot-Web-Application/Step06.md b/02.Spring-Boot-Web-Application/Step06.md new file mode 100644 index 0000000..776452f --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step06.md @@ -0,0 +1,12 @@ +## What You Will Learn during this Step: +- Understand importance of DispatcherServlet. + +## Spring MVC Request Flow +- DispatcherServlet receives HTTP Request. +- DispatcherServlet identifies the right Controller based on the URL. +- Controller executes Business Logic. +- Controller returns a) Model b) View Name Back to DispatcherServlet. +- DispatcherServlet identifies the correct view (ViewResolver). +- DispatcherServlet makes the model available to view and executes it. +- DispatcherServlet returns HTTP Response Back. +- Flow : http://docs.spring.io/spring-framework/docs/2.0.8/reference/images/mvc.png diff --git a/02.Spring-Boot-Web-Application/Step07.md b/02.Spring-Boot-Web-Application/Step07.md new file mode 100644 index 0000000..514ffac --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step07.md @@ -0,0 +1,226 @@ +## What You Will Learn during this Step: +- Lets get the name from the user in a form + +## Useful Snippets and References +First Snippet +``` + @RequestMapping(value = "/login", method = RequestMethod.GET) + public String showLoginPage(ModelMap model, @RequestParam String name) { + return "login"; + } + + @RequestMapping(value = "/login", method = RequestMethod.POST) + public String handleLogin(ModelMap model, @RequestParam String name) { + model.put("name", name); + return "welcome"; + } + +``` +Second Snippet +``` +
+ Name : +
+ +``` + +## Files List + +### pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/LoginController.java + +```java +package com.in28minutes.springboot.web.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +@Controller +public class LoginController { + + @RequestMapping(value="/login", method = RequestMethod.GET) + public String showLoginPage(ModelMap model){ + return "login"; + } + + @RequestMapping(value="/login", method = RequestMethod.POST) + public String showWelcomePage(ModelMap model, @RequestParam String name){ + model.put("name", name); + return "welcome"; + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java + +```java +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringBootFirstWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootFirstWebApplication.class, args); + } +} +``` +--- +### src/main/resources/application.properties + +``` +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp +logging.level.org.springframework.web=DEBUG +``` +--- +### src/main/webapp/WEB-INF/jsp/login.jsp + +``` + + + +First Web Application + + + +
+ Name : + Password : + +
+ + + +``` +--- +### src/main/webapp/WEB-INF/jsp/welcome.jsp + +``` + + + +First Web Application + + + + Welcome ${name}!! + + + +``` +--- +### src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java + +```java +package com.in28minutes.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootFirstWebApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- +### todo.txt + +``` +Spring Boot Starter Parent +Spring Boot Starter Web +@SpringBootApplication +Auto Configuration + +Dispatcher Servlet + +/login => "login" + +"login" => src/main/webapp/WEB-INF/jsp/login.jsp + + +Search for a view named "login" + + + +/login => LoginController +``` +--- diff --git a/02.Spring-Boot-Web-Application/Step07.zip b/02.Spring-Boot-Web-Application/Step07.zip new file mode 100644 index 0000000..0a86c7d Binary files /dev/null and b/02.Spring-Boot-Web-Application/Step07.zip differ diff --git a/02.Spring-Boot-Web-Application/Step08.md b/02.Spring-Boot-Web-Application/Step08.md new file mode 100644 index 0000000..33c38ce --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step08.md @@ -0,0 +1,280 @@ +## What You Will Learn during this Step: +- Add validation for userid and password + - Hard coded validation!! + +## Useful Snippets and References +First Snippet +``` +package com.in28minutes.springboot.web.service; + +import org.springframework.stereotype.Component; + +@Component +public class LoginService { + public boolean validateUser(String user, String password) { + return user.equalsIgnoreCase("in28Minutes") && password.equals("dummy"); + } +} + +``` +Second Snippet +``` + @Autowired + private LoginService service; + + @RequestMapping(value = "/login", method = RequestMethod.POST) + public String handleLogin(ModelMap model, @RequestParam String name, + @RequestParam String password) { + + boolean isValidUser = service.validateUser(name, password); + + if (isValidUser) { + model.put("name", name); + return "welcome"; + } else { + model.put("errorMessage", "Invalid Credentials!!"); + return "login"; + } + } +``` + +## Files List + +### pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/LoginController.java + +```java +package com.in28minutes.springboot.web.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import com.in28minutes.springboot.web.service.LoginService; + +@Controller +public class LoginController { + + @Autowired + LoginService service; + + @RequestMapping(value="/login", method = RequestMethod.GET) + public String showLoginPage(ModelMap model){ + return "login"; + } + + @RequestMapping(value="/login", method = RequestMethod.POST) + public String showWelcomePage(ModelMap model, @RequestParam String name, @RequestParam String password){ + boolean isValidUser = service.validateUser(name, password); + + if (!isValidUser) { + model.put("errorMessage", "Invalid Credentials"); + return "login"; + } + + model.put("name", name); + model.put("password", password); + + return "welcome"; + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/LoginService.java + +```java +package com.in28minutes.springboot.web.service; + +import org.springframework.stereotype.Component; + +@Component +public class LoginService { + + public boolean validateUser(String userid, String password) { + // in28minutes, dummy + return userid.equalsIgnoreCase("in28minutes") + && password.equalsIgnoreCase("dummy"); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java + +```java +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringBootFirstWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootFirstWebApplication.class, args); + } +} +``` +--- +### src/main/resources/application.properties + +``` +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp +logging.level.org.springframework.web=DEBUG +``` +--- +### src/main/webapp/WEB-INF/jsp/login.jsp + +``` + + + +First Web Application + + + + ${errorMessage} +
+ Name : + Password : + +
+ + + +``` +--- +### src/main/webapp/WEB-INF/jsp/welcome.jsp + +``` + + + +First Web Application + + + + Welcome ${name}!! + + + +``` +--- +### src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java + +```java +package com.in28minutes.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootFirstWebApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- +### todo.txt + +``` +Component, Service, Repository, Controller +Autowired +ComponentScan + + +Spring Boot Starter Parent +Spring Boot Starter Web +@SpringBootApplication +Auto Configuration + +Dispatcher Servlet + +/login => "login" + +"login" => src/main/webapp/WEB-INF/jsp/login.jsp + + +Search for a view named "login" + + + +/login => LoginController +``` +--- diff --git a/02.Spring-Boot-Web-Application/Step08.zip b/02.Spring-Boot-Web-Application/Step08.zip new file mode 100644 index 0000000..86e7a50 Binary files /dev/null and b/02.Spring-Boot-Web-Application/Step08.zip differ diff --git a/02.Spring-Boot-Web-Application/Step09.md b/02.Spring-Boot-Web-Application/Step09.md new file mode 100644 index 0000000..9a223c4 --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step09.md @@ -0,0 +1,4 @@ +## What You Will Learn during this Step: +- Magic of Spring + - Learn about Spring Auto-wiring and Dependency Management. + - @Autowired, @Component diff --git a/02.Spring-Boot-Web-Application/Step10.md b/02.Spring-Boot-Web-Application/Step10.md new file mode 100644 index 0000000..10150b7 --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step10.md @@ -0,0 +1,161 @@ +## What we will do: +- Create TodoController and list-todos.jsp +- Make TodoService a @Service and inject it + +## Pending for Next Step +- ${name} is not available in list-todos.jsp +- in28Minutes is hardcoded in TodoController + +## Snippets + +Snippet - /src/main/java/com/in28minutes/springboot/web/model/Todo.java +``` +package com.in28minutes.springboot.web.model; + +import java.util.Date; + +public class Todo { + private int id; + private String user; + private String desc; + private Date targetDate; + private boolean isDone; + + public Todo(int id, String user, String desc, Date targetDate, + boolean isDone) { + super(); + this.id = id; + this.user = user; + this.desc = desc; + this.targetDate = targetDate; + this.isDone = isDone; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public Date getTargetDate() { + return targetDate; + } + + public void setTargetDate(Date targetDate) { + this.targetDate = targetDate; + } + + public boolean isDone() { + return isDone; + } + + public void setDone(boolean isDone) { + this.isDone = isDone; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Todo other = (Todo) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return String.format( + "Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]", id, + user, desc, targetDate, isDone); + } + +} +``` + +Snippet - /src/main/java/com/in28minutes/springboot/web/service/TodoService.java +``` +package com.in28minutes.springboot.web.service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.in28minutes.springboot.web.model.Todo; + +@Service +public class TodoService { + private static List todos = new ArrayList(); + private static int todoCount = 3; + + static { + todos.add(new Todo(1, "in28Minutes", "Learn Spring MVC", new Date(), + false)); + todos.add(new Todo(2, "in28Minutes", "Learn Struts", new Date(), false)); + todos.add(new Todo(3, "in28Minutes", "Learn Hibernate", new Date(), + false)); + } + + public List retrieveTodos(String user) { + List filteredTodos = new ArrayList(); + for (Todo todo : todos) { + if (todo.getUser().equals(user)) { + filteredTodos.add(todo); + } + } + return filteredTodos; + } + + public void addTodo(String name, String desc, Date targetDate, + boolean isDone) { + todos.add(new Todo(++todoCount, name, desc, targetDate, isDone)); + } + + public void deleteTodo(int id) { + Iterator iterator = todos.iterator(); + while (iterator.hasNext()) { + Todo todo = iterator.next(); + if (todo.getId() == id) { + iterator.remove(); + } + } + } +} +``` diff --git a/02.Spring-Boot-Web-Application/Step11.md b/02.Spring-Boot-Web-Application/Step11.md new file mode 100644 index 0000000..27de8bc --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step11.md @@ -0,0 +1,2 @@ +## What we will do: +- Lets discuss about Architecture of web applications diff --git a/02.Spring-Boot-Web-Application/Step12.md b/02.Spring-Boot-Web-Application/Step12.md new file mode 100644 index 0000000..6296489 --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step12.md @@ -0,0 +1,474 @@ +## What You Will Learn during this Step: +- Session vs Model vs Request. +- Be cautious about what you use Session for. +- @SessionAttributes("name") and how it works? +- Why use Model? "adding elements directly to the HttpServletRequest (as request attributes) would seem to serve the same purpose. The reason to do this is obvious when taking a look at one of the requirements we have set for the MVC framework: It should be as view-agnostic as possible, which means we’d like to be able to incorporate view technologies not bound to the HttpServletRequest as well." - Rod Johnson et. al’s book Professional Java Development with the Spring Framework +- Spring documentation states that the @SessionAttributes annotation “list the names of model attributes which should be transparently stored in the session or some conversational storage.” + +## Useful Snippets and References +First Snippet +``` +@SessionAttributes("name") +``` + +## Files List + +### pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/LoginController.java + +```java +package com.in28minutes.springboot.web.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.service.LoginService; + +@Controller +@SessionAttributes("name") +public class LoginController { + + @Autowired + LoginService service; + + @RequestMapping(value="/login", method = RequestMethod.GET) + public String showLoginPage(ModelMap model){ + return "login"; + } + + @RequestMapping(value="/login", method = RequestMethod.POST) + public String showWelcomePage(ModelMap model, @RequestParam String name, @RequestParam String password){ + + boolean isValidUser = service.validateUser(name, password); + + if (!isValidUser) { + model.put("errorMessage", "Invalid Credentials"); + return "login"; + } + + model.put("name", name); + model.put("password", password); + + return "welcome"; + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/TodoController.java + +```java +package com.in28minutes.springboot.web.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.service.LoginService; +import com.in28minutes.springboot.web.service.TodoService; + +@Controller +@SessionAttributes("name") +public class TodoController { + + @Autowired + TodoService service; + + @RequestMapping(value="/list-todos", method = RequestMethod.GET) + public String showTodos(ModelMap model){ + String name = (String) model.get("name"); + model.put("todos", service.retrieveTodos(name)); + return "list-todos"; + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/model/Todo.java + +```java +package com.in28minutes.springboot.web.model; + +import java.util.Date; + +public class Todo { + private int id; + private String user; + private String desc; + private Date targetDate; + private boolean isDone; + + public Todo(int id, String user, String desc, Date targetDate, + boolean isDone) { + super(); + this.id = id; + this.user = user; + this.desc = desc; + this.targetDate = targetDate; + this.isDone = isDone; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public Date getTargetDate() { + return targetDate; + } + + public void setTargetDate(Date targetDate) { + this.targetDate = targetDate; + } + + public boolean isDone() { + return isDone; + } + + public void setDone(boolean isDone) { + this.isDone = isDone; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Todo other = (Todo) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return String.format( + "Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]", id, + user, desc, targetDate, isDone); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/LoginService.java + +```java +package com.in28minutes.springboot.web.service; + +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +@Service +public class LoginService { + + public boolean validateUser(String userid, String password) { + // in28minutes, dummy + return userid.equalsIgnoreCase("in28minutes") + && password.equalsIgnoreCase("dummy"); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/TodoService.java + +```java +package com.in28minutes.springboot.web.service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.in28minutes.springboot.web.model.Todo; + +@Service +public class TodoService { + private static List todos = new ArrayList(); + private static int todoCount = 3; + + static { + todos.add(new Todo(1, "in28Minutes", "Learn Spring MVC", new Date(), + false)); + todos.add(new Todo(2, "in28Minutes", "Learn Struts", new Date(), false)); + todos.add(new Todo(3, "in28Minutes", "Learn Hibernate", new Date(), + false)); + } + + public List retrieveTodos(String user) { + List filteredTodos = new ArrayList(); + for (Todo todo : todos) { + if (todo.getUser().equals(user)) { + filteredTodos.add(todo); + } + } + return filteredTodos; + } + + public void addTodo(String name, String desc, Date targetDate, + boolean isDone) { + todos.add(new Todo(++todoCount, name, desc, targetDate, isDone)); + } + + public void deleteTodo(int id) { + Iterator iterator = todos.iterator(); + while (iterator.hasNext()) { + Todo todo = iterator.next(); + if (todo.getId() == id) { + iterator.remove(); + } + } + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java + +```java +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan("com.in28minutes.springboot.web") +public class SpringBootFirstWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootFirstWebApplication.class, args); + } +} +``` +--- +### src/main/resources/application.properties + +``` +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp +logging.level.org.springframework.web=DEBUG +``` +--- +### src/main/webapp/WEB-INF/jsp/list-todos.jsp + +``` + + + +First Web Application + + + + Here are the list of your todos: + ${todos} +
+ Your Name is : ${name} + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/login.jsp + +``` + + + +First Web Application + + + + ${errorMessage} +
+ Name : + Password : + +
+ + + +``` +--- +### src/main/webapp/WEB-INF/jsp/welcome.jsp + +``` + + + +First Web Application + + + + Welcome ${name}!! Click here to manage your todo's. + + + +``` +--- +### src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java + +```java +package com.in28minutes.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootFirstWebApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- +### todo.txt + +``` +LoginController -> adds name to model +welcome.jsp -> shows ${name} + +TodoController -> redirects to list-todos.jsp +${name} is empty + + +Component, Service, Repository, Controller +Autowired +ComponentScan + +Field dummyService in com.in28minutes.springboot.web.controller.LoginController +required a bean of type 'com.in28minutes.dummy.DummyService' +that could not be found. + +Spring Boot Starter Parent +Spring Boot Starter Web +@SpringBootApplication +Auto Configuration + +Dispatcher Servlet + +/login => "login" + +"login" => src/main/webapp/WEB-INF/jsp/login.jsp + + +Search for a view named "login" + + + +/login => LoginController +``` +--- diff --git a/02.Spring-Boot-Web-Application/Step12.zip b/02.Spring-Boot-Web-Application/Step12.zip new file mode 100644 index 0000000..f38ee97 Binary files /dev/null and b/02.Spring-Boot-Web-Application/Step12.zip differ diff --git a/02.Spring-Boot-Web-Application/Step13.md b/02.Spring-Boot-Web-Application/Step13.md new file mode 100644 index 0000000..795872e --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step13.md @@ -0,0 +1,502 @@ +## What we will do: +- Add Facility to add New Todo +- todo.jsp +- Importance of redirect:/list-todos + +## Files List + +### pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/LoginController.java + +```java +package com.in28minutes.springboot.web.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.service.LoginService; + +@Controller +@SessionAttributes("name") +public class LoginController { + + @Autowired + LoginService service; + + @RequestMapping(value="/login", method = RequestMethod.GET) + public String showLoginPage(ModelMap model){ + return "login"; + } + + @RequestMapping(value="/login", method = RequestMethod.POST) + public String showWelcomePage(ModelMap model, @RequestParam String name, @RequestParam String password){ + + boolean isValidUser = service.validateUser(name, password); + + if (!isValidUser) { + model.put("errorMessage", "Invalid Credentials"); + return "login"; + } + + model.put("name", name); + model.put("password", password); + + return "welcome"; + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/TodoController.java + +```java +package com.in28minutes.springboot.web.controller; + +import java.util.Date; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.service.LoginService; +import com.in28minutes.springboot.web.service.TodoService; + +@Controller +@SessionAttributes("name") +public class TodoController { + + @Autowired + TodoService service; + + @RequestMapping(value="/list-todos", method = RequestMethod.GET) + public String showTodos(ModelMap model){ + String name = (String) model.get("name"); + model.put("todos", service.retrieveTodos(name)); + return "list-todos"; + } + + @RequestMapping(value="/add-todo", method = RequestMethod.GET) + public String showAddTodoPage(ModelMap model){ + return "todo"; + } + + @RequestMapping(value="/add-todo", method = RequestMethod.POST) + public String addTodo(ModelMap model, @RequestParam String desc){ + service.addTodo((String) model.get("name"), desc, new Date(), false); + return "redirect:/list-todos"; + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/model/Todo.java + +```java +package com.in28minutes.springboot.web.model; + +import java.util.Date; + +public class Todo { + private int id; + private String user; + private String desc; + private Date targetDate; + private boolean isDone; + + public Todo(int id, String user, String desc, Date targetDate, + boolean isDone) { + super(); + this.id = id; + this.user = user; + this.desc = desc; + this.targetDate = targetDate; + this.isDone = isDone; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public Date getTargetDate() { + return targetDate; + } + + public void setTargetDate(Date targetDate) { + this.targetDate = targetDate; + } + + public boolean isDone() { + return isDone; + } + + public void setDone(boolean isDone) { + this.isDone = isDone; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Todo other = (Todo) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return String.format( + "Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]", id, + user, desc, targetDate, isDone); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/LoginService.java + +```java +package com.in28minutes.springboot.web.service; + +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +@Service +public class LoginService { + + public boolean validateUser(String userid, String password) { + // in28minutes, dummy + return userid.equalsIgnoreCase("in28minutes") + && password.equalsIgnoreCase("dummy"); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/TodoService.java + +```java +package com.in28minutes.springboot.web.service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.in28minutes.springboot.web.model.Todo; + +@Service +public class TodoService { + private static List todos = new ArrayList(); + private static int todoCount = 3; + + static { + todos.add(new Todo(1, "in28Minutes", "Learn Spring MVC", new Date(), + false)); + todos.add(new Todo(2, "in28Minutes", "Learn Struts", new Date(), false)); + todos.add(new Todo(3, "in28Minutes", "Learn Hibernate", new Date(), + false)); + } + + public List retrieveTodos(String user) { + List filteredTodos = new ArrayList(); + for (Todo todo : todos) { + if (todo.getUser().equals(user)) { + filteredTodos.add(todo); + } + } + return filteredTodos; + } + + public void addTodo(String name, String desc, Date targetDate, + boolean isDone) { + todos.add(new Todo(++todoCount, name, desc, targetDate, isDone)); + } + + public void deleteTodo(int id) { + Iterator iterator = todos.iterator(); + while (iterator.hasNext()) { + Todo todo = iterator.next(); + if (todo.getId() == id) { + iterator.remove(); + } + } + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java + +```java +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan("com.in28minutes.springboot.web") +public class SpringBootFirstWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootFirstWebApplication.class, args); + } +} +``` +--- +### src/main/resources/application.properties + +``` +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp +logging.level.org.springframework.web=DEBUG +``` +--- +### src/main/webapp/WEB-INF/jsp/list-todos.jsp + +``` + + + +First Web Application + + + + Here are the list of ${name}'s todos: + ${todos}. +
+ Add a Todo + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/login.jsp + +``` + + + +First Web Application + + + + ${errorMessage} +
+ Name : + Password : + +
+ + + +``` +--- +### src/main/webapp/WEB-INF/jsp/todo.jsp + +``` + + + +First Web Application + + + +ADD Todo Page for ${name} + +
+ Description : + +
+ + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/welcome.jsp + +``` + + + +First Web Application + + + + Welcome ${name}!! Click here to manage your todo's. + + + +``` +--- +### src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java + +```java +package com.in28minutes.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootFirstWebApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- +### todo.txt + +``` +LoginController -> adds name to model +welcome.jsp -> shows ${name} + +TodoController -> redirects to list-todos.jsp +${name} is empty + + +Component, Service, Repository, Controller +Autowired +ComponentScan + +Field dummyService in com.in28minutes.springboot.web.controller.LoginController +required a bean of type 'com.in28minutes.dummy.DummyService' +that could not be found. + +Spring Boot Starter Parent +Spring Boot Starter Web +@SpringBootApplication +Auto Configuration + +Dispatcher Servlet + +/login => "login" + +"login" => src/main/webapp/WEB-INF/jsp/login.jsp + + +Search for a view named "login" + + + +/login => LoginController +``` +--- diff --git a/02.Spring-Boot-Web-Application/Step13.zip b/02.Spring-Boot-Web-Application/Step13.zip new file mode 100644 index 0000000..2a78a2a Binary files /dev/null and b/02.Spring-Boot-Web-Application/Step13.zip differ diff --git a/02.Spring-Boot-Web-Application/Step14.md b/02.Spring-Boot-Web-Application/Step14.md new file mode 100644 index 0000000..01b021b --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step14.md @@ -0,0 +1,12 @@ +## What we will do: +- Display Todos in a table using JSTL Tags +- <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +- Add Dependency for jstl + +## Snippet +``` + + javax.servlet + jstl + +``` diff --git a/02.Spring-Boot-Web-Application/Step15.md b/02.Spring-Boot-Web-Application/Step15.md new file mode 100644 index 0000000..0542599 --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step15.md @@ -0,0 +1,566 @@ +## What we will do: +- Add bootstrap to give basic formatting to the page : We use bootstrap classes container,table and table-striped. +- We will use webjars + - Already auto configured by Spring Boot : o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] + +## Useful Snippets +``` + + org.webjars + bootstrap + 3.3.6 + + + org.webjars + jquery + 1.9.1 + + + + + + + + +``` + +## Files List + +### pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + javax.servlet + jstl + + + + org.webjars + bootstrap + 3.3.6 + + + + org.webjars + jquery + 1.9.1 + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/LoginController.java + +```java +package com.in28minutes.springboot.web.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.service.LoginService; + +@Controller +@SessionAttributes("name") +public class LoginController { + + @Autowired + LoginService service; + + @RequestMapping(value="/login", method = RequestMethod.GET) + public String showLoginPage(ModelMap model){ + return "login"; + } + + @RequestMapping(value="/login", method = RequestMethod.POST) + public String showWelcomePage(ModelMap model, @RequestParam String name, @RequestParam String password){ + + boolean isValidUser = service.validateUser(name, password); + + if (!isValidUser) { + model.put("errorMessage", "Invalid Credentials"); + return "login"; + } + + model.put("name", name); + model.put("password", password); + + return "welcome"; + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/TodoController.java + +```java +package com.in28minutes.springboot.web.controller; + +import java.util.Date; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.service.LoginService; +import com.in28minutes.springboot.web.service.TodoService; + +@Controller +@SessionAttributes("name") +public class TodoController { + + @Autowired + TodoService service; + + @RequestMapping(value="/list-todos", method = RequestMethod.GET) + public String showTodos(ModelMap model){ + String name = (String) model.get("name"); + model.put("todos", service.retrieveTodos(name)); + return "list-todos"; + } + + @RequestMapping(value="/add-todo", method = RequestMethod.GET) + public String showAddTodoPage(ModelMap model){ + return "todo"; + } + + @RequestMapping(value="/add-todo", method = RequestMethod.POST) + public String addTodo(ModelMap model, @RequestParam String desc){ + service.addTodo((String) model.get("name"), desc, new Date(), false); + return "redirect:/list-todos"; + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/model/Todo.java + +```java +package com.in28minutes.springboot.web.model; + +import java.util.Date; + +public class Todo { + private int id; + private String user; + private String desc; + private Date targetDate; + private boolean isDone; + + public Todo(int id, String user, String desc, Date targetDate, + boolean isDone) { + super(); + this.id = id; + this.user = user; + this.desc = desc; + this.targetDate = targetDate; + this.isDone = isDone; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public Date getTargetDate() { + return targetDate; + } + + public void setTargetDate(Date targetDate) { + this.targetDate = targetDate; + } + + public boolean isDone() { + return isDone; + } + + public void setDone(boolean isDone) { + this.isDone = isDone; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Todo other = (Todo) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return String.format( + "Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]", id, + user, desc, targetDate, isDone); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/LoginService.java + +```java +package com.in28minutes.springboot.web.service; + +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +@Service +public class LoginService { + + public boolean validateUser(String userid, String password) { + // in28minutes, dummy + return userid.equalsIgnoreCase("in28minutes") + && password.equalsIgnoreCase("dummy"); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/TodoService.java + +```java +package com.in28minutes.springboot.web.service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.in28minutes.springboot.web.model.Todo; + +@Service +public class TodoService { + private static List todos = new ArrayList(); + private static int todoCount = 3; + + static { + todos.add(new Todo(1, "in28Minutes", "Learn Spring MVC", new Date(), + false)); + todos.add(new Todo(2, "in28Minutes", "Learn Struts", new Date(), false)); + todos.add(new Todo(3, "in28Minutes", "Learn Hibernate", new Date(), + false)); + } + + public List retrieveTodos(String user) { + List filteredTodos = new ArrayList(); + for (Todo todo : todos) { + if (todo.getUser().equals(user)) { + filteredTodos.add(todo); + } + } + return filteredTodos; + } + + public void addTodo(String name, String desc, Date targetDate, + boolean isDone) { + todos.add(new Todo(++todoCount, name, desc, targetDate, isDone)); + } + + public void deleteTodo(int id) { + Iterator iterator = todos.iterator(); + while (iterator.hasNext()) { + Todo todo = iterator.next(); + if (todo.getId() == id) { + iterator.remove(); + } + } + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java + +```java +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan("com.in28minutes.springboot.web") +public class SpringBootFirstWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootFirstWebApplication.class, args); + } +} +``` +--- +### src/main/resources/application.properties + +``` +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp +logging.level.org.springframework.web=INFO +``` +--- +### src/main/webapp/WEB-INF/jsp/list-todos.jsp + +``` +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> + + + + +Todo's for ${name} + + + + +
+ + + + + + + + + + + + + + + + + + +
Your todos are
DescriptionTarget DateIs it Done?
${todo.desc}${todo.targetDate}${todo.done}
+ + + + +
+ + + +``` +--- +### src/main/webapp/WEB-INF/jsp/login.jsp + +``` + + + +First Web Application + + + + ${errorMessage} +
+ Name : + Password : + +
+ + + +``` +--- +### src/main/webapp/WEB-INF/jsp/todo.jsp + +``` + + + +First Web Application + + + +ADD Todo Page for ${name} + +
+ Description : + +
+ + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/welcome.jsp + +``` + + + +First Web Application + + + + Welcome ${name}!! Click here to manage your todo's. + + + +``` +--- +### src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java + +```java +package com.in28minutes.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootFirstWebApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- +### todo.txt + +``` +LoginController -> adds name to model +welcome.jsp -> shows ${name} + +TodoController -> redirects to list-todos.jsp +${name} is empty + + +Component, Service, Repository, Controller +Autowired +ComponentScan + +Field dummyService in com.in28minutes.springboot.web.controller.LoginController +required a bean of type 'com.in28minutes.dummy.DummyService' +that could not be found. + +Spring Boot Starter Parent +Spring Boot Starter Web +@SpringBootApplication +Auto Configuration + +Dispatcher Servlet + +/login => "login" + +"login" => src/main/webapp/WEB-INF/jsp/login.jsp + + +Search for a view named "login" + + + +/login => LoginController +``` +--- diff --git a/02.Spring-Boot-Web-Application/Step15.zip b/02.Spring-Boot-Web-Application/Step15.zip new file mode 100644 index 0000000..14da314 Binary files /dev/null and b/02.Spring-Boot-Web-Application/Step15.zip differ diff --git a/02.Spring-Boot-Web-Application/Step16.md b/02.Spring-Boot-Web-Application/Step16.md new file mode 100644 index 0000000..8b0a2af --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step16.md @@ -0,0 +1,555 @@ +## What we will do: +- Add functionality to delete a todo + +## Useful Snippets +``` + Delete +``` +## Files List + +### pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + javax.servlet + jstl + + + + org.webjars + bootstrap + 3.3.6 + + + + org.webjars + jquery + 1.9.1 + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/LoginController.java + +```java +package com.in28minutes.springboot.web.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.service.LoginService; + +@Controller +@SessionAttributes("name") +public class LoginController { + + @Autowired + LoginService service; + + @RequestMapping(value="/login", method = RequestMethod.GET) + public String showLoginPage(ModelMap model){ + return "login"; + } + + @RequestMapping(value="/login", method = RequestMethod.POST) + public String showWelcomePage(ModelMap model, @RequestParam String name, @RequestParam String password){ + + boolean isValidUser = service.validateUser(name, password); + + if (!isValidUser) { + model.put("errorMessage", "Invalid Credentials"); + return "login"; + } + + model.put("name", name); + model.put("password", password); + + return "welcome"; + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/TodoController.java + +```java +package com.in28minutes.springboot.web.controller; + +import java.util.Date; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.service.LoginService; +import com.in28minutes.springboot.web.service.TodoService; + +@Controller +@SessionAttributes("name") +public class TodoController { + + @Autowired + TodoService service; + + @RequestMapping(value="/list-todos", method = RequestMethod.GET) + public String showTodos(ModelMap model){ + String name = (String) model.get("name"); + model.put("todos", service.retrieveTodos(name)); + return "list-todos"; + } + + @RequestMapping(value="/add-todo", method = RequestMethod.GET) + public String showAddTodoPage(ModelMap model){ + return "todo"; + } + + @RequestMapping(value="/delete-todo", method = RequestMethod.GET) + public String deleteTodo(@RequestParam int id){ + service.deleteTodo(id); + return "redirect:/list-todos"; + } + + @RequestMapping(value="/add-todo", method = RequestMethod.POST) + public String addTodo(ModelMap model, @RequestParam String desc){ + service.addTodo((String) model.get("name"), desc, new Date(), false); + return "redirect:/list-todos"; + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/model/Todo.java + +```java +package com.in28minutes.springboot.web.model; + +import java.util.Date; + +public class Todo { + private int id; + private String user; + private String desc; + private Date targetDate; + private boolean isDone; + + public Todo(int id, String user, String desc, Date targetDate, + boolean isDone) { + super(); + this.id = id; + this.user = user; + this.desc = desc; + this.targetDate = targetDate; + this.isDone = isDone; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public Date getTargetDate() { + return targetDate; + } + + public void setTargetDate(Date targetDate) { + this.targetDate = targetDate; + } + + public boolean isDone() { + return isDone; + } + + public void setDone(boolean isDone) { + this.isDone = isDone; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Todo other = (Todo) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return String.format( + "Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]", id, + user, desc, targetDate, isDone); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/LoginService.java + +```java +package com.in28minutes.springboot.web.service; + +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +@Service +public class LoginService { + + public boolean validateUser(String userid, String password) { + // in28minutes, dummy + return userid.equalsIgnoreCase("in28minutes") + && password.equalsIgnoreCase("dummy"); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/TodoService.java + +```java +package com.in28minutes.springboot.web.service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.in28minutes.springboot.web.model.Todo; + +@Service +public class TodoService { + private static List todos = new ArrayList(); + private static int todoCount = 3; + + static { + todos.add(new Todo(1, "in28Minutes", "Learn Spring MVC", new Date(), + false)); + todos.add(new Todo(2, "in28Minutes", "Learn Struts", new Date(), false)); + todos.add(new Todo(3, "in28Minutes", "Learn Hibernate", new Date(), + false)); + } + + public List retrieveTodos(String user) { + List filteredTodos = new ArrayList(); + for (Todo todo : todos) { + if (todo.getUser().equals(user)) { + filteredTodos.add(todo); + } + } + return filteredTodos; + } + + public void addTodo(String name, String desc, Date targetDate, + boolean isDone) { + todos.add(new Todo(++todoCount, name, desc, targetDate, isDone)); + } + + public void deleteTodo(int id) { + Iterator iterator = todos.iterator(); + while (iterator.hasNext()) { + Todo todo = iterator.next(); + if (todo.getId() == id) { + iterator.remove(); + } + } + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java + +```java +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan("com.in28minutes.springboot.web") +public class SpringBootFirstWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootFirstWebApplication.class, args); + } +} +``` +--- +### src/main/resources/application.properties + +``` +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp +logging.level.org.springframework.web=INFO +``` +--- +### src/main/webapp/WEB-INF/jsp/list-todos.jsp + +``` +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> + + + + +Todo's for ${name} + + + + +
+ + + + + + + + + + + + + + + + + + + + +
Your todos are
DescriptionTarget DateIs it Done?
${todo.desc}${todo.targetDate}${todo.done}Delete
+ + + + +
+ + + +``` +--- +### src/main/webapp/WEB-INF/jsp/login.jsp + +``` + + + +First Web Application + + + + ${errorMessage} +
+ Name : + Password : + +
+ + + +``` +--- +### src/main/webapp/WEB-INF/jsp/todo.jsp + +``` + + + +First Web Application + + + +ADD Todo Page for ${name} + +
+ Description : + +
+ + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/welcome.jsp + +``` + + + +First Web Application + + + + Welcome ${name}!! Click here to manage your todo's. + + + +``` +--- +### src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java + +```java +package com.in28minutes.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootFirstWebApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- +### todo.txt + +``` +LoginController -> adds name to model +welcome.jsp -> shows ${name} + +TodoController -> redirects to list-todos.jsp +${name} is empty + + +Component, Service, Repository, Controller +Autowired +ComponentScan + +Field dummyService in com.in28minutes.springboot.web.controller.LoginController +required a bean of type 'com.in28minutes.dummy.DummyService' +that could not be found. + +Spring Boot Starter Parent +Spring Boot Starter Web +@SpringBootApplication +Auto Configuration + +Dispatcher Servlet + +/login => "login" + +"login" => src/main/webapp/WEB-INF/jsp/login.jsp + + +Search for a view named "login" + + + +/login => LoginController +``` +--- diff --git a/02.Spring-Boot-Web-Application/Step16.zip b/02.Spring-Boot-Web-Application/Step16.zip new file mode 100644 index 0000000..603e6c8 Binary files /dev/null and b/02.Spring-Boot-Web-Application/Step16.zip differ diff --git a/02.Spring-Boot-Web-Application/Step17.md b/02.Spring-Boot-Web-Application/Step17.md new file mode 100644 index 0000000..d6a1cc6 --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step17.md @@ -0,0 +1,576 @@ +## What we will do: +In this short step: +- Format Add Todo Page +- Add Html5 Form Validations + +## Useful Snippets +``` +
+ + +
+ +``` + +## Files List + +### pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + javax.servlet + jstl + + + + org.webjars + bootstrap + 3.3.6 + + + + org.webjars + jquery + 1.9.1 + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/LoginController.java + +```java +package com.in28minutes.springboot.web.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.service.LoginService; + +@Controller +@SessionAttributes("name") +public class LoginController { + + @Autowired + LoginService service; + + @RequestMapping(value="/login", method = RequestMethod.GET) + public String showLoginPage(ModelMap model){ + return "login"; + } + + @RequestMapping(value="/login", method = RequestMethod.POST) + public String showWelcomePage(ModelMap model, @RequestParam String name, @RequestParam String password){ + + boolean isValidUser = service.validateUser(name, password); + + if (!isValidUser) { + model.put("errorMessage", "Invalid Credentials"); + return "login"; + } + + model.put("name", name); + model.put("password", password); + + return "welcome"; + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/TodoController.java + +```java +package com.in28minutes.springboot.web.controller; + +import java.util.Date; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.service.LoginService; +import com.in28minutes.springboot.web.service.TodoService; + +@Controller +@SessionAttributes("name") +public class TodoController { + + @Autowired + TodoService service; + + @RequestMapping(value="/list-todos", method = RequestMethod.GET) + public String showTodos(ModelMap model){ + String name = (String) model.get("name"); + model.put("todos", service.retrieveTodos(name)); + return "list-todos"; + } + + @RequestMapping(value="/add-todo", method = RequestMethod.GET) + public String showAddTodoPage(ModelMap model){ + return "todo"; + } + + @RequestMapping(value="/delete-todo", method = RequestMethod.GET) + public String deleteTodo(@RequestParam int id){ + service.deleteTodo(id); + return "redirect:/list-todos"; + } + + @RequestMapping(value="/add-todo", method = RequestMethod.POST) + public String addTodo(ModelMap model, @RequestParam String desc){ + service.addTodo((String) model.get("name"), desc, new Date(), false); + return "redirect:/list-todos"; + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/model/Todo.java + +```java +package com.in28minutes.springboot.web.model; + +import java.util.Date; + +public class Todo { + private int id; + private String user; + private String desc; + private Date targetDate; + private boolean isDone; + + public Todo(int id, String user, String desc, Date targetDate, + boolean isDone) { + super(); + this.id = id; + this.user = user; + this.desc = desc; + this.targetDate = targetDate; + this.isDone = isDone; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public Date getTargetDate() { + return targetDate; + } + + public void setTargetDate(Date targetDate) { + this.targetDate = targetDate; + } + + public boolean isDone() { + return isDone; + } + + public void setDone(boolean isDone) { + this.isDone = isDone; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Todo other = (Todo) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return String.format( + "Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]", id, + user, desc, targetDate, isDone); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/LoginService.java + +```java +package com.in28minutes.springboot.web.service; + +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +@Service +public class LoginService { + + public boolean validateUser(String userid, String password) { + // in28minutes, dummy + return userid.equalsIgnoreCase("in28minutes") + && password.equalsIgnoreCase("dummy"); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/TodoService.java + +```java +package com.in28minutes.springboot.web.service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.in28minutes.springboot.web.model.Todo; + +@Service +public class TodoService { + private static List todos = new ArrayList(); + private static int todoCount = 3; + + static { + todos.add(new Todo(1, "in28Minutes", "Learn Spring MVC", new Date(), + false)); + todos.add(new Todo(2, "in28Minutes", "Learn Struts", new Date(), false)); + todos.add(new Todo(3, "in28Minutes", "Learn Hibernate", new Date(), + false)); + } + + public List retrieveTodos(String user) { + List filteredTodos = new ArrayList(); + for (Todo todo : todos) { + if (todo.getUser().equals(user)) { + filteredTodos.add(todo); + } + } + return filteredTodos; + } + + public void addTodo(String name, String desc, Date targetDate, + boolean isDone) { + todos.add(new Todo(++todoCount, name, desc, targetDate, isDone)); + } + + public void deleteTodo(int id) { + Iterator iterator = todos.iterator(); + while (iterator.hasNext()) { + Todo todo = iterator.next(); + if (todo.getId() == id) { + iterator.remove(); + } + } + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java + +```java +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan("com.in28minutes.springboot.web") +public class SpringBootFirstWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootFirstWebApplication.class, args); + } +} +``` +--- +### src/main/resources/application.properties + +``` +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp +logging.level.org.springframework.web=INFO +``` +--- +### src/main/webapp/WEB-INF/jsp/list-todos.jsp + +``` +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> + + + + +Todo's for ${name} + + + + +
+ + + + + + + + + + + + + + + + + + + + +
Your todos are
DescriptionTarget DateIs it Done?
${todo.desc}${todo.targetDate}${todo.done}Delete
+ +
+ + + + + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/login.jsp + +``` + + + +First Web Application + + + + ${errorMessage} +
+ Name : + Password : + +
+ + + +``` +--- +### src/main/webapp/WEB-INF/jsp/todo.jsp + +``` + + + +First Web Application + + + + + +
+
+
+ + +
+ + +
+
+ + + + + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/welcome.jsp + +``` + + + +First Web Application + + + + Welcome ${name}!! Click here to manage your todo's. + + + +``` +--- +### src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java + +```java +package com.in28minutes.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootFirstWebApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- +### todo.txt + +``` +LoginController -> adds name to model +welcome.jsp -> shows ${name} + +TodoController -> redirects to list-todos.jsp +${name} is empty + + +Component, Service, Repository, Controller +Autowired +ComponentScan + +Field dummyService in com.in28minutes.springboot.web.controller.LoginController +required a bean of type 'com.in28minutes.dummy.DummyService' +that could not be found. + +Spring Boot Starter Parent +Spring Boot Starter Web +@SpringBootApplication +Auto Configuration + +Dispatcher Servlet + +/login => "login" + +"login" => src/main/webapp/WEB-INF/jsp/login.jsp + + +Search for a view named "login" + + + +/login => LoginController +``` +--- diff --git a/02.Spring-Boot-Web-Application/Step18.md b/02.Spring-Boot-Web-Application/Step18.md new file mode 100644 index 0000000..c507ca6 --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step18.md @@ -0,0 +1,626 @@ +## What we will do: +- Lets use a command bean for Todo +- Add Validations +- The JSR 303 and JSR 349 defines specification for the Bean Validation API (version 1.0 and 1.1, respectively), and Hibernate Validator is the reference implementation. +- org.hibernate:hibernate-validator + +## Useful Snippets +``` + <%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> + + +
+ Description + +
+
+ + @Size(min = 10, message = "Enter atleast 10 Characters.") + + @Valid Todo todo, BindingResult result + + if (result.hasErrors()) + return "todo"; + + + +``` +## Files List + +### pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + javax.servlet + jstl + + + + org.webjars + bootstrap + 3.3.6 + + + + org.webjars + jquery + 1.9.1 + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/LoginController.java + +```java +package com.in28minutes.springboot.web.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.service.LoginService; + +@Controller +@SessionAttributes("name") +public class LoginController { + + @Autowired + LoginService service; + + @RequestMapping(value="/login", method = RequestMethod.GET) + public String showLoginPage(ModelMap model){ + return "login"; + } + + @RequestMapping(value="/login", method = RequestMethod.POST) + public String showWelcomePage(ModelMap model, @RequestParam String name, @RequestParam String password){ + + boolean isValidUser = service.validateUser(name, password); + + if (!isValidUser) { + model.put("errorMessage", "Invalid Credentials"); + return "login"; + } + + model.put("name", name); + model.put("password", password); + + return "welcome"; + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/TodoController.java + +```java +package com.in28minutes.springboot.web.controller; + +import java.util.Date; + +import javax.validation.Valid; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.model.Todo; +import com.in28minutes.springboot.web.service.LoginService; +import com.in28minutes.springboot.web.service.TodoService; + +@Controller +@SessionAttributes("name") +public class TodoController { + + @Autowired + TodoService service; + + @RequestMapping(value = "/list-todos", method = RequestMethod.GET) + public String showTodos(ModelMap model) { + String name = (String) model.get("name"); + model.put("todos", service.retrieveTodos(name)); + return "list-todos"; + } + + @RequestMapping(value = "/add-todo", method = RequestMethod.GET) + public String showAddTodoPage(ModelMap model) { + model.addAttribute("todo", new Todo(0, (String) model.get("name"), "Default Desc", + new Date(), false)); + return "todo"; + } + + @RequestMapping(value = "/delete-todo", method = RequestMethod.GET) + public String deleteTodo(@RequestParam int id) { + service.deleteTodo(id); + return "redirect:/list-todos"; + } + + @RequestMapping(value = "/add-todo", method = RequestMethod.POST) + public String addTodo(ModelMap model, @Valid Todo todo, BindingResult result) { + + if(result.hasErrors()){ + return "todo"; + } + + service.addTodo((String) model.get("name"), todo.getDesc(), new Date(), + false); + return "redirect:/list-todos"; + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/model/Todo.java + +```java +package com.in28minutes.springboot.web.model; + +import java.util.Date; + +import javax.validation.constraints.Size; + +public class Todo { + private int id; + private String user; + + @Size(min=10, message="Enter at least 10 Characters...") + private String desc; + + private Date targetDate; + private boolean isDone; + + public Todo() { + super(); + } + + public Todo(int id, String user, String desc, Date targetDate, + boolean isDone) { + super(); + this.id = id; + this.user = user; + this.desc = desc; + this.targetDate = targetDate; + this.isDone = isDone; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public Date getTargetDate() { + return targetDate; + } + + public void setTargetDate(Date targetDate) { + this.targetDate = targetDate; + } + + public boolean isDone() { + return isDone; + } + + public void setDone(boolean isDone) { + this.isDone = isDone; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Todo other = (Todo) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return String.format( + "Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]", id, + user, desc, targetDate, isDone); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/LoginService.java + +```java +package com.in28minutes.springboot.web.service; + +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +@Service +public class LoginService { + + public boolean validateUser(String userid, String password) { + // in28minutes, dummy + return userid.equalsIgnoreCase("in28minutes") + && password.equalsIgnoreCase("dummy"); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/TodoService.java + +```java +package com.in28minutes.springboot.web.service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.in28minutes.springboot.web.model.Todo; + +@Service +public class TodoService { + private static List todos = new ArrayList(); + private static int todoCount = 3; + + static { + todos.add(new Todo(1, "in28Minutes", "Learn Spring MVC", new Date(), + false)); + todos.add(new Todo(2, "in28Minutes", "Learn Struts", new Date(), false)); + todos.add(new Todo(3, "in28Minutes", "Learn Hibernate", new Date(), + false)); + } + + public List retrieveTodos(String user) { + List filteredTodos = new ArrayList(); + for (Todo todo : todos) { + if (todo.getUser().equals(user)) { + filteredTodos.add(todo); + } + } + return filteredTodos; + } + + public void addTodo(String name, String desc, Date targetDate, + boolean isDone) { + todos.add(new Todo(++todoCount, name, desc, targetDate, isDone)); + } + + public void deleteTodo(int id) { + Iterator iterator = todos.iterator(); + while (iterator.hasNext()) { + Todo todo = iterator.next(); + if (todo.getId() == id) { + iterator.remove(); + } + } + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java + +```java +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan("com.in28minutes.springboot.web") +public class SpringBootFirstWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootFirstWebApplication.class, args); + } +} +``` +--- +### src/main/resources/application.properties + +``` +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp +logging.level.org.springframework.web=INFO +``` +--- +### src/main/webapp/WEB-INF/jsp/list-todos.jsp + +``` +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> + + + + +Todo's for ${name} + + + + +
+ + + + + + + + + + + + + + + + + + + + +
Your todos are
DescriptionTarget DateIs it Done?
${todo.desc}${todo.targetDate}${todo.done}Delete
+ +
+ + + + + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/login.jsp + +``` + + + +First Web Application + + + + ${errorMessage} +
+ Name : + Password : + +
+ + + +``` +--- +### src/main/webapp/WEB-INF/jsp/todo.jsp + +``` +<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> + + + +First Web Application + + + + + +
+ +
+ Description + + +
+ + +
+
+ + + + + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/welcome.jsp + +``` + + + +First Web Application + + + + Welcome ${name}!! Click here to manage your todo's. + + + +``` +--- +### src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java + +```java +package com.in28minutes.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootFirstWebApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- +### todo.txt + +``` +Implementing Server Side Validation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Command Bean or Form Backing Bean + +Add Validation +Use Validation on Controller +Display Errors in View + +Command Bean +~~~~~~~~~~~~ +Controller +View - Spring Form Tags + + + +LoginController -> adds name to model +welcome.jsp -> shows ${name} + +TodoController -> redirects to list-todos.jsp +${name} is empty + + +Component, Service, Repository, Controller +Autowired +ComponentScan + +Field dummyService in com.in28minutes.springboot.web.controller.LoginController +required a bean of type 'com.in28minutes.dummy.DummyService' +that could not be found. + +Spring Boot Starter Parent +Spring Boot Starter Web +@SpringBootApplication +Auto Configuration + +Dispatcher Servlet + +/login => "login" + +"login" => src/main/webapp/WEB-INF/jsp/login.jsp + + +Search for a view named "login" + + + +/login => LoginController +``` +--- diff --git a/02.Spring-Boot-Web-Application/Step18.zip b/02.Spring-Boot-Web-Application/Step18.zip new file mode 100644 index 0000000..dcbe6af Binary files /dev/null and b/02.Spring-Boot-Web-Application/Step18.zip differ diff --git a/02.Spring-Boot-Web-Application/Step19.md b/02.Spring-Boot-Web-Application/Step19.md new file mode 100644 index 0000000..88bc25e --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step19.md @@ -0,0 +1,665 @@ +## What we will do: +- Add Update Functionality +- Lets Use the Same JSP as earlier. + +## Useful Snippets +``` + public Todo retrieveTodo(int id) { + for (Todo todo : todos) { + if (todo.getId() == id) + return todo; + } + return null; + } + + public void updateTodo(Todo todo) { + todos.remove(todo); + todos.add(todo); + } + + todo.setUser("in28Minutes"); //TODO:Remove Hardcoding Later + service.updateTodo(todo); + + +``` + +## Files List + +### pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + javax.servlet + jstl + + + + org.webjars + bootstrap + 3.3.6 + + + + org.webjars + jquery + 1.9.1 + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/LoginController.java + +```java +package com.in28minutes.springboot.web.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.service.LoginService; + +@Controller +@SessionAttributes("name") +public class LoginController { + + @Autowired + LoginService service; + + @RequestMapping(value="/login", method = RequestMethod.GET) + public String showLoginPage(ModelMap model){ + return "login"; + } + + @RequestMapping(value="/login", method = RequestMethod.POST) + public String showWelcomePage(ModelMap model, @RequestParam String name, @RequestParam String password){ + + boolean isValidUser = service.validateUser(name, password); + + if (!isValidUser) { + model.put("errorMessage", "Invalid Credentials"); + return "login"; + } + + model.put("name", name); + model.put("password", password); + + return "welcome"; + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/TodoController.java + +```java +package com.in28minutes.springboot.web.controller; + +import java.util.Date; + +import javax.validation.Valid; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.model.Todo; +import com.in28minutes.springboot.web.service.LoginService; +import com.in28minutes.springboot.web.service.TodoService; + +@Controller +@SessionAttributes("name") +public class TodoController { + + @Autowired + TodoService service; + + @RequestMapping(value = "/list-todos", method = RequestMethod.GET) + public String showTodos(ModelMap model) { + String name = (String) model.get("name"); + model.put("todos", service.retrieveTodos(name)); + return "list-todos"; + } + + @RequestMapping(value = "/add-todo", method = RequestMethod.GET) + public String showAddTodoPage(ModelMap model) { + model.addAttribute("todo", new Todo(0, (String) model.get("name"), + "Default Desc", new Date(), false)); + return "todo"; + } + + @RequestMapping(value = "/delete-todo", method = RequestMethod.GET) + public String deleteTodo(@RequestParam int id) { + service.deleteTodo(id); + return "redirect:/list-todos"; + } + + @RequestMapping(value = "/update-todo", method = RequestMethod.GET) + public String showUpdateTodoPage(@RequestParam int id, ModelMap model) { + Todo todo = service.retrieveTodo(id); + model.put("todo", todo); + return "todo"; + } + + @RequestMapping(value = "/update-todo", method = RequestMethod.POST) + public String updateTodo(ModelMap model, @Valid Todo todo, BindingResult result) { + + if (result.hasErrors()) { + return "todo"; + } + + todo.setUser((String) model.get("name")); + + service.updateTodo(todo); + + return "redirect:/list-todos"; + } + + @RequestMapping(value = "/add-todo", method = RequestMethod.POST) + public String addTodo(ModelMap model, @Valid Todo todo, BindingResult result) { + + if (result.hasErrors()) { + return "todo"; + } + + service.addTodo((String) model.get("name"), todo.getDesc(), new Date(), + false); + return "redirect:/list-todos"; + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/model/Todo.java + +```java +package com.in28minutes.springboot.web.model; + +import java.util.Date; + +import javax.validation.constraints.Size; + +public class Todo { + private int id; + private String user; + + @Size(min=10, message="Enter at least 10 Characters...") + private String desc; + + private Date targetDate; + private boolean isDone; + + public Todo() { + super(); + } + + public Todo(int id, String user, String desc, Date targetDate, + boolean isDone) { + super(); + this.id = id; + this.user = user; + this.desc = desc; + this.targetDate = targetDate; + this.isDone = isDone; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public Date getTargetDate() { + return targetDate; + } + + public void setTargetDate(Date targetDate) { + this.targetDate = targetDate; + } + + public boolean isDone() { + return isDone; + } + + public void setDone(boolean isDone) { + this.isDone = isDone; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Todo other = (Todo) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return String.format( + "Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]", id, + user, desc, targetDate, isDone); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/LoginService.java + +```java +package com.in28minutes.springboot.web.service; + +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +@Service +public class LoginService { + + public boolean validateUser(String userid, String password) { + // in28minutes, dummy + return userid.equalsIgnoreCase("in28minutes") + && password.equalsIgnoreCase("dummy"); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/TodoService.java + +```java +package com.in28minutes.springboot.web.service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.in28minutes.springboot.web.model.Todo; + +@Service +public class TodoService { + private static List todos = new ArrayList(); + private static int todoCount = 3; + + static { + todos.add(new Todo(1, "in28Minutes", "Learn Spring MVC", new Date(), + false)); + todos.add(new Todo(2, "in28Minutes", "Learn Struts", new Date(), false)); + todos.add(new Todo(3, "in28Minutes", "Learn Hibernate", new Date(), + false)); + } + + public List retrieveTodos(String user) { + List filteredTodos = new ArrayList(); + for (Todo todo : todos) { + if (todo.getUser().equalsIgnoreCase(user)) { + filteredTodos.add(todo); + } + } + return filteredTodos; + } + + public Todo retrieveTodo(int id) { + for (Todo todo : todos) { + if (todo.getId()==id) { + return todo; + } + } + return null; + } + + public void updateTodo(Todo todo){ + todos.remove(todo); + todos.add(todo); + } + + public void addTodo(String name, String desc, Date targetDate, + boolean isDone) { + todos.add(new Todo(++todoCount, name, desc, targetDate, isDone)); + } + + public void deleteTodo(int id) { + Iterator iterator = todos.iterator(); + while (iterator.hasNext()) { + Todo todo = iterator.next(); + if (todo.getId() == id) { + iterator.remove(); + } + } + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java + +```java +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan("com.in28minutes.springboot.web") +public class SpringBootFirstWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootFirstWebApplication.class, args); + } +} +``` +--- +### src/main/resources/application.properties + +``` +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp +logging.level.org.springframework.web=INFO +``` +--- +### src/main/webapp/WEB-INF/jsp/list-todos.jsp + +``` +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> + + + + +Todo's for ${name} + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
Your todos are
DescriptionTarget DateIs it Done?
${todo.desc}${todo.targetDate}${todo.done}UpdateDelete
+ +
+ + + + + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/login.jsp + +``` + + + +First Web Application + + + + ${errorMessage} +
+ Name : + Password : + +
+ + + +``` +--- +### src/main/webapp/WEB-INF/jsp/todo.jsp + +``` +<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> + + + +First Web Application + + + + + +
+ + + + +
+ Description + + +
+ + +
+
+ + + + + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/welcome.jsp + +``` + + + +First Web Application + + + + Welcome ${name}!! Click here to manage your todo's. + + + +``` +--- +### src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java + +```java +package com.in28minutes.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootFirstWebApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- +### todo.txt + +``` +Implementing Server Side Validation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Command Bean or Form Backing Bean + +Add Validation +Use Validation on Controller +Display Errors in View + +Command Bean +~~~~~~~~~~~~ +Controller +View - Spring Form Tags + + + +LoginController -> adds name to model +welcome.jsp -> shows ${name} + +TodoController -> redirects to list-todos.jsp +${name} is empty + + +Component, Service, Repository, Controller +Autowired +ComponentScan + +Field dummyService in com.in28minutes.springboot.web.controller.LoginController +required a bean of type 'com.in28minutes.dummy.DummyService' +that could not be found. + +Spring Boot Starter Parent +Spring Boot Starter Web +@SpringBootApplication +Auto Configuration + +Dispatcher Servlet + +/login => "login" + +"login" => src/main/webapp/WEB-INF/jsp/login.jsp + + +Search for a view named "login" + + + +/login => LoginController +``` +--- diff --git a/02.Spring-Boot-Web-Application/Step19.zip b/02.Spring-Boot-Web-Application/Step19.zip new file mode 100644 index 0000000..d3b9fdf Binary files /dev/null and b/02.Spring-Boot-Web-Application/Step19.zip differ diff --git a/02.Spring-Boot-Web-Application/Step20.md b/02.Spring-Boot-Web-Application/Step20.md new file mode 100644 index 0000000..2edcde3 --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step20.md @@ -0,0 +1,708 @@ +## What we will do: +- Make real use of the Target Date Field +- initBinder method + +## Useful Snippets +``` + <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> + + + + @InitBinder + protected void initBinder(WebDataBinder binder) { + SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy"); + binder.registerCustomEditor(Date.class, new CustomDateEditor( + dateFormat, false)); + } + + + org.webjars + bootstrap-datepicker + 1.0.1 + + + + + + +``` + +## Files List + +### pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + javax.servlet + jstl + + + + org.webjars + bootstrap + 3.3.6 + + + + org.webjars + bootstrap-datepicker + 1.0.1 + + + + org.webjars + jquery + 1.9.1 + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/LoginController.java + +```java +package com.in28minutes.springboot.web.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.service.LoginService; + +@Controller +@SessionAttributes("name") +public class LoginController { + + @Autowired + LoginService service; + + @RequestMapping(value="/login", method = RequestMethod.GET) + public String showLoginPage(ModelMap model){ + return "login"; + } + + @RequestMapping(value="/login", method = RequestMethod.POST) + public String showWelcomePage(ModelMap model, @RequestParam String name, @RequestParam String password){ + + boolean isValidUser = service.validateUser(name, password); + + if (!isValidUser) { + model.put("errorMessage", "Invalid Credentials"); + return "login"; + } + + model.put("name", name); + model.put("password", password); + + return "welcome"; + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/TodoController.java + +```java +package com.in28minutes.springboot.web.controller; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.validation.Valid; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.propertyeditors.CustomDateEditor; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.model.Todo; +import com.in28minutes.springboot.web.service.LoginService; +import com.in28minutes.springboot.web.service.TodoService; + +@Controller +@SessionAttributes("name") +public class TodoController { + + @Autowired + TodoService service; + + @InitBinder + public void initBinder(WebDataBinder binder) { + // Date - dd/MM/yyyy + SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy"); + binder.registerCustomEditor(Date.class, new CustomDateEditor( + dateFormat, false)); + } + + @RequestMapping(value = "/list-todos", method = RequestMethod.GET) + public String showTodos(ModelMap model) { + String name = (String) model.get("name"); + model.put("todos", service.retrieveTodos(name)); + return "list-todos"; + } + + @RequestMapping(value = "/add-todo", method = RequestMethod.GET) + public String showAddTodoPage(ModelMap model) { + model.addAttribute("todo", new Todo(0, (String) model.get("name"), + "Default Desc", new Date(), false)); + return "todo"; + } + + @RequestMapping(value = "/delete-todo", method = RequestMethod.GET) + public String deleteTodo(@RequestParam int id) { + service.deleteTodo(id); + return "redirect:/list-todos"; + } + + @RequestMapping(value = "/update-todo", method = RequestMethod.GET) + public String showUpdateTodoPage(@RequestParam int id, ModelMap model) { + Todo todo = service.retrieveTodo(id); + model.put("todo", todo); + return "todo"; + } + + @RequestMapping(value = "/update-todo", method = RequestMethod.POST) + public String updateTodo(ModelMap model, @Valid Todo todo, + BindingResult result) { + + if (result.hasErrors()) { + return "todo"; + } + + todo.setUser((String) model.get("name")); + + service.updateTodo(todo); + + return "redirect:/list-todos"; + } + + @RequestMapping(value = "/add-todo", method = RequestMethod.POST) + public String addTodo(ModelMap model, @Valid Todo todo, BindingResult result) { + + if (result.hasErrors()) { + return "todo"; + } + + service.addTodo((String) model.get("name"), todo.getDesc(), todo.getTargetDate(), + false); + return "redirect:/list-todos"; + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/model/Todo.java + +```java +package com.in28minutes.springboot.web.model; + +import java.util.Date; + +import javax.validation.constraints.Size; + +public class Todo { + private int id; + private String user; + + @Size(min=10, message="Enter at least 10 Characters...") + private String desc; + + private Date targetDate; + private boolean isDone; + + public Todo() { + super(); + } + + public Todo(int id, String user, String desc, Date targetDate, + boolean isDone) { + super(); + this.id = id; + this.user = user; + this.desc = desc; + this.targetDate = targetDate; + this.isDone = isDone; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public Date getTargetDate() { + return targetDate; + } + + public void setTargetDate(Date targetDate) { + this.targetDate = targetDate; + } + + public boolean isDone() { + return isDone; + } + + public void setDone(boolean isDone) { + this.isDone = isDone; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Todo other = (Todo) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return String.format( + "Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]", id, + user, desc, targetDate, isDone); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/LoginService.java + +```java +package com.in28minutes.springboot.web.service; + +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +@Service +public class LoginService { + + public boolean validateUser(String userid, String password) { + // in28minutes, dummy + return userid.equalsIgnoreCase("in28minutes") + && password.equalsIgnoreCase("dummy"); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/TodoService.java + +```java +package com.in28minutes.springboot.web.service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.in28minutes.springboot.web.model.Todo; + +@Service +public class TodoService { + private static List todos = new ArrayList(); + private static int todoCount = 3; + + static { + todos.add(new Todo(1, "in28Minutes", "Learn Spring MVC", new Date(), + false)); + todos.add(new Todo(2, "in28Minutes", "Learn Struts", new Date(), false)); + todos.add(new Todo(3, "in28Minutes", "Learn Hibernate", new Date(), + false)); + } + + public List retrieveTodos(String user) { + List filteredTodos = new ArrayList(); + for (Todo todo : todos) { + if (todo.getUser().equalsIgnoreCase(user)) { + filteredTodos.add(todo); + } + } + return filteredTodos; + } + + public Todo retrieveTodo(int id) { + for (Todo todo : todos) { + if (todo.getId()==id) { + return todo; + } + } + return null; + } + + public void updateTodo(Todo todo){ + todos.remove(todo); + todos.add(todo); + } + + public void addTodo(String name, String desc, Date targetDate, + boolean isDone) { + todos.add(new Todo(++todoCount, name, desc, targetDate, isDone)); + } + + public void deleteTodo(int id) { + Iterator iterator = todos.iterator(); + while (iterator.hasNext()) { + Todo todo = iterator.next(); + if (todo.getId() == id) { + iterator.remove(); + } + } + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java + +```java +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan("com.in28minutes.springboot.web") +public class SpringBootFirstWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootFirstWebApplication.class, args); + } +} +``` +--- +### src/main/resources/application.properties + +``` +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp +logging.level.org.springframework.web=INFO +``` +--- +### src/main/webapp/WEB-INF/jsp/list-todos.jsp + +``` +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> + + + + +Todo's for ${name} + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
Your todos are
DescriptionTarget DateIs it Done?
${todo.desc}${todo.done}UpdateDelete
+ +
+ + + + + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/login.jsp + +``` + + + +First Web Application + + + + ${errorMessage} +
+ Name : + Password : + +
+ + + +``` +--- +### src/main/webapp/WEB-INF/jsp/todo.jsp + +``` +<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> + + + +First Web Application + + + + + +
+ + + + +
+ Description + + +
+ +
+ Target Date + + +
+ + +
+
+ + + + + + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/welcome.jsp + +``` + + + +First Web Application + + + + Welcome ${name}!! Click here to manage your todo's. + + + +``` +--- +### src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java + +```java +package com.in28minutes.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootFirstWebApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- +### todo.txt + +``` +Implementing Server Side Validation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Command Bean or Form Backing Bean + +Add Validation +Use Validation on Controller +Display Errors in View + +Command Bean +~~~~~~~~~~~~ +Controller +View - Spring Form Tags + + + +LoginController -> adds name to model +welcome.jsp -> shows ${name} + +TodoController -> redirects to list-todos.jsp +${name} is empty + + +Component, Service, Repository, Controller +Autowired +ComponentScan + +Field dummyService in com.in28minutes.springboot.web.controller.LoginController +required a bean of type 'com.in28minutes.dummy.DummyService' +that could not be found. + +Spring Boot Starter Parent +Spring Boot Starter Web +@SpringBootApplication +Auto Configuration + +Dispatcher Servlet + +/login => "login" + +"login" => src/main/webapp/WEB-INF/jsp/login.jsp + + +Search for a view named "login" + + + +/login => LoginController +``` +--- diff --git a/02.Spring-Boot-Web-Application/Step20.zip b/02.Spring-Boot-Web-Application/Step20.zip new file mode 100644 index 0000000..15de171 Binary files /dev/null and b/02.Spring-Boot-Web-Application/Step20.zip differ diff --git a/02.Spring-Boot-Web-Application/Step21.md b/02.Spring-Boot-Web-Application/Step21.md new file mode 100644 index 0000000..47f82cc --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step21.md @@ -0,0 +1,702 @@ +## What we will do: +- Add a navigation bar +- Use JSP Fragments +- Exercise : Align the login & welcome pages. +- Exercise : Highlight the correct menu item. + +## Useful Snippets +``` + +``` + +## Files List + +### pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + javax.servlet + jstl + + + + org.webjars + bootstrap + 3.3.6 + + + + org.webjars + bootstrap-datepicker + 1.0.1 + + + + org.webjars + jquery + 1.9.1 + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/LoginController.java + +```java +package com.in28minutes.springboot.web.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.service.LoginService; + +@Controller +@SessionAttributes("name") +public class LoginController { + + @Autowired + LoginService service; + + @RequestMapping(value="/login", method = RequestMethod.GET) + public String showLoginPage(ModelMap model){ + return "login"; + } + + @RequestMapping(value="/login", method = RequestMethod.POST) + public String showWelcomePage(ModelMap model, @RequestParam String name, @RequestParam String password){ + + boolean isValidUser = service.validateUser(name, password); + + if (!isValidUser) { + model.put("errorMessage", "Invalid Credentials"); + return "login"; + } + + model.put("name", name); + model.put("password", password); + + return "welcome"; + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/TodoController.java + +```java +package com.in28minutes.springboot.web.controller; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.validation.Valid; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.propertyeditors.CustomDateEditor; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.model.Todo; +import com.in28minutes.springboot.web.service.LoginService; +import com.in28minutes.springboot.web.service.TodoService; + +@Controller +@SessionAttributes("name") +public class TodoController { + + @Autowired + TodoService service; + + @InitBinder + public void initBinder(WebDataBinder binder) { + // Date - dd/MM/yyyy + SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy"); + binder.registerCustomEditor(Date.class, new CustomDateEditor( + dateFormat, false)); + } + + @RequestMapping(value = "/list-todos", method = RequestMethod.GET) + public String showTodos(ModelMap model) { + String name = (String) model.get("name"); + model.put("todos", service.retrieveTodos(name)); + return "list-todos"; + } + + @RequestMapping(value = "/add-todo", method = RequestMethod.GET) + public String showAddTodoPage(ModelMap model) { + model.addAttribute("todo", new Todo(0, (String) model.get("name"), + "Default Desc", new Date(), false)); + return "todo"; + } + + @RequestMapping(value = "/delete-todo", method = RequestMethod.GET) + public String deleteTodo(@RequestParam int id) { + service.deleteTodo(id); + return "redirect:/list-todos"; + } + + @RequestMapping(value = "/update-todo", method = RequestMethod.GET) + public String showUpdateTodoPage(@RequestParam int id, ModelMap model) { + Todo todo = service.retrieveTodo(id); + model.put("todo", todo); + return "todo"; + } + + @RequestMapping(value = "/update-todo", method = RequestMethod.POST) + public String updateTodo(ModelMap model, @Valid Todo todo, + BindingResult result) { + + if (result.hasErrors()) { + return "todo"; + } + + todo.setUser((String) model.get("name")); + + service.updateTodo(todo); + + return "redirect:/list-todos"; + } + + @RequestMapping(value = "/add-todo", method = RequestMethod.POST) + public String addTodo(ModelMap model, @Valid Todo todo, BindingResult result) { + + if (result.hasErrors()) { + return "todo"; + } + + service.addTodo((String) model.get("name"), todo.getDesc(), todo.getTargetDate(), + false); + return "redirect:/list-todos"; + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/model/Todo.java + +```java +package com.in28minutes.springboot.web.model; + +import java.util.Date; + +import javax.validation.constraints.Size; + +public class Todo { + private int id; + private String user; + + @Size(min=10, message="Enter at least 10 Characters...") + private String desc; + + private Date targetDate; + private boolean isDone; + + public Todo() { + super(); + } + + public Todo(int id, String user, String desc, Date targetDate, + boolean isDone) { + super(); + this.id = id; + this.user = user; + this.desc = desc; + this.targetDate = targetDate; + this.isDone = isDone; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public Date getTargetDate() { + return targetDate; + } + + public void setTargetDate(Date targetDate) { + this.targetDate = targetDate; + } + + public boolean isDone() { + return isDone; + } + + public void setDone(boolean isDone) { + this.isDone = isDone; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Todo other = (Todo) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return String.format( + "Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]", id, + user, desc, targetDate, isDone); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/LoginService.java + +```java +package com.in28minutes.springboot.web.service; + +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +@Service +public class LoginService { + + public boolean validateUser(String userid, String password) { + // in28minutes, dummy + return userid.equalsIgnoreCase("in28minutes") + && password.equalsIgnoreCase("dummy"); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/TodoService.java + +```java +package com.in28minutes.springboot.web.service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.in28minutes.springboot.web.model.Todo; + +@Service +public class TodoService { + private static List todos = new ArrayList(); + private static int todoCount = 3; + + static { + todos.add(new Todo(1, "in28Minutes", "Learn Spring MVC", new Date(), + false)); + todos.add(new Todo(2, "in28Minutes", "Learn Struts", new Date(), false)); + todos.add(new Todo(3, "in28Minutes", "Learn Hibernate", new Date(), + false)); + } + + public List retrieveTodos(String user) { + List filteredTodos = new ArrayList(); + for (Todo todo : todos) { + if (todo.getUser().equalsIgnoreCase(user)) { + filteredTodos.add(todo); + } + } + return filteredTodos; + } + + public Todo retrieveTodo(int id) { + for (Todo todo : todos) { + if (todo.getId()==id) { + return todo; + } + } + return null; + } + + public void updateTodo(Todo todo){ + todos.remove(todo); + todos.add(todo); + } + + public void addTodo(String name, String desc, Date targetDate, + boolean isDone) { + todos.add(new Todo(++todoCount, name, desc, targetDate, isDone)); + } + + public void deleteTodo(int id) { + Iterator iterator = todos.iterator(); + while (iterator.hasNext()) { + Todo todo = iterator.next(); + if (todo.getId() == id) { + iterator.remove(); + } + } + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java + +```java +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan("com.in28minutes.springboot.web") +public class SpringBootFirstWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootFirstWebApplication.class, args); + } +} +``` +--- +### src/main/resources/application.properties + +``` +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp +logging.level.org.springframework.web=INFO +``` +--- +### src/main/webapp/WEB-INF/jsp/common/footer.jspf + +``` + + + + + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/common/header.jspf + +``` +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> +<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> + + + + +First Web Application + + + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/common/navigation.jspf + +``` + +``` +--- +### src/main/webapp/WEB-INF/jsp/list-todos.jsp + +``` +<%@ include file="common/header.jspf" %> +<%@ include file="common/navigation.jspf" %> + +
+ + + + + + + + + + + + + + + + + + + + + + +
Your todos are
DescriptionTarget DateIs it Done?
${todo.desc}${todo.done}UpdateDelete
+ +
+<%@ include file="common/footer.jspf" %> +``` +--- +### src/main/webapp/WEB-INF/jsp/login.jsp + +``` +<%@ include file="common/header.jspf" %> +<%@ include file="common/navigation.jspf" %> + +
+ + ${errorMessage} +
+ Name : + Password : + +
+
+<%@ include file="common/footer.jspf" %> +``` +--- +### src/main/webapp/WEB-INF/jsp/todo.jsp + +``` +<%@ include file="common/header.jspf" %> +<%@ include file="common/navigation.jspf" %> +
+ + +
+ Description + + +
+ +
+ Target Date + + +
+ + +
+
+<%@ include file="common/footer.jspf" %> +``` +--- +### src/main/webapp/WEB-INF/jsp/welcome.jsp + +``` +<%@ include file="common/header.jspf"%> +<%@ include file="common/navigation.jspf"%> +
+ Welcome ${name}!! Click here to manage your + todo's. +
+<%@ include file="common/footer.jspf"%> +``` +--- +### src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java + +```java +package com.in28minutes.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootFirstWebApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- +### todo.txt + +``` +Implementing Server Side Validation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Command Bean or Form Backing Bean + +Add Validation +Use Validation on Controller +Display Errors in View + +Command Bean +~~~~~~~~~~~~ +Controller +View - Spring Form Tags + + + +LoginController -> adds name to model +welcome.jsp -> shows ${name} + +TodoController -> redirects to list-todos.jsp +${name} is empty + + +Component, Service, Repository, Controller +Autowired +ComponentScan + +Field dummyService in com.in28minutes.springboot.web.controller.LoginController +required a bean of type 'com.in28minutes.dummy.DummyService' +that could not be found. + +Spring Boot Starter Parent +Spring Boot Starter Web +@SpringBootApplication +Auto Configuration + +Dispatcher Servlet + +/login => "login" + +"login" => src/main/webapp/WEB-INF/jsp/login.jsp + + +Search for a view named "login" + + + +/login => LoginController +``` +--- diff --git a/02.Spring-Boot-Web-Application/Step21.zip b/02.Spring-Boot-Web-Application/Step21.zip new file mode 100644 index 0000000..21e955f Binary files /dev/null and b/02.Spring-Boot-Web-Application/Step21.zip differ diff --git a/02.Spring-Boot-Web-Application/Step22.md b/02.Spring-Boot-Web-Application/Step22.md new file mode 100644 index 0000000..323aae9 --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step22.md @@ -0,0 +1,657 @@ +## What we will do: +- Prepare for Using Spring Security +- Remove All the Login Related Functionality +- Make Welcome the default page - with some hardcoding to start with. +- Refactor getLoggedInUserName +- Update Home Page Link in navigation + +## Files List + +### pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + javax.servlet + jstl + + + + org.webjars + bootstrap + 3.3.6 + + + + org.webjars + bootstrap-datepicker + 1.0.1 + + + + org.webjars + jquery + 1.9.1 + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/LoginController.java + +```java +package com.in28minutes.springboot.web.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.service.LoginService; + +@Controller +@SessionAttributes("name") +public class LoginController { + + @Autowired + LoginService service; + + @RequestMapping(value = "/", method = RequestMethod.GET) + public String showLoginPage(ModelMap model) { + model.put("name", "in28Minutes"); + return "welcome"; + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/TodoController.java + +```java +package com.in28minutes.springboot.web.controller; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.validation.Valid; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.propertyeditors.CustomDateEditor; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.model.Todo; +import com.in28minutes.springboot.web.service.LoginService; +import com.in28minutes.springboot.web.service.TodoService; + +@Controller +@SessionAttributes("name") +public class TodoController { + + @Autowired + TodoService service; + + @InitBinder + public void initBinder(WebDataBinder binder) { + // Date - dd/MM/yyyy + SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy"); + binder.registerCustomEditor(Date.class, new CustomDateEditor( + dateFormat, false)); + } + + @RequestMapping(value = "/list-todos", method = RequestMethod.GET) + public String showTodos(ModelMap model) { + String name = getLoggedInUserName(model); + model.put("todos", service.retrieveTodos(name)); + return "list-todos"; + } + + private String getLoggedInUserName(ModelMap model) { + return (String) model.get("name"); + } + + @RequestMapping(value = "/add-todo", method = RequestMethod.GET) + public String showAddTodoPage(ModelMap model) { + model.addAttribute("todo", new Todo(0, getLoggedInUserName(model), + "Default Desc", new Date(), false)); + return "todo"; + } + + @RequestMapping(value = "/delete-todo", method = RequestMethod.GET) + public String deleteTodo(@RequestParam int id) { + service.deleteTodo(id); + return "redirect:/list-todos"; + } + + @RequestMapping(value = "/update-todo", method = RequestMethod.GET) + public String showUpdateTodoPage(@RequestParam int id, ModelMap model) { + Todo todo = service.retrieveTodo(id); + model.put("todo", todo); + return "todo"; + } + + @RequestMapping(value = "/update-todo", method = RequestMethod.POST) + public String updateTodo(ModelMap model, @Valid Todo todo, + BindingResult result) { + + if (result.hasErrors()) { + return "todo"; + } + + todo.setUser(getLoggedInUserName(model)); + + service.updateTodo(todo); + + return "redirect:/list-todos"; + } + + @RequestMapping(value = "/add-todo", method = RequestMethod.POST) + public String addTodo(ModelMap model, @Valid Todo todo, BindingResult result) { + + if (result.hasErrors()) { + return "todo"; + } + + service.addTodo(getLoggedInUserName(model), todo.getDesc(), todo.getTargetDate(), + false); + return "redirect:/list-todos"; + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/model/Todo.java + +```java +package com.in28minutes.springboot.web.model; + +import java.util.Date; + +import javax.validation.constraints.Size; + +public class Todo { + private int id; + private String user; + + @Size(min=10, message="Enter at least 10 Characters...") + private String desc; + + private Date targetDate; + private boolean isDone; + + public Todo() { + super(); + } + + public Todo(int id, String user, String desc, Date targetDate, + boolean isDone) { + super(); + this.id = id; + this.user = user; + this.desc = desc; + this.targetDate = targetDate; + this.isDone = isDone; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public Date getTargetDate() { + return targetDate; + } + + public void setTargetDate(Date targetDate) { + this.targetDate = targetDate; + } + + public boolean isDone() { + return isDone; + } + + public void setDone(boolean isDone) { + this.isDone = isDone; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Todo other = (Todo) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return String.format( + "Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]", id, + user, desc, targetDate, isDone); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/LoginService.java + +```java +package com.in28minutes.springboot.web.service; + +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +@Service +public class LoginService { + + public boolean validateUser(String userid, String password) { + // in28minutes, dummy + return userid.equalsIgnoreCase("in28minutes") + && password.equalsIgnoreCase("dummy"); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/TodoService.java + +```java +package com.in28minutes.springboot.web.service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.in28minutes.springboot.web.model.Todo; + +@Service +public class TodoService { + private static List todos = new ArrayList(); + private static int todoCount = 3; + + static { + todos.add(new Todo(1, "in28Minutes", "Learn Spring MVC", new Date(), + false)); + todos.add(new Todo(2, "in28Minutes", "Learn Struts", new Date(), false)); + todos.add(new Todo(3, "in28Minutes", "Learn Hibernate", new Date(), + false)); + } + + public List retrieveTodos(String user) { + List filteredTodos = new ArrayList(); + for (Todo todo : todos) { + if (todo.getUser().equalsIgnoreCase(user)) { + filteredTodos.add(todo); + } + } + return filteredTodos; + } + + public Todo retrieveTodo(int id) { + for (Todo todo : todos) { + if (todo.getId()==id) { + return todo; + } + } + return null; + } + + public void updateTodo(Todo todo){ + todos.remove(todo); + todos.add(todo); + } + + public void addTodo(String name, String desc, Date targetDate, + boolean isDone) { + todos.add(new Todo(++todoCount, name, desc, targetDate, isDone)); + } + + public void deleteTodo(int id) { + Iterator iterator = todos.iterator(); + while (iterator.hasNext()) { + Todo todo = iterator.next(); + if (todo.getId() == id) { + iterator.remove(); + } + } + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java + +```java +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan("com.in28minutes.springboot.web") +public class SpringBootFirstWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootFirstWebApplication.class, args); + } +} +``` +--- +### src/main/resources/application.properties + +``` +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp +logging.level.org.springframework.web=INFO +``` +--- +### src/main/webapp/WEB-INF/jsp/common/footer.jspf + +``` + + + + + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/common/header.jspf + +``` +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> +<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> + + + + +First Web Application + + + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/common/navigation.jspf + +``` + +``` +--- +### src/main/webapp/WEB-INF/jsp/list-todos.jsp + +``` +<%@ include file="common/header.jspf" %> +<%@ include file="common/navigation.jspf" %> + +
+ + + + + + + + + + + + + + + + + + + + + + +
Your todos are
DescriptionTarget DateIs it Done?
${todo.desc}${todo.done}UpdateDelete
+ +
+<%@ include file="common/footer.jspf" %> +``` +--- +### src/main/webapp/WEB-INF/jsp/todo.jsp + +``` +<%@ include file="common/header.jspf" %> +<%@ include file="common/navigation.jspf" %> +
+ + +
+ Description + + +
+ +
+ Target Date + + +
+ + +
+
+<%@ include file="common/footer.jspf" %> +``` +--- +### src/main/webapp/WEB-INF/jsp/welcome.jsp + +``` +<%@ include file="common/header.jspf"%> +<%@ include file="common/navigation.jspf"%> +
+ Welcome ${name}!! Click here to manage your + todo's. +
+<%@ include file="common/footer.jspf"%> +``` +--- +### src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java + +```java +package com.in28minutes.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootFirstWebApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- +### todo.txt + +``` +Implementing Server Side Validation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Command Bean or Form Backing Bean + +Add Validation +Use Validation on Controller +Display Errors in View + +Command Bean +~~~~~~~~~~~~ +Controller +View - Spring Form Tags + + + +LoginController -> adds name to model +welcome.jsp -> shows ${name} + +TodoController -> redirects to list-todos.jsp +${name} is empty + + +Component, Service, Repository, Controller +Autowired +ComponentScan + +Field dummyService in com.in28minutes.springboot.web.controller.LoginController +required a bean of type 'com.in28minutes.dummy.DummyService' +that could not be found. + +Spring Boot Starter Parent +Spring Boot Starter Web +@SpringBootApplication +Auto Configuration + +Dispatcher Servlet + +/login => "login" + +"login" => src/main/webapp/WEB-INF/jsp/login.jsp + + +Search for a view named "login" + + + +/login => LoginController +``` +--- diff --git a/02.Spring-Boot-Web-Application/Step22.zip b/02.Spring-Boot-Web-Application/Step22.zip new file mode 100644 index 0000000..0cd9766 Binary files /dev/null and b/02.Spring-Boot-Web-Application/Step22.zip differ diff --git a/02.Spring-Boot-Web-Application/Step23.md b/02.Spring-Boot-Web-Application/Step23.md new file mode 100644 index 0000000..0a78acd --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step23.md @@ -0,0 +1,739 @@ +## What we will do: +- Get Setup for Spring Security + + +## Useful Snippets +``` + + org.springframework.boot + spring-boot-starter-security + + +package com.in28minutes.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +@EnableWebSecurity +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Autowired + public void configureGlobalSecurity(AuthenticationManagerBuilder auth) + throws Exception { + auth.inMemoryAuthentication().withUser("in28Minutes").password("dummy") + .roles("USER", "ADMIN"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests().antMatchers("/login").permitAll() + .antMatchers("/", "/*todo*/**").access("hasRole('USER')").and() + .formLogin(); + } +} + +``` + +Not Needed anymore with Spring Boot Auto Configuration +``` + + springSecurityFilterChain + org.springframework.web.filter.DelegatingFilterProxy + + + + springSecurityFilterChain + /* + +``` + +## Files List + +### pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + javax.servlet + jstl + + + + org.webjars + bootstrap + 3.3.6 + + + + org.webjars + bootstrap-datepicker + 1.0.1 + + + + org.webjars + jquery + 1.9.1 + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/LoginController.java + +```java +package com.in28minutes.springboot.web.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.service.LoginService; + +@Controller +@SessionAttributes("name") +public class LoginController { + + @Autowired + LoginService service; + + @RequestMapping(value = "/", method = RequestMethod.GET) + public String showLoginPage(ModelMap model) { + model.put("name", "in28Minutes"); + return "welcome"; + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/TodoController.java + +```java +package com.in28minutes.springboot.web.controller; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.validation.Valid; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.propertyeditors.CustomDateEditor; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.SessionAttributes; + +import com.in28minutes.springboot.web.model.Todo; +import com.in28minutes.springboot.web.service.LoginService; +import com.in28minutes.springboot.web.service.TodoService; + +@Controller +@SessionAttributes("name") +public class TodoController { + + @Autowired + TodoService service; + + @InitBinder + public void initBinder(WebDataBinder binder) { + // Date - dd/MM/yyyy + SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy"); + binder.registerCustomEditor(Date.class, new CustomDateEditor( + dateFormat, false)); + } + + @RequestMapping(value = "/list-todos", method = RequestMethod.GET) + public String showTodos(ModelMap model) { + String name = getLoggedInUserName(model); + model.put("todos", service.retrieveTodos(name)); + return "list-todos"; + } + + private String getLoggedInUserName(ModelMap model) { + return (String) model.get("name"); + } + + @RequestMapping(value = "/add-todo", method = RequestMethod.GET) + public String showAddTodoPage(ModelMap model) { + model.addAttribute("todo", new Todo(0, getLoggedInUserName(model), + "Default Desc", new Date(), false)); + return "todo"; + } + + @RequestMapping(value = "/delete-todo", method = RequestMethod.GET) + public String deleteTodo(@RequestParam int id) { + service.deleteTodo(id); + return "redirect:/list-todos"; + } + + @RequestMapping(value = "/update-todo", method = RequestMethod.GET) + public String showUpdateTodoPage(@RequestParam int id, ModelMap model) { + Todo todo = service.retrieveTodo(id); + model.put("todo", todo); + return "todo"; + } + + @RequestMapping(value = "/update-todo", method = RequestMethod.POST) + public String updateTodo(ModelMap model, @Valid Todo todo, + BindingResult result) { + + if (result.hasErrors()) { + return "todo"; + } + + todo.setUser(getLoggedInUserName(model)); + + service.updateTodo(todo); + + return "redirect:/list-todos"; + } + + @RequestMapping(value = "/add-todo", method = RequestMethod.POST) + public String addTodo(ModelMap model, @Valid Todo todo, BindingResult result) { + + if (result.hasErrors()) { + return "todo"; + } + + service.addTodo(getLoggedInUserName(model), todo.getDesc(), todo.getTargetDate(), + false); + return "redirect:/list-todos"; + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/model/Todo.java + +```java +package com.in28minutes.springboot.web.model; + +import java.util.Date; + +import javax.validation.constraints.Size; + +public class Todo { + private int id; + private String user; + + @Size(min=10, message="Enter at least 10 Characters...") + private String desc; + + private Date targetDate; + private boolean isDone; + + public Todo() { + super(); + } + + public Todo(int id, String user, String desc, Date targetDate, + boolean isDone) { + super(); + this.id = id; + this.user = user; + this.desc = desc; + this.targetDate = targetDate; + this.isDone = isDone; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public Date getTargetDate() { + return targetDate; + } + + public void setTargetDate(Date targetDate) { + this.targetDate = targetDate; + } + + public boolean isDone() { + return isDone; + } + + public void setDone(boolean isDone) { + this.isDone = isDone; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Todo other = (Todo) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return String.format( + "Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]", id, + user, desc, targetDate, isDone); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/security/SecurityConfiguration.java + +```java +package com.in28minutes.springboot.web.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +public class SecurityConfiguration extends WebSecurityConfigurerAdapter{ + //Create User - in28Minutes/dummy + @Autowired + public void configureGlobalSecurity(AuthenticationManagerBuilder auth) + throws Exception { + auth.inMemoryAuthentication().withUser("in28Minutes").password("dummy") + .roles("USER", "ADMIN"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests().antMatchers("/login").permitAll() + .antMatchers("/", "/*todo*/**").access("hasRole('USER')").and() + .formLogin(); + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/LoginService.java + +```java +package com.in28minutes.springboot.web.service; + +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +@Service +public class LoginService { + + public boolean validateUser(String userid, String password) { + // in28minutes, dummy + return userid.equalsIgnoreCase("in28minutes") + && password.equalsIgnoreCase("dummy"); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/TodoService.java + +```java +package com.in28minutes.springboot.web.service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.in28minutes.springboot.web.model.Todo; + +@Service +public class TodoService { + private static List todos = new ArrayList(); + private static int todoCount = 3; + + static { + todos.add(new Todo(1, "in28Minutes", "Learn Spring MVC", new Date(), + false)); + todos.add(new Todo(2, "in28Minutes", "Learn Struts", new Date(), false)); + todos.add(new Todo(3, "in28Minutes", "Learn Hibernate", new Date(), + false)); + } + + public List retrieveTodos(String user) { + List filteredTodos = new ArrayList(); + for (Todo todo : todos) { + if (todo.getUser().equalsIgnoreCase(user)) { + filteredTodos.add(todo); + } + } + return filteredTodos; + } + + public Todo retrieveTodo(int id) { + for (Todo todo : todos) { + if (todo.getId()==id) { + return todo; + } + } + return null; + } + + public void updateTodo(Todo todo){ + todos.remove(todo); + todos.add(todo); + } + + public void addTodo(String name, String desc, Date targetDate, + boolean isDone) { + todos.add(new Todo(++todoCount, name, desc, targetDate, isDone)); + } + + public void deleteTodo(int id) { + Iterator iterator = todos.iterator(); + while (iterator.hasNext()) { + Todo todo = iterator.next(); + if (todo.getId() == id) { + iterator.remove(); + } + } + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java + +```java +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan("com.in28minutes.springboot.web") +public class SpringBootFirstWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootFirstWebApplication.class, args); + } +} +``` +--- +### src/main/resources/application.properties + +``` +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp +logging.level.org.springframework.web=INFO +``` +--- +### src/main/webapp/WEB-INF/jsp/common/footer.jspf + +``` + + + + + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/common/header.jspf + +``` +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> +<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> + + + + +First Web Application + + + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/common/navigation.jspf + +``` + +``` +--- +### src/main/webapp/WEB-INF/jsp/list-todos.jsp + +``` +<%@ include file="common/header.jspf" %> +<%@ include file="common/navigation.jspf" %> + +
+ + + + + + + + + + + + + + + + + + + + + + +
Your todos are
DescriptionTarget DateIs it Done?
${todo.desc}${todo.done}UpdateDelete
+ +
+<%@ include file="common/footer.jspf" %> +``` +--- +### src/main/webapp/WEB-INF/jsp/todo.jsp + +``` +<%@ include file="common/header.jspf" %> +<%@ include file="common/navigation.jspf" %> +
+ + +
+ Description + + +
+ +
+ Target Date + + +
+ + +
+
+<%@ include file="common/footer.jspf" %> +``` +--- +### src/main/webapp/WEB-INF/jsp/welcome.jsp + +``` +<%@ include file="common/header.jspf"%> +<%@ include file="common/navigation.jspf"%> +
+ Welcome ${name}!! Click here to manage your + todo's. +
+<%@ include file="common/footer.jspf"%> +``` +--- +### src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java + +```java +package com.in28minutes.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootFirstWebApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- +### todo.txt + +``` +Implementing Server Side Validation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Command Bean or Form Backing Bean + +Add Validation +Use Validation on Controller +Display Errors in View + +Command Bean +~~~~~~~~~~~~ +Controller +View - Spring Form Tags + + + +LoginController -> adds name to model +welcome.jsp -> shows ${name} + +TodoController -> redirects to list-todos.jsp +${name} is empty + + +Component, Service, Repository, Controller +Autowired +ComponentScan + +Field dummyService in com.in28minutes.springboot.web.controller.LoginController +required a bean of type 'com.in28minutes.dummy.DummyService' +that could not be found. + +Spring Boot Starter Parent +Spring Boot Starter Web +@SpringBootApplication +Auto Configuration + +Dispatcher Servlet + +/login => "login" + +"login" => src/main/webapp/WEB-INF/jsp/login.jsp + + +Search for a view named "login" + + + +/login => LoginController +``` +--- diff --git a/02.Spring-Boot-Web-Application/Step23.zip b/02.Spring-Boot-Web-Application/Step23.zip new file mode 100644 index 0000000..bdeb466 Binary files /dev/null and b/02.Spring-Boot-Web-Application/Step23.zip differ diff --git a/02.Spring-Boot-Web-Application/Step24.md b/02.Spring-Boot-Web-Application/Step24.md new file mode 100644 index 0000000..e3b786a --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step24.md @@ -0,0 +1,34 @@ +## What we will do: +- Remove Hardcoding of User Name +- Remove LoginService +- Rename LoginController to WelcomeController +- Add Logout Functionality + +## Useful Snippets +``` + private String getLoggedInUserName(ModelMap model) { + Object principal = SecurityContextHolder.getContext() + .getAuthentication().getPrincipal(); + + if (principal instanceof UserDetails) + return ((UserDetails) principal).getUsername(); + + return principal.toString(); + } + + + + @RequestMapping(value = "/logout", method = RequestMethod.GET) + public String logout(HttpServletRequest request, + HttpServletResponse response) { + Authentication auth = SecurityContextHolder.getContext() + .getAuthentication(); + if (auth != null) { + new SecurityContextLogoutHandler().logout(request, response, auth); + } + return "redirect:/"; + } + +``` diff --git a/02.Spring-Boot-Web-Application/Step25.md b/02.Spring-Boot-Web-Application/Step25.md new file mode 100644 index 0000000..93b8dd3 --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step25.md @@ -0,0 +1,795 @@ +## What we will do: +- Basic Exception Handling +- Exception Handling is a cross cutting concern +- Do not handle exceptions in Controllers or Services, if you cannot add value to them. +- Bit of refactoring on the controllers +- Whitelabel Error Page provided by default by Spring Boot + - You can see a few details of the errors +- We can customize if we would want to +- @ControllerAdvice and Controller Specific Exception Handling +- Handling Errors thrown from Views + +## Useful Snippets +``` +@Controller("error") +public class ExceptionController { + + private Log logger = LogFactory.getLog(ExceptionController.class); + + @ExceptionHandler(Exception.class) + public ModelAndView handleError(HttpServletRequest req, Exception ex) { + logger.error("Request: " + req.getRequestURL() + " raised " + ex); + + ModelAndView mav = new ModelAndView(); + mav.addObject("exception", ex); + mav.addObject("url", req.getRequestURL()); + mav.setViewName("error"); + return mav; + } +} + +``` + +## Files List + +### pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + javax.servlet + jstl + + + + org.webjars + bootstrap + 3.3.6 + + + + org.webjars + bootstrap-datepicker + 1.0.1 + + + + org.webjars + jquery + 1.9.1 + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/ErrorController.java + +```java +package com.in28minutes.springboot.web.controller; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.servlet.ModelAndView; + +@Controller("error") +public class ErrorController { + + @ExceptionHandler(Exception.class) + public ModelAndView handleException + (HttpServletRequest request, Exception ex){ + ModelAndView mv = new ModelAndView(); + + mv.addObject("exception", ex.getLocalizedMessage()); + mv.addObject("url", request.getRequestURL()); + + mv.setViewName("error"); + return mv; + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/LogoutController.java + +```java +package com.in28minutes.springboot.web.controller; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@Controller +public class LogoutController { + + @RequestMapping(value = "/logout", method = RequestMethod.GET) + public String logout(HttpServletRequest request, + HttpServletResponse response) { + + Authentication authentication = SecurityContextHolder.getContext() + .getAuthentication(); + + if (authentication != null) { + new SecurityContextLogoutHandler().logout(request, response, + authentication); + } + + return "redirect:/"; + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/TodoController.java + +```java +package com.in28minutes.springboot.web.controller; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.validation.Valid; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.propertyeditors.CustomDateEditor; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import com.in28minutes.springboot.web.model.Todo; +import com.in28minutes.springboot.web.service.TodoService; + +@Controller +public class TodoController { + + @Autowired + TodoService service; + + @InitBinder + public void initBinder(WebDataBinder binder) { + // Date - dd/MM/yyyy + SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy"); + binder.registerCustomEditor(Date.class, new CustomDateEditor( + dateFormat, false)); + } + + @RequestMapping(value = "/list-todos", method = RequestMethod.GET) + public String showTodos(ModelMap model) { + String name = getLoggedInUserName(model); + model.put("todos", service.retrieveTodos(name)); + return "list-todos"; + } + + private String getLoggedInUserName(ModelMap model) { + Object principal = SecurityContextHolder.getContext() + .getAuthentication().getPrincipal(); + + if (principal instanceof UserDetails) { + return ((UserDetails) principal).getUsername(); + } + + return principal.toString(); + } + + @RequestMapping(value = "/add-todo", method = RequestMethod.GET) + public String showAddTodoPage(ModelMap model) { + model.addAttribute("todo", new Todo(0, getLoggedInUserName(model), + "Default Desc", new Date(), false)); + return "todo"; + } + + @RequestMapping(value = "/delete-todo", method = RequestMethod.GET) + public String deleteTodo(@RequestParam int id) { + + if(id==1) + throw new RuntimeException("Something went wrong"); + + service.deleteTodo(id); + return "redirect:/list-todos"; + } + + @RequestMapping(value = "/update-todo", method = RequestMethod.GET) + public String showUpdateTodoPage(@RequestParam int id, ModelMap model) { + Todo todo = service.retrieveTodo(id); + model.put("todo", todo); + return "todo"; + } + + @RequestMapping(value = "/update-todo", method = RequestMethod.POST) + public String updateTodo(ModelMap model, @Valid Todo todo, + BindingResult result) { + + if (result.hasErrors()) { + return "todo"; + } + + todo.setUser(getLoggedInUserName(model)); + + service.updateTodo(todo); + + return "redirect:/list-todos"; + } + + @RequestMapping(value = "/add-todo", method = RequestMethod.POST) + public String addTodo(ModelMap model, @Valid Todo todo, BindingResult result) { + + if (result.hasErrors()) { + return "todo"; + } + + service.addTodo(getLoggedInUserName(model), todo.getDesc(), todo.getTargetDate(), + false); + return "redirect:/list-todos"; + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/controller/WelcomeController.java + +```java +package com.in28minutes.springboot.web.controller; + +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@Controller +public class WelcomeController { + + @RequestMapping(value = "/", method = RequestMethod.GET) + public String showWelcomePage(ModelMap model) { + model.put("name", getLoggedinUserName()); + return "welcome"; + } + + private String getLoggedinUserName() { + Object principal = SecurityContextHolder.getContext() + .getAuthentication().getPrincipal(); + + if (principal instanceof UserDetails) { + return ((UserDetails) principal).getUsername(); + } + + return principal.toString(); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/model/Todo.java + +```java +package com.in28minutes.springboot.web.model; + +import java.util.Date; + +import javax.validation.constraints.Size; + +public class Todo { + private int id; + private String user; + + @Size(min=10, message="Enter at least 10 Characters...") + private String desc; + + private Date targetDate; + private boolean isDone; + + public Todo() { + super(); + } + + public Todo(int id, String user, String desc, Date targetDate, + boolean isDone) { + super(); + this.id = id; + this.user = user; + this.desc = desc; + this.targetDate = targetDate; + this.isDone = isDone; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public Date getTargetDate() { + return targetDate; + } + + public void setTargetDate(Date targetDate) { + this.targetDate = targetDate; + } + + public boolean isDone() { + return isDone; + } + + public void setDone(boolean isDone) { + this.isDone = isDone; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Todo other = (Todo) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return String.format( + "Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]", id, + user, desc, targetDate, isDone); + } + +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/security/SecurityConfiguration.java + +```java +package com.in28minutes.springboot.web.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +public class SecurityConfiguration extends WebSecurityConfigurerAdapter{ + //Create User - in28Minutes/dummy + @Autowired + public void configureGlobalSecurity(AuthenticationManagerBuilder auth) + throws Exception { + auth.inMemoryAuthentication().withUser("in28Minutes").password("dummy") + .roles("USER", "ADMIN"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests().antMatchers("/login").permitAll() + .antMatchers("/", "/*todo*/**").access("hasRole('USER')").and() + .formLogin(); + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/service/TodoService.java + +```java +package com.in28minutes.springboot.web.service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.in28minutes.springboot.web.model.Todo; + +@Service +public class TodoService { + private static List todos = new ArrayList(); + private static int todoCount = 3; + + static { + todos.add(new Todo(1, "in28Minutes", "Learn Spring MVC", new Date(), + false)); + todos.add(new Todo(2, "in28Minutes", "Learn Struts", new Date(), false)); + todos.add(new Todo(3, "in28Minutes", "Learn Hibernate", new Date(), + false)); + } + + public List retrieveTodos(String user) { + List filteredTodos = new ArrayList(); + for (Todo todo : todos) { + if (todo.getUser().equalsIgnoreCase(user)) { + filteredTodos.add(todo); + } + } + return filteredTodos; + } + + public Todo retrieveTodo(int id) { + for (Todo todo : todos) { + if (todo.getId()==id) { + return todo; + } + } + return null; + } + + public void updateTodo(Todo todo){ + todos.remove(todo); + todos.add(todo); + } + + public void addTodo(String name, String desc, Date targetDate, + boolean isDone) { + todos.add(new Todo(++todoCount, name, desc, targetDate, isDone)); + } + + public void deleteTodo(int id) { + Iterator iterator = todos.iterator(); + while (iterator.hasNext()) { + Todo todo = iterator.next(); + if (todo.getId() == id) { + iterator.remove(); + } + } + } +} +``` +--- +### src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java + +```java +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan("com.in28minutes.springboot.web") +public class SpringBootFirstWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootFirstWebApplication.class, args); + } +} +``` +--- +### src/main/resources/application.properties + +``` +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp +logging.level.org.springframework.web=INFO +``` +--- +### src/main/webapp/WEB-INF/jsp/common/footer.jspf + +``` + + + + + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/common/header.jspf + +``` +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> +<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> + + + + +First Web Application + + + + + +``` +--- +### src/main/webapp/WEB-INF/jsp/common/navigation.jspf + +``` + + +``` +--- +### src/main/webapp/WEB-INF/jsp/error.jsp + +``` +<%@ include file="common/header.jspf"%> +<%@ include file="common/navigation.jspf"%> +
+An exception occurred! Please contact Support! +
+<%@ include file="common/footer.jspf"%> +``` +--- +### src/main/webapp/WEB-INF/jsp/list-todos.jsp + +``` +<%@ include file="common/header.jspf" %> +<%@ include file="common/navigation.jspf" %> + +
+ + + + + + + + + + + + + + + + + + + + + + +
Your todos are
DescriptionTarget DateIs it Done?
${todo.desc}${todo.done}UpdateDelete
+ +
+<%@ include file="common/footer.jspf" %> +``` +--- +### src/main/webapp/WEB-INF/jsp/todo.jsp + +``` +<%@ include file="common/header.jspf" %> +<%@ include file="common/navigation.jspf" %> +
+ + +
+ Description + + +
+ +
+ Target Date + + +
+ + +
+
+<%@ include file="common/footer.jspf" %> +``` +--- +### src/main/webapp/WEB-INF/jsp/welcome.jsp + +``` +<%@ include file="common/header.jspf"%> +<%@ include file="common/navigation.jspf"%> +
+ Welcome ${name}!! Click here to manage your + todo's. +
+<%@ include file="common/footer.jspf"%> +``` +--- +### src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java + +```java +package com.in28minutes.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootFirstWebApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- +### todo.txt + +``` +Implementing Server Side Validation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Command Bean or Form Backing Bean + +Add Validation +Use Validation on Controller +Display Errors in View + +Command Bean +~~~~~~~~~~~~ +Controller +View - Spring Form Tags + + + +LoginController -> adds name to model +welcome.jsp -> shows ${name} + +TodoController -> redirects to list-todos.jsp +${name} is empty + + +Component, Service, Repository, Controller +Autowired +ComponentScan + +Field dummyService in com.in28minutes.springboot.web.controller.LoginController +required a bean of type 'com.in28minutes.dummy.DummyService' +that could not be found. + +Spring Boot Starter Parent +Spring Boot Starter Web +@SpringBootApplication +Auto Configuration + +Dispatcher Servlet + +/login => "login" + +"login" => src/main/webapp/WEB-INF/jsp/login.jsp + + +Search for a view named "login" + + + +/login => LoginController +``` +--- diff --git a/02.Spring-Boot-Web-Application/Step25.zip b/02.Spring-Boot-Web-Application/Step25.zip new file mode 100644 index 0000000..22cf7f9 Binary files /dev/null and b/02.Spring-Boot-Web-Application/Step25.zip differ diff --git a/02.Spring-Boot-Web-Application/Step26.md b/02.Spring-Boot-Web-Application/Step26.md new file mode 100644 index 0000000..79cfed6 --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step26.md @@ -0,0 +1,729 @@ +## What we will do: +- Basic Spring Rest Services. +- We use com.fasterxml.jackson.core:jackson-databind +- http://localhost:8080/rest/todos +- How does the magic happen? + +## Useful Snippets +``` +@RestController +public class TodoRestController { + @Autowired + private TodoService service; + + @RequestMapping(value = "/rest/todos", method = RequestMethod.GET) + public List listAllTodos() { + List users = service.retrieveTodos("in28Minutes"); + return users; + } + +} +``` + +## Files List +### /pom.xml +``` + + 4.0.0 + com.in28minutes.springboot.web + Spring-Boot-First-Web-Application + 0.0.1-SNAPSHOT + jar + + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-tomcat + provided + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + + javax.servlet + jstl + + + + org.webjars + bootstrap + 3.3.6 + + + + org.webjars + jquery + 1.9.1 + + + + org.webjars + bootstrap-datepicker + 1.0.1 + + + + org.springframework.boot + spring-boot-devtools + true + + + + + + + 1.8 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +### /src/main/java/com/in28minutes/springboot/web/controller/ExceptionController.java +``` +package com.in28minutes.springboot.web.controller; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.servlet.ModelAndView; + +@Controller("error") +public class ExceptionController { + + private Log logger = LogFactory.getLog(ExceptionController.class); + + @ExceptionHandler(Exception.class) + public ModelAndView handleError(HttpServletRequest req, Exception ex) { + logger.error("Request: " + req.getRequestURL() + " raised " + ex); + + ModelAndView mav = new ModelAndView(); + mav.addObject("exception", ex); + mav.addObject("url", req.getRequestURL()); + mav.setViewName("error"); + return mav; + } +} +``` +### /src/main/java/com/in28minutes/springboot/web/controller/LogoutController.java +``` +package com.in28minutes.springboot.web.controller; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@Controller +public class LogoutController { + + @RequestMapping(value = "/logout", method = RequestMethod.GET) + public String logout(HttpServletRequest request, + HttpServletResponse response) { + Authentication auth = SecurityContextHolder.getContext() + .getAuthentication(); + if (auth != null) { + new SecurityContextLogoutHandler().logout(request, response, auth); + } + return "redirect:/"; + } +} +``` +### /src/main/java/com/in28minutes/springboot/web/controller/TodoController.java +``` +package com.in28minutes.springboot.web.controller; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.validation.Valid; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.propertyeditors.CustomDateEditor; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import com.in28minutes.springboot.web.model.Todo; +import com.in28minutes.springboot.web.service.TodoService; + +@Controller +public class TodoController { + + @Autowired + private TodoService service; + + @InitBinder + protected void initBinder(WebDataBinder binder) { + SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy"); + binder.registerCustomEditor(Date.class, new CustomDateEditor( + dateFormat, false)); + } + + @RequestMapping(value = "/list-todos", method = RequestMethod.GET) + public String showTodosList(ModelMap model) { + String user = getLoggedInUserName(); + model.addAttribute("todos", service.retrieveTodos(user)); + return "list-todos"; + } + + @RequestMapping(value = "/add-todo", method = RequestMethod.GET) + public String showAddTodoPage(ModelMap model) { + model.addAttribute("todo", new Todo()); + return "todo"; + } + + @RequestMapping(value = "/add-todo", method = RequestMethod.POST) + public String addTodo(ModelMap model, @Valid Todo todo, BindingResult result) { + + if (result.hasErrors()) { + return "todo"; + } + + service.addTodo(getLoggedInUserName(), todo.getDesc(), + todo.getTargetDate(), false); + model.clear();// to prevent request parameter "name" to be passed + return "redirect:/list-todos"; + } + + private String getLoggedInUserName() { + Object principal = SecurityContextHolder.getContext() + .getAuthentication().getPrincipal(); + + if (principal instanceof UserDetails) { + return ((UserDetails) principal).getUsername(); + } + + return principal.toString(); + } + + @RequestMapping(value = "/update-todo", method = RequestMethod.GET) + public String showUpdateTodoPage(ModelMap model, @RequestParam int id) { + model.addAttribute("todo", service.retrieveTodo(id)); + return "todo"; + } + + @RequestMapping(value = "/update-todo", method = RequestMethod.POST) + public String updateTodo(ModelMap model, @Valid Todo todo, + BindingResult result) { + if (result.hasErrors()) { + return "todo"; + } + + todo.setUser(getLoggedInUserName()); + service.updateTodo(todo); + + model.clear();// to prevent request parameter "name" to be passed + return "redirect:/list-todos"; + } + + @RequestMapping(value = "/delete-todo", method = RequestMethod.GET) + public String deleteTodo(@RequestParam int id) { + service.deleteTodo(id); + + return "redirect:/list-todos"; + } + +} +``` +### /src/main/java/com/in28minutes/springboot/web/controller/TodoRestController.java +``` +package com.in28minutes.springboot.web.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import com.in28minutes.springboot.web.model.Todo; +import com.in28minutes.springboot.web.service.TodoService; + +@RestController +public class TodoRestController { + @Autowired + private TodoService service; + + @RequestMapping(value = "/rest/todos", method = RequestMethod.GET) + public List listAllTodos() { + List users = service.retrieveTodos("in28Minutes"); + return users; + } + +} +``` +### /src/main/java/com/in28minutes/springboot/web/controller/WelcomeController.java +``` +package com.in28minutes.springboot.web.controller; + +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@Controller +public class WelcomeController { + + @RequestMapping(value = "/", method = RequestMethod.GET) + public String showWelcomePage(ModelMap model) { + model.put("name", getLoggedInUserName()); + return "welcome"; + } + + private String getLoggedInUserName() { + Object principal = SecurityContextHolder.getContext() + .getAuthentication().getPrincipal(); + + if (principal instanceof UserDetails) { + return ((UserDetails) principal).getUsername(); + } + + return principal.toString(); + } + +} +``` +### /src/main/java/com/in28minutes/springboot/web/model/Todo.java +``` +package com.in28minutes.springboot.web.model; + +import java.util.Date; + +import javax.validation.constraints.Size; + +public class Todo { + + private int id; + + private String user; + + @Size(min = 10, message = "Enter atleast 10 Characters.") + private String desc; + + private Date targetDate; + + private boolean isDone; + + public Todo() { + super(); + } + + public Todo(int id, String user, String desc, Date targetDate, + boolean isDone) { + super(); + this.id = id; + this.user = user; + this.desc = desc; + this.targetDate = targetDate; + this.isDone = isDone; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public Date getTargetDate() { + return targetDate; + } + + public void setTargetDate(Date targetDate) { + this.targetDate = targetDate; + } + + public boolean isDone() { + return isDone; + } + + public void setDone(boolean isDone) { + this.isDone = isDone; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Todo other = (Todo) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return String.format( + "Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]", id, + user, desc, targetDate, isDone); + } + +} +``` +### /src/main/java/com/in28minutes/springboot/web/security/SecurityConfiguration.java +``` +package com.in28minutes.springboot.web.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +@EnableWebSecurity +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + @Autowired + public void configureGlobalSecurity(AuthenticationManagerBuilder auth) + throws Exception { + auth.inMemoryAuthentication().withUser("in28Minutes").password("dummy") + .roles("USER", "ADMIN"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests().antMatchers("/login").permitAll() + .antMatchers("/", "/*todo*/**").access("hasRole('USER')").and() + .formLogin(); + } +} +``` +### /src/main/java/com/in28minutes/springboot/web/service/LoginService.java +``` +package com.in28minutes.springboot.web.service; + +import org.springframework.stereotype.Component; + +@Component +public class LoginService { + public boolean validateUser(String user, String password) { + return user.equalsIgnoreCase("in28Minutes") && password.equals("dummy"); + } +} +``` +### /src/main/java/com/in28minutes/springboot/web/service/TodoService.java +``` +package com.in28minutes.springboot.web.service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.in28minutes.springboot.web.model.Todo; + +@Service +public class TodoService { + private static List todos = new ArrayList(); + private static int todoCount = 3; + + static { + todos.add(new Todo(1, "in28Minutes", "Learn Spring MVC", new Date(), + false)); + todos.add(new Todo(2, "in28Minutes", "Learn Struts", new Date(), false)); + todos.add(new Todo(3, "in28Minutes", "Learn Hibernate", new Date(), + false)); + } + + public List retrieveTodos(String user) { + List filteredTodos = new ArrayList(); + for (Todo todo : todos) { + if (todo.getUser().equalsIgnoreCase(user)) { + filteredTodos.add(todo); + } + } + return filteredTodos; + } + + public Todo retrieveTodo(int id) { + for (Todo todo : todos) { + if (todo.getId() == id) { + return todo; + } + } + return null; + } + + public void updateTodo(Todo todo) { + todos.remove(todo); + todos.add(todo); + } + + public void addTodo(String name, String desc, Date targetDate, + boolean isDone) { + todos.add(new Todo(++todoCount, name, desc, targetDate, isDone)); + } + + public void deleteTodo(int id) { + Iterator iterator = todos.iterator(); + while (iterator.hasNext()) { + Todo todo = iterator.next(); + if (todo.getId() == id) { + iterator.remove(); + } + } + } +} +``` +### /src/main/java/com/in28minutes/springboot/web/WebApplication.java +``` +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; + +@SpringBootApplication +public class WebApplication { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(WebApplication.class, args); + } +} +``` +### /src/main/resources/application.properties +``` +spring.mvc.view.prefix: /WEB-INF/jsp/ +spring.mvc.view.suffix: .jsp +logging.level.: DEBUG +``` +### /src/main/webapp/WEB-INF/jsp/common/footer.jspf +``` + + + + + + +``` +### /src/main/webapp/WEB-INF/jsp/common/header.jspf +``` +<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> + + +Todos Application + + + + +``` +### /src/main/webapp/WEB-INF/jsp/common/navigation.jspf +``` + +``` +### /src/main/webapp/WEB-INF/jsp/error.jsp +``` +<%@ include file="common/header.jspf"%> +<%@ include file="common/navigation.jspf"%> +
+ Application has encountered an error. Please contact support on ... +
+ +<%@ include file="common/footer.jspf"%> +``` +### /src/main/webapp/WEB-INF/jsp/list-todos.jsp +``` +<%@ include file="common/header.jspf"%> +<%@ include file="common/navigation.jspf"%> + +
+ + + + + + + + + + + + + + + + + + + + +
Your Todos are
DescriptionDateCompleted
${todo.desc}${todo.done}Edit Delete +
+
+ Add +
+
+<%@ include file="common/footer.jspf"%> +``` +### /src/main/webapp/WEB-INF/jsp/login.jsp +``` +<%@ include file="common/header.jspf"%> +<%@ include file="common/navigation.jspf"%> +
+

+ ${errorMessage} +

+
+
+ +
+
+ +
+ +
+ +
+ +<%@ include file="common/footer.jspf"%> +``` +### /src/main/webapp/WEB-INF/jsp/todo.jsp +``` +<%@ include file="common/header.jspf"%> +<%@ include file="common/navigation.jspf"%> +
+ + +
+ Description + + +
+
+ Target Date + + +
+ +
+
+ +<%@ include file="common/footer.jspf"%> + + +``` +### /src/main/webapp/WEB-INF/jsp/welcome.jsp +``` +<%@ include file="common/header.jspf"%> +<%@ include file="common/navigation.jspf"%> +
+ Welcome ${name}. You are now authenticated. +
+ +<%@ include file="common/footer.jspf"%> +``` diff --git a/02.Spring-Boot-Web-Application/Step27.md b/02.Spring-Boot-Web-Application/Step27.md new file mode 100644 index 0000000..445fb3b --- /dev/null +++ b/02.Spring-Boot-Web-Application/Step27.md @@ -0,0 +1,15 @@ +## What we will do: +- One More Spring Rest Services. +- @PathVariable("id") int id +- http://localhost:8080/rest/todos/1 + +## Useful Snippets + +``` + @RequestMapping(value = "/rest/todos/{id}", method = RequestMethod.GET) + public Todo retrieveTodo(@PathVariable("id") int id) { + return service.retrieveTodo(id); + } + +``` +## Files List diff --git a/02.Spring-Boot-Web-Application/Step27.zip b/02.Spring-Boot-Web-Application/Step27.zip new file mode 100644 index 0000000..8a4c9f6 Binary files /dev/null and b/02.Spring-Boot-Web-Application/Step27.zip differ diff --git a/02.Spring-Boot-Web-Application/StepReference.md b/02.Spring-Boot-Web-Application/StepReference.md new file mode 100644 index 0000000..f409414 --- /dev/null +++ b/02.Spring-Boot-Web-Application/StepReference.md @@ -0,0 +1,16 @@ +## What You Will Learn during this Step: +- First +- Second +- Third + +## Useful Snippets and References +First Snippet +``` +``` +Second Snippet +``` +``` + +## Exercises + +## Files List diff --git a/02.Spring-Boot-Web-Application/pom.xml b/02.Spring-Boot-Web-Application/pom.xml new file mode 100644 index 0000000..660b6d1 --- /dev/null +++ b/02.Spring-Boot-Web-Application/pom.xml @@ -0,0 +1,89 @@ + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application-git + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + javax.servlet + jstl + + + + org.webjars + bootstrap + 3.3.6 + + + + org.webjars + bootstrap-datepicker + 1.0.1 + + + + org.webjars + jquery + 1.9.1 + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/02.Spring-Boot-Web-Application/src/.DS_Store b/02.Spring-Boot-Web-Application/src/.DS_Store new file mode 100644 index 0000000..0787fb7 Binary files /dev/null and b/02.Spring-Boot-Web-Application/src/.DS_Store differ diff --git a/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java b/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java new file mode 100644 index 0000000..3cbb8c5 --- /dev/null +++ b/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/SpringBootFirstWebApplication.java @@ -0,0 +1,14 @@ +package com.in28minutes.springboot.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan("com.in28minutes.springboot.web") +public class SpringBootFirstWebApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringBootFirstWebApplication.class, args); + } +} diff --git a/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/controller/ErrorController.java b/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/controller/ErrorController.java new file mode 100644 index 0000000..a9f973a --- /dev/null +++ b/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/controller/ErrorController.java @@ -0,0 +1,25 @@ +package com.in28minutes.springboot.web.controller; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.servlet.ModelAndView; + +@Controller("error") +public class ErrorController { + + @ExceptionHandler(Exception.class) + public ModelAndView handleException + (HttpServletRequest request, Exception ex){ + ModelAndView mv = new ModelAndView(); + + mv.addObject("exception", ex.getLocalizedMessage()); + mv.addObject("url", request.getRequestURL()); + + mv.setViewName("error"); + return mv; + } + +} diff --git a/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/controller/LogoutController.java b/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/controller/LogoutController.java new file mode 100644 index 0000000..275100e --- /dev/null +++ b/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/controller/LogoutController.java @@ -0,0 +1,33 @@ +package com.in28minutes.springboot.web.controller; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@Controller +public class LogoutController { + + @RequestMapping(value = "/logout", method = RequestMethod.GET) + public String logout(HttpServletRequest request, + HttpServletResponse response) { + + Authentication authentication = SecurityContextHolder.getContext() + .getAuthentication(); + + if (authentication != null) { + new SecurityContextLogoutHandler().logout(request, response, + authentication); + } + + return "redirect:/"; + } +} diff --git a/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/controller/TodoController.java b/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/controller/TodoController.java new file mode 100644 index 0000000..7e4e0fc --- /dev/null +++ b/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/controller/TodoController.java @@ -0,0 +1,106 @@ +package com.in28minutes.springboot.web.controller; + +import java.text.SimpleDateFormat; +import java.util.Date; + +import javax.validation.Valid; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.propertyeditors.CustomDateEditor; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import com.in28minutes.springboot.web.model.Todo; +import com.in28minutes.springboot.web.service.TodoService; + +@Controller +public class TodoController { + + @Autowired + TodoService service; + + @InitBinder + public void initBinder(WebDataBinder binder) { + // Date - dd/MM/yyyy + SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy"); + binder.registerCustomEditor(Date.class, new CustomDateEditor( + dateFormat, false)); + } + + @RequestMapping(value = "/list-todos", method = RequestMethod.GET) + public String showTodos(ModelMap model) { + String name = getLoggedInUserName(model); + model.put("todos", service.retrieveTodos(name)); + return "list-todos"; + } + + private String getLoggedInUserName(ModelMap model) { + Object principal = SecurityContextHolder.getContext() + .getAuthentication().getPrincipal(); + + if (principal instanceof UserDetails) { + return ((UserDetails) principal).getUsername(); + } + + return principal.toString(); + } + + @RequestMapping(value = "/add-todo", method = RequestMethod.GET) + public String showAddTodoPage(ModelMap model) { + model.addAttribute("todo", new Todo(0, getLoggedInUserName(model), + "Default Desc", new Date(), false)); + return "todo"; + } + + @RequestMapping(value = "/delete-todo", method = RequestMethod.GET) + public String deleteTodo(@RequestParam int id) { + + if(id==1) + throw new RuntimeException("Something went wrong"); + + service.deleteTodo(id); + return "redirect:/list-todos"; + } + + @RequestMapping(value = "/update-todo", method = RequestMethod.GET) + public String showUpdateTodoPage(@RequestParam int id, ModelMap model) { + Todo todo = service.retrieveTodo(id); + model.put("todo", todo); + return "todo"; + } + + @RequestMapping(value = "/update-todo", method = RequestMethod.POST) + public String updateTodo(ModelMap model, @Valid Todo todo, + BindingResult result) { + + if (result.hasErrors()) { + return "todo"; + } + + todo.setUser(getLoggedInUserName(model)); + + service.updateTodo(todo); + + return "redirect:/list-todos"; + } + + @RequestMapping(value = "/add-todo", method = RequestMethod.POST) + public String addTodo(ModelMap model, @Valid Todo todo, BindingResult result) { + + if (result.hasErrors()) { + return "todo"; + } + + service.addTodo(getLoggedInUserName(model), todo.getDesc(), todo.getTargetDate(), + false); + return "redirect:/list-todos"; + } +} diff --git a/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/controller/WelcomeController.java b/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/controller/WelcomeController.java new file mode 100644 index 0000000..da5018c --- /dev/null +++ b/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/controller/WelcomeController.java @@ -0,0 +1,30 @@ +package com.in28minutes.springboot.web.controller; + +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@Controller +public class WelcomeController { + + @RequestMapping(value = "/", method = RequestMethod.GET) + public String showWelcomePage(ModelMap model) { + model.put("name", getLoggedinUserName()); + return "welcome"; + } + + private String getLoggedinUserName() { + Object principal = SecurityContextHolder.getContext() + .getAuthentication().getPrincipal(); + + if (principal instanceof UserDetails) { + return ((UserDetails) principal).getUsername(); + } + + return principal.toString(); + } + +} diff --git a/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/model/Todo.java b/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/model/Todo.java new file mode 100644 index 0000000..12abae3 --- /dev/null +++ b/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/model/Todo.java @@ -0,0 +1,104 @@ +package com.in28minutes.springboot.web.model; + +import java.util.Date; + +import javax.validation.constraints.Size; + +public class Todo { + private int id; + private String user; + + @Size(min=10, message="Enter at least 10 Characters...") + private String desc; + + private Date targetDate; + private boolean isDone; + + public Todo() { + super(); + } + + public Todo(int id, String user, String desc, Date targetDate, + boolean isDone) { + super(); + this.id = id; + this.user = user; + this.desc = desc; + this.targetDate = targetDate; + this.isDone = isDone; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public Date getTargetDate() { + return targetDate; + } + + public void setTargetDate(Date targetDate) { + this.targetDate = targetDate; + } + + public boolean isDone() { + return isDone; + } + + public void setDone(boolean isDone) { + this.isDone = isDone; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Todo other = (Todo) obj; + if (id != other.id) { + return false; + } + return true; + } + + @Override + public String toString() { + return String.format( + "Todo [id=%s, user=%s, desc=%s, targetDate=%s, isDone=%s]", id, + user, desc, targetDate, isDone); + } + +} \ No newline at end of file diff --git a/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/security/SecurityConfiguration.java b/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/security/SecurityConfiguration.java new file mode 100644 index 0000000..5d41129 --- /dev/null +++ b/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/security/SecurityConfiguration.java @@ -0,0 +1,25 @@ +package com.in28minutes.springboot.web.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +public class SecurityConfiguration extends WebSecurityConfigurerAdapter{ + //Create User - in28Minutes/dummy + @Autowired + public void configureGlobalSecurity(AuthenticationManagerBuilder auth) + throws Exception { + auth.inMemoryAuthentication().withUser("in28Minutes").password("dummy") + .roles("USER", "ADMIN"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests().antMatchers("/login").permitAll() + .antMatchers("/", "/*todo*/**").access("hasRole('USER')").and() + .formLogin(); + } +} diff --git a/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/service/TodoService.java b/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/service/TodoService.java new file mode 100644 index 0000000..322231a --- /dev/null +++ b/02.Spring-Boot-Web-Application/src/main/java/com/in28minutes/springboot/web/service/TodoService.java @@ -0,0 +1,63 @@ +package com.in28minutes.springboot.web.service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.in28minutes.springboot.web.model.Todo; + +@Service +public class TodoService { + private static List todos = new ArrayList(); + private static int todoCount = 3; + + static { + todos.add(new Todo(1, "in28Minutes", "Learn Spring MVC", new Date(), + false)); + todos.add(new Todo(2, "in28Minutes", "Learn Struts", new Date(), false)); + todos.add(new Todo(3, "in28Minutes", "Learn Hibernate", new Date(), + false)); + } + + public List retrieveTodos(String user) { + List filteredTodos = new ArrayList(); + for (Todo todo : todos) { + if (todo.getUser().equalsIgnoreCase(user)) { + filteredTodos.add(todo); + } + } + return filteredTodos; + } + + public Todo retrieveTodo(int id) { + for (Todo todo : todos) { + if (todo.getId()==id) { + return todo; + } + } + return null; + } + + public void updateTodo(Todo todo){ + todos.remove(todo); + todos.add(todo); + } + + public void addTodo(String name, String desc, Date targetDate, + boolean isDone) { + todos.add(new Todo(++todoCount, name, desc, targetDate, isDone)); + } + + public void deleteTodo(int id) { + Iterator iterator = todos.iterator(); + while (iterator.hasNext()) { + Todo todo = iterator.next(); + if (todo.getId() == id) { + iterator.remove(); + } + } + } +} \ No newline at end of file diff --git a/02.Spring-Boot-Web-Application/src/main/resources/application.properties b/02.Spring-Boot-Web-Application/src/main/resources/application.properties new file mode 100644 index 0000000..bc7d47b --- /dev/null +++ b/02.Spring-Boot-Web-Application/src/main/resources/application.properties @@ -0,0 +1,3 @@ +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp +logging.level.org.springframework.web=INFO \ No newline at end of file diff --git a/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/common/footer.jspf b/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/common/footer.jspf new file mode 100644 index 0000000..879b276 --- /dev/null +++ b/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/common/footer.jspf @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/common/header.jspf b/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/common/header.jspf new file mode 100644 index 0000000..6c8761f --- /dev/null +++ b/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/common/header.jspf @@ -0,0 +1,14 @@ +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> +<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> + + + + +First Web Application + + + + + diff --git a/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/common/navigation.jspf b/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/common/navigation.jspf new file mode 100644 index 0000000..6af8ff1 --- /dev/null +++ b/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/common/navigation.jspf @@ -0,0 +1,15 @@ + + diff --git a/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/error.jsp b/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/error.jsp new file mode 100644 index 0000000..d44949d --- /dev/null +++ b/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/error.jsp @@ -0,0 +1,6 @@ +<%@ include file="common/header.jspf"%> +<%@ include file="common/navigation.jspf"%> +
+An exception occurred! Please contact Support! +
+<%@ include file="common/footer.jspf"%> \ No newline at end of file diff --git a/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/list-todos.jsp b/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/list-todos.jsp new file mode 100644 index 0000000..9e86ebe --- /dev/null +++ b/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/list-todos.jsp @@ -0,0 +1,34 @@ +<%@ include file="common/header.jspf" %> +<%@ include file="common/navigation.jspf" %> + +
+ + + + + + + + + + + + + + + + + + + + + + +
Your todos are
DescriptionTarget DateIs it Done?
${todo.desc}${todo.done}UpdateDelete
+ +
+<%@ include file="common/footer.jspf" %> \ No newline at end of file diff --git a/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/todo.jsp b/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/todo.jsp new file mode 100644 index 0000000..9860549 --- /dev/null +++ b/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/todo.jsp @@ -0,0 +1,23 @@ +<%@ include file="common/header.jspf" %> +<%@ include file="common/navigation.jspf" %> +
+ + +
+ Description + + +
+ +
+ Target Date + + +
+ + +
+
+<%@ include file="common/footer.jspf" %> \ No newline at end of file diff --git a/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/welcome.jsp b/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/welcome.jsp new file mode 100644 index 0000000..51f67df --- /dev/null +++ b/02.Spring-Boot-Web-Application/src/main/webapp/WEB-INF/jsp/welcome.jsp @@ -0,0 +1,7 @@ +<%@ include file="common/header.jspf"%> +<%@ include file="common/navigation.jspf"%> +
+ Welcome ${name}!! Click here to manage your + todo's. +
+<%@ include file="common/footer.jspf"%> \ No newline at end of file diff --git a/02.Spring-Boot-Web-Application/src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java b/02.Spring-Boot-Web-Application/src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java new file mode 100644 index 0000000..f207136 --- /dev/null +++ b/02.Spring-Boot-Web-Application/src/test/java/com/in28minutes/springboot/web/SpringBootFirstWebApplicationTests.java @@ -0,0 +1,16 @@ +package com.in28minutes.springboot.web; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringBootFirstWebApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/02.Spring-Boot-Web-Application/target/classes/META-INF/MANIFEST.MF b/02.Spring-Boot-Web-Application/target/classes/META-INF/MANIFEST.MF new file mode 100644 index 0000000..2aec69c --- /dev/null +++ b/02.Spring-Boot-Web-Application/target/classes/META-INF/MANIFEST.MF @@ -0,0 +1,11 @@ +Manifest-Version: 1.0 +Implementation-Title: spring-boot-first-web-application +Implementation-Version: 0.0.1-SNAPSHOT +Built-By: rangaraokaranam +Implementation-Vendor-Id: com.in28minutes.springboot.web +Build-Jdk: 1.8.0_31 +Implementation-URL: http://projects.spring.io/spring-boot/spring-boot- + first-web-application-git/ +Created-By: Maven Integration for Eclipse +Implementation-Vendor: Pivotal Software, Inc. + diff --git a/02.Spring-Boot-Web-Application/target/classes/META-INF/maven/com.in28minutes.springboot.web/spring-boot-first-web-application-git/pom.properties b/02.Spring-Boot-Web-Application/target/classes/META-INF/maven/com.in28minutes.springboot.web/spring-boot-first-web-application-git/pom.properties new file mode 100644 index 0000000..b7fed34 --- /dev/null +++ b/02.Spring-Boot-Web-Application/target/classes/META-INF/maven/com.in28minutes.springboot.web/spring-boot-first-web-application-git/pom.properties @@ -0,0 +1,7 @@ +#Generated by Maven Integration for Eclipse +#Sun Jul 30 12:42:04 IST 2017 +version=0.0.1-SNAPSHOT +groupId=com.in28minutes.springboot.web +m2e.projectName=spring-boot-first-web-application-git +m2e.projectLocation=/in28Minutes/git/SpringBootWebApplicationStepByStep +artifactId=spring-boot-first-web-application-git diff --git a/02.Spring-Boot-Web-Application/target/classes/META-INF/maven/com.in28minutes.springboot.web/spring-boot-first-web-application-git/pom.xml b/02.Spring-Boot-Web-Application/target/classes/META-INF/maven/com.in28minutes.springboot.web/spring-boot-first-web-application-git/pom.xml new file mode 100644 index 0000000..660b6d1 --- /dev/null +++ b/02.Spring-Boot-Web-Application/target/classes/META-INF/maven/com.in28minutes.springboot.web/spring-boot-first-web-application-git/pom.xml @@ -0,0 +1,89 @@ + + + 4.0.0 + + com.in28minutes.springboot.web + spring-boot-first-web-application-git + 0.0.1-SNAPSHOT + jar + + spring-boot-first-web-application + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + javax.servlet + jstl + + + + org.webjars + bootstrap + 3.3.6 + + + + org.webjars + bootstrap-datepicker + 1.0.1 + + + + org.webjars + jquery + 1.9.1 + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + + org.springframework.boot + spring-boot-devtools + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/02.Spring-Boot-Web-Application/target/classes/application.properties b/02.Spring-Boot-Web-Application/target/classes/application.properties new file mode 100644 index 0000000..bc7d47b --- /dev/null +++ b/02.Spring-Boot-Web-Application/target/classes/application.properties @@ -0,0 +1,3 @@ +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp +logging.level.org.springframework.web=INFO \ No newline at end of file diff --git a/03.JUnit-Introduction-In-5-Steps/.DS_Store b/03.JUnit-Introduction-In-5-Steps/.DS_Store new file mode 100644 index 0000000..1321586 Binary files /dev/null and b/03.JUnit-Introduction-In-5-Steps/.DS_Store differ diff --git a/03.JUnit-Introduction-In-5-Steps/.classpath b/03.JUnit-Introduction-In-5-Steps/.classpath new file mode 100644 index 0000000..e72ef7c --- /dev/null +++ b/03.JUnit-Introduction-In-5-Steps/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/03.JUnit-Introduction-In-5-Steps/.gitignore b/03.JUnit-Introduction-In-5-Steps/.gitignore new file mode 100644 index 0000000..ae3c172 --- /dev/null +++ b/03.JUnit-Introduction-In-5-Steps/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/03.JUnit-Introduction-In-5-Steps/.project b/03.JUnit-Introduction-In-5-Steps/.project new file mode 100644 index 0000000..a22f67d --- /dev/null +++ b/03.JUnit-Introduction-In-5-Steps/.project @@ -0,0 +1,17 @@ + + + junit-in-5-steps + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/03.JUnit-Introduction-In-5-Steps/code-21July2017.zip b/03.JUnit-Introduction-In-5-Steps/code-21July2017.zip new file mode 100644 index 0000000..f503f51 Binary files /dev/null and b/03.JUnit-Introduction-In-5-Steps/code-21July2017.zip differ diff --git a/03.JUnit-Introduction-In-5-Steps/readme.md b/03.JUnit-Introduction-In-5-Steps/readme.md new file mode 100644 index 0000000..83a62ea --- /dev/null +++ b/03.JUnit-Introduction-In-5-Steps/readme.md @@ -0,0 +1,124 @@ +## First 5 Steps in JUnit + +- Git Repository - https://github.com/in28minutes/getting-started-in-5-steps +- Pre-requisites - Java & Eclipse - https://www.youtube.com/playlist?list=PLBBog2r6uMCSmMVTW_QmDLyASBvovyAO3 +- We will use embedded maven in Eclipse + +### Step 1 : What is JUnit and Unit Testing? + - What is JUnit? + - What is Unit Testing? + - Advantages of Unit Testing + +### Step 2 : First JUnit Project and Green Bar + - What is JUnit? + - First Project with JUnit + - First JUnit Class + - No Failure is Success + - MyMath class with sum method + +### Step 3 : First Code and First Unit Test + - Unit test for the sum method + +### Step 4 : Other assert methods + - assertTrue and assertFalse methods + +### Step 5 : Important annotations + - @Before @After annotations + - @BeforeClass @AfterClass annotations + +## Complete Code Example + + +### /src/com/in28minutes/junit/MyMath.java + +```java +package com.in28minutes.junit; + +public class MyMath { + int sum(int[] numbers) { + int sum = 0; + for (int i : numbers) { + sum += i; + } + return sum; + } +} +``` +--- + +### /test/com/in28minutes/junit/AssertTest.java + +```java +package com.in28minutes.junit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class AssertTest { + + @Test + public void test() { + boolean condn = true; + assertEquals(true, condn); + assertTrue(condn); + // assertFalse(condn); + } + +} +``` +--- + +### /test/com/in28minutes/junit/MyMathTest.java + +```java +package com.in28minutes.junit; + +import static org.junit.Assert.assertEquals; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +public class MyMathTest { + MyMath myMath = new MyMath(); + + @Before + public void before() { + System.out.println("Before"); + } + + @After + public void after() { + System.out.println("After"); + } + + @BeforeClass + public static void beforeClass() { + System.out.println("Before Class"); + } + + @AfterClass + public static void afterClass() { + System.out.println("After Class"); + } + + // MyMath.sum + // 1,2,3 => 6 + @Test + public void sum_with3numbers() { + System.out.println("Test1"); + assertEquals(6, myMath.sum(new int[] { 1, 2, 3 })); + } + + @Test + public void sum_with1number() { + System.out.println("Test2"); + assertEquals(3, myMath.sum(new int[] { 3 })); + } +} +``` +--- diff --git a/03.JUnit-Introduction-In-5-Steps/src/.DS_Store b/03.JUnit-Introduction-In-5-Steps/src/.DS_Store new file mode 100644 index 0000000..082d232 Binary files /dev/null and b/03.JUnit-Introduction-In-5-Steps/src/.DS_Store differ diff --git a/03.JUnit-Introduction-In-5-Steps/src/com/.DS_Store b/03.JUnit-Introduction-In-5-Steps/src/com/.DS_Store new file mode 100644 index 0000000..aa7381c Binary files /dev/null and b/03.JUnit-Introduction-In-5-Steps/src/com/.DS_Store differ diff --git a/03.JUnit-Introduction-In-5-Steps/src/com/in28minutes/.DS_Store b/03.JUnit-Introduction-In-5-Steps/src/com/in28minutes/.DS_Store new file mode 100644 index 0000000..4c188fe Binary files /dev/null and b/03.JUnit-Introduction-In-5-Steps/src/com/in28minutes/.DS_Store differ diff --git a/03.JUnit-Introduction-In-5-Steps/src/com/in28minutes/junit/MyMath.java b/03.JUnit-Introduction-In-5-Steps/src/com/in28minutes/junit/MyMath.java new file mode 100644 index 0000000..8bcb701 --- /dev/null +++ b/03.JUnit-Introduction-In-5-Steps/src/com/in28minutes/junit/MyMath.java @@ -0,0 +1,11 @@ +package com.in28minutes.junit; + +public class MyMath { + int sum(int[] numbers) { + int sum = 0; + for (int i : numbers) { + sum += i; + } + return sum; + } +} diff --git a/03.JUnit-Introduction-In-5-Steps/step22.md b/03.JUnit-Introduction-In-5-Steps/step22.md new file mode 100644 index 0000000..240a25c --- /dev/null +++ b/03.JUnit-Introduction-In-5-Steps/step22.md @@ -0,0 +1,383 @@ + +## Complete Code Example + + +### /notes.txt + +``` +Questions +- Where is the database created? +- What schema is used to create the tables? +- Where are the tables created? +- Can I see the data in the database? +- Where is Hibernate coming in from? +- How is a datasource created? + +Magic of Spring Boot and in Memory Database +- Zero project setup or infrastructure +- Zero Configuration +- Zero Maintainance +- Easy to use for Learning and Unit Tests +- Simple Configuration to switch to a real database + +# Restrictions of using in-memory database +- Data is not persisted between restarts + +Spring Boot chooses a default value for you based on whether it thinks your database is embedded (default create-drop) or not (default none). + +HibernateJpaAutoConfiguration matched: + - @ConditionalOnClass found required classes 'org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean', 'javax.persistence.EntityManager'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) + - HibernateEntityManager found class 'org.hibernate.ejb.HibernateEntityManager' (HibernateJpaAutoConfiguration.HibernateEntityManagerCondition) + +DataSourceAutoConfiguration matched: + - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) + +JpaBaseConfiguration#entityManagerFactory matched: + - @ConditionalOnMissingBean (types: org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean,javax.persistence.EntityManagerFactory; SearchStrategy: all) did not find any beans (OnBeanCondition) + +JpaBaseConfiguration#transactionManager matched: + - @ConditionalOnMissingBean (types: org.springframework.transaction.PlatformTransactionManager; SearchStrategy: all) did not find any beans (OnBeanCondition) +``` +--- + +### /pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.learning.jpa + jpa-in-10-steps + 0.0.1-SNAPSHOT + jar + + jpa-in-10-steps + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.BUILD-SNAPSHOT + + + + + UTF-8 + UTF-8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-web + + + + com.h2database + h2 + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/entity/User.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.entity; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +//Table - User +@Entity +public class User { + + @Id + @GeneratedValue + private long id; + + private String name; + + private String role; + + protected User() { + + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return String.format("User [id=%s, name=%s, role=%s]", id, name, role); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplication.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class JpaIn10StepsApplication { + + public static void main(String[] args) { + SpringApplication.run(JpaIn10StepsApplication.class, args); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserDAOService.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.service; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.transaction.Transactional; + +import org.springframework.stereotype.Repository; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; + +@Repository +@Transactional +public class UserDAOService { + + @PersistenceContext + private EntityManager entityManager; + + public long insert(User user){ + entityManager.persist(user); + return user.getId(); + } +} + +/* + * Spring Data JPA + * + * + * + */ +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserRepository.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.service; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; + +public interface UserRepository extends JpaRepository{ + +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserDaoServiceCommandLineRunner.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; +import com.in28minutes.learning.jpa.jpain10steps.service.UserDAOService; + +@Component +public class UserDaoServiceCommandLineRunner implements CommandLineRunner{ + + private static final Logger log = + LoggerFactory.getLogger(UserDaoServiceCommandLineRunner.class); + + @Autowired + private UserDAOService userDaoService; + + @Override + public void run(String... arg0) throws Exception { + User user = new User("Jack", "Admin"); + //New User is created : User [id=1, name=Jack, role=Admin] + long insert = userDaoService.insert(user); + log.info("New User is created : " + user); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserRepositoryCommandLineRunner.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; +import com.in28minutes.learning.jpa.jpain10steps.service.UserRepository; + +@Component +public class UserRepositoryCommandLineRunner implements CommandLineRunner{ + + private static final Logger log = + LoggerFactory.getLogger(UserRepositoryCommandLineRunner.class); + + @Autowired + private UserRepository userRepository; + + @Override + public void run(String... arg0) throws Exception { + User user = new User("Jill", "Admin"); + userRepository.save(user); + log.info("New User is created : " + user); + + Optional userWithIdOne = userRepository.findById(1L); + log.info("User is retrived : " + userWithIdOne); + + List users = userRepository.findAll(); + log.info("All Users : " + users); + } + +} +``` +--- + +### /src/main/resources/application.properties + +``` +spring.jpa.show-sql=true +spring.h2.console.enabled=true +logging.level.org.springframework=debug +``` +--- + +### /src/test/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplicationTests.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class JpaIn10StepsApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- + +### /step-completed.sh + +``` +java -cp /ProgrammingExcellence/Workspaces/Rithus.com/ListDirectoryContentInGitFormat/bin test.ListDirectoryContentInGitFormat >> $1.md +zip -r $1.zip . -x "target/*" -x ".*/*" -x ".*" -x "*.md" -x "mvn*" -x "*.zip" +git add *; git commit -m "$1"; git push; +``` +--- diff --git a/03.JUnit-Introduction-In-5-Steps/step22.zip b/03.JUnit-Introduction-In-5-Steps/step22.zip new file mode 100644 index 0000000..3a7fec6 Binary files /dev/null and b/03.JUnit-Introduction-In-5-Steps/step22.zip differ diff --git a/03.JUnit-Introduction-In-5-Steps/step25.md b/03.JUnit-Introduction-In-5-Steps/step25.md new file mode 100644 index 0000000..f1192f8 --- /dev/null +++ b/03.JUnit-Introduction-In-5-Steps/step25.md @@ -0,0 +1,97 @@ + +## Complete Code Example + + +### /in28Minutes/git/getting-started-in-5-steps/junit-in-5-steps/src/com/in28minutes/junit/MyMath.java + +```java +package com.in28minutes.junit; + +public class MyMath { + int sum(int[] numbers) { + int sum = 0; + for (int i : numbers) { + sum += i; + } + return sum; + } +} +``` +--- + +### /in28Minutes/git/getting-started-in-5-steps/junit-in-5-steps/test/com/in28minutes/junit/AssertTest.java + +```java +package com.in28minutes.junit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class AssertTest { + + @Test + public void test() { + boolean condn = true; + assertEquals(true, condn); + assertTrue(condn); + // assertFalse(condn); + } + +} +``` +--- + +### /in28Minutes/git/getting-started-in-5-steps/junit-in-5-steps/test/com/in28minutes/junit/MyMathTest.java + +```java +package com.in28minutes.junit; + +import static org.junit.Assert.assertEquals; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +public class MyMathTest { + MyMath myMath = new MyMath(); + + @Before + public void before() { + System.out.println("Before"); + } + + @After + public void after() { + System.out.println("After"); + } + + @BeforeClass + public static void beforeClass() { + System.out.println("Before Class"); + } + + @AfterClass + public static void afterClass() { + System.out.println("After Class"); + } + + // MyMath.sum + // 1,2,3 => 6 + @Test + public void sum_with3numbers() { + System.out.println("Test1"); + assertEquals(6, myMath.sum(new int[] { 1, 2, 3 })); + } + + @Test + public void sum_with1number() { + System.out.println("Test2"); + assertEquals(3, myMath.sum(new int[] { 3 })); + } +} +``` +--- diff --git a/03.JUnit-Introduction-In-5-Steps/step25.zip b/03.JUnit-Introduction-In-5-Steps/step25.zip new file mode 100644 index 0000000..190decf Binary files /dev/null and b/03.JUnit-Introduction-In-5-Steps/step25.zip differ diff --git a/03.JUnit-Introduction-In-5-Steps/test/.DS_Store b/03.JUnit-Introduction-In-5-Steps/test/.DS_Store new file mode 100644 index 0000000..788768e Binary files /dev/null and b/03.JUnit-Introduction-In-5-Steps/test/.DS_Store differ diff --git a/03.JUnit-Introduction-In-5-Steps/test/com/in28minutes/junit/AssertTest.java b/03.JUnit-Introduction-In-5-Steps/test/com/in28minutes/junit/AssertTest.java new file mode 100644 index 0000000..29135c0 --- /dev/null +++ b/03.JUnit-Introduction-In-5-Steps/test/com/in28minutes/junit/AssertTest.java @@ -0,0 +1,18 @@ +package com.in28minutes.junit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class AssertTest { + + @Test + public void test() { + boolean condn = true; + assertEquals(true, condn); + assertTrue(condn); + // assertFalse(condn); + } + +} diff --git a/03.JUnit-Introduction-In-5-Steps/test/com/in28minutes/junit/MyMathTest.java b/03.JUnit-Introduction-In-5-Steps/test/com/in28minutes/junit/MyMathTest.java new file mode 100644 index 0000000..705445a --- /dev/null +++ b/03.JUnit-Introduction-In-5-Steps/test/com/in28minutes/junit/MyMathTest.java @@ -0,0 +1,47 @@ +package com.in28minutes.junit; + +import static org.junit.Assert.assertEquals; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +public class MyMathTest { + MyMath myMath = new MyMath(); + + @Before + public void before() { + System.out.println("Before"); + } + + @After + public void after() { + System.out.println("After"); + } + + @BeforeClass + public static void beforeClass() { + System.out.println("Before Class"); + } + + @AfterClass + public static void afterClass() { + System.out.println("After Class"); + } + + // MyMath.sum + // 1,2,3 => 6 + @Test + public void sum_with3numbers() { + System.out.println("Test1"); + assertEquals(6, myMath.sum(new int[] { 1, 2, 3 })); + } + + @Test + public void sum_with1number() { + System.out.println("Test2"); + assertEquals(3, myMath.sum(new int[] { 3 })); + } +} diff --git a/04.Mockito-Introduction-In-5-Steps/.DS_Store b/04.Mockito-Introduction-In-5-Steps/.DS_Store new file mode 100644 index 0000000..0c7f5fb Binary files /dev/null and b/04.Mockito-Introduction-In-5-Steps/.DS_Store differ diff --git a/04.Mockito-Introduction-In-5-Steps/.gitignore b/04.Mockito-Introduction-In-5-Steps/.gitignore new file mode 100644 index 0000000..2af7cef --- /dev/null +++ b/04.Mockito-Introduction-In-5-Steps/.gitignore @@ -0,0 +1,24 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ \ No newline at end of file diff --git a/04.Mockito-Introduction-In-5-Steps/.mvn/wrapper/maven-wrapper.jar b/04.Mockito-Introduction-In-5-Steps/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..9cc84ea Binary files /dev/null and b/04.Mockito-Introduction-In-5-Steps/.mvn/wrapper/maven-wrapper.jar differ diff --git a/04.Mockito-Introduction-In-5-Steps/.mvn/wrapper/maven-wrapper.properties b/04.Mockito-Introduction-In-5-Steps/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..c315043 --- /dev/null +++ b/04.Mockito-Introduction-In-5-Steps/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip diff --git a/04.Mockito-Introduction-In-5-Steps/code-21July2017.zip b/04.Mockito-Introduction-In-5-Steps/code-21July2017.zip new file mode 100644 index 0000000..2353384 Binary files /dev/null and b/04.Mockito-Introduction-In-5-Steps/code-21July2017.zip differ diff --git a/04.Mockito-Introduction-In-5-Steps/mvnw b/04.Mockito-Introduction-In-5-Steps/mvnw new file mode 100755 index 0000000..5bf251c --- /dev/null +++ b/04.Mockito-Introduction-In-5-Steps/mvnw @@ -0,0 +1,225 @@ +#!/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 +# +# http://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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + 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" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +echo $MAVEN_PROJECTBASEDIR +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/04.Mockito-Introduction-In-5-Steps/mvnw.cmd b/04.Mockito-Introduction-In-5-Steps/mvnw.cmd new file mode 100644 index 0000000..019bd74 --- /dev/null +++ b/04.Mockito-Introduction-In-5-Steps/mvnw.cmd @@ -0,0 +1,143 @@ +@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 http://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 Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/04.Mockito-Introduction-In-5-Steps/pom.xml b/04.Mockito-Introduction-In-5-Steps/pom.xml new file mode 100644 index 0000000..a088b5b --- /dev/null +++ b/04.Mockito-Introduction-In-5-Steps/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + com.in28minutes.mockito + mockito-demo + 0.0.1-SNAPSHOT + jar + + mockito-demo + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.BUILD-SNAPSHOT + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + diff --git a/04.Mockito-Introduction-In-5-Steps/readme.md b/04.Mockito-Introduction-In-5-Steps/readme.md new file mode 100644 index 0000000..535ff02 --- /dev/null +++ b/04.Mockito-Introduction-In-5-Steps/readme.md @@ -0,0 +1,366 @@ +## First 5 Steps in Mockito + +Mockito is the most famous mocking framework in Java. + +- Git Repository - https://github.com/in28minutes/getting-started-in-5-steps +- Pre-requisites + - Java & Eclipse - https://www.youtube.com/playlist?list=PLBBog2r6uMCSmMVTW_QmDLyASBvovyAO3 + - JUnit - https://courses.in28minutes.com/p/junit-tutorial-for-beginners +- Easier Static Imports + - Window > Preferences > Java > Editor > Content Assist > Favorites + - org.junit.Assert + - org.mockito.BDDMockito + - org.mockito.Mockito + - org.hamcrest.Matchers + - org.hamcrest.CoreMatchers +- More information + - Visit Mockito Official Documentation - [Mockito Documentation] (http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html) + +- Step 1 : Setting up an example using http://start.spring.io. +- Step 2 : Using a Stubs - Disadvantages +- Step 3 : Your first mock. +- Step 4 : Using Mockito Annotations - @Mock, @InjectMocks, @RunWith(MockitoJUnitRunner.class) +- Step 5 : Mocking List interface +- Next Steps + +## Complete Code Example + + +### /pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.mockito + mockito-demo + 0.0.1-SNAPSHOT + jar + + mockito-demo + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.BUILD-SNAPSHOT + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + +``` +--- + +### /src/main/java/com/in28minutes/mockito/mockitodemo/DataService.java + +```java +package com.in28minutes.mockito.mockitodemo; + +public interface DataService { + int[] retrieveAllData(); +} +``` +--- + +### /src/main/java/com/in28minutes/mockito/mockitodemo/MockitoDemoApplication.java + +```java +package com.in28minutes.mockito.mockitodemo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MockitoDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(MockitoDemoApplication.class, args); + } +} +``` +--- + +### /src/main/java/com/in28minutes/mockito/mockitodemo/SomeBusinessImpl.java + +```java +package com.in28minutes.mockito.mockitodemo; + +public class SomeBusinessImpl { + private DataService dataService; + + public SomeBusinessImpl(DataService dataService) { + super(); + this.dataService = dataService; + } + + int findTheGreatestFromAllData() { + int[] data = dataService.retrieveAllData(); + int greatest = Integer.MIN_VALUE; + + for (int value : data) { + if (value > greatest) { + greatest = value; + } + } + return greatest; + } +} +``` +--- + +### /src/main/resources/application.properties + +```properties +``` +--- + +### /src/test/java/com/in28minutes/mockito/mockitodemo/ListTest.java + +```java +package com.in28minutes.mockito.mockitodemo; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; + +import org.junit.Test; +import org.mockito.Mockito; + +public class ListTest { + + @Test + public void testSize() { + List listMock = mock(List.class); + when(listMock.size()).thenReturn(10); + assertEquals(10, listMock.size()); + assertEquals(10, listMock.size()); + } + + @Test + public void testSize_multipleReturns() { + List listMock = mock(List.class); + when(listMock.size()).thenReturn(10).thenReturn(20); + assertEquals(10, listMock.size()); + assertEquals(20, listMock.size()); + assertEquals(20, listMock.size()); + } + + @Test + public void testGet_SpecificParameter() { + List listMock = mock(List.class); + when(listMock.get(0)).thenReturn("SomeString"); + assertEquals("SomeString", listMock.get(0)); + assertEquals(null, listMock.get(1)); + } + + @Test + public void testGet_GenericParameter() { + List listMock = mock(List.class); + when(listMock.get(Mockito.anyInt())).thenReturn("SomeString"); + assertEquals("SomeString", listMock.get(0)); + assertEquals("SomeString", listMock.get(1)); + } +} +``` +--- + +### /src/test/java/com/in28minutes/mockito/mockitodemo/MockitoDemoApplicationTests.java + +```java +package com.in28minutes.mockito.mockitodemo; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class MockitoDemoApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- + +### /src/test/java/com/in28minutes/mockito/mockitodemo/SomeBusinessMockAnnotationsTest.java + +```java +package com.in28minutes.mockito.mockitodemo; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class SomeBusinessMockAnnotationsTest { + + @Mock + DataService dataServiceMock; + + @InjectMocks + SomeBusinessImpl businessImpl; + + @Test + public void testFindTheGreatestFromAllData() { + when(dataServiceMock.retrieveAllData()).thenReturn(new int[] { 24, 15, 3 }); + assertEquals(24, businessImpl.findTheGreatestFromAllData()); + } + + @Test + public void testFindTheGreatestFromAllData_ForOneValue() { + when(dataServiceMock.retrieveAllData()).thenReturn(new int[] { 15 }); + assertEquals(15, businessImpl.findTheGreatestFromAllData()); + } + + @Test + public void testFindTheGreatestFromAllData_NoValues() { + when(dataServiceMock.retrieveAllData()).thenReturn(new int[] {}); + assertEquals(Integer.MIN_VALUE, businessImpl.findTheGreatestFromAllData()); + } +} +``` +--- + +### /src/test/java/com/in28minutes/mockito/mockitodemo/SomeBusinessMockTest.java + +```java +package com.in28minutes.mockito.mockitodemo; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.Test; + +public class SomeBusinessMockTest { + + @Test + public void testFindTheGreatestFromAllData() { + DataService dataServiceMock = mock(DataService.class); + when(dataServiceMock.retrieveAllData()).thenReturn(new int[] { 24, 15, 3 }); + SomeBusinessImpl businessImpl = new SomeBusinessImpl(dataServiceMock); + int result = businessImpl.findTheGreatestFromAllData(); + assertEquals(24, result); + } + + @Test + public void testFindTheGreatestFromAllData_ForOneValue() { + DataService dataServiceMock = mock(DataService.class); + when(dataServiceMock.retrieveAllData()).thenReturn(new int[] { 15 }); + SomeBusinessImpl businessImpl = new SomeBusinessImpl(dataServiceMock); + int result = businessImpl.findTheGreatestFromAllData(); + assertEquals(15, result); + } + +} +``` +--- + +### /src/test/java/com/in28minutes/mockito/mockitodemo/SomeBusinessStubTest.java + +```java +package com.in28minutes.mockito.mockitodemo; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class SomeBusinessStubTest { + @Test + public void testFindTheGreatestFromAllData() { + SomeBusinessImpl businessImpl = new SomeBusinessImpl(new DataServiceStub()); + int result = businessImpl.findTheGreatestFromAllData(); + assertEquals(24, result); + + } + +} + +class DataServiceStub implements DataService { + @Override + public int[] retrieveAllData() { + return new int[] { 24, 6, 15 }; + } +} +``` +--- diff --git a/04.Mockito-Introduction-In-5-Steps/src/.DS_Store b/04.Mockito-Introduction-In-5-Steps/src/.DS_Store new file mode 100644 index 0000000..7b5c75f Binary files /dev/null and b/04.Mockito-Introduction-In-5-Steps/src/.DS_Store differ diff --git a/04.Mockito-Introduction-In-5-Steps/src/main/.DS_Store b/04.Mockito-Introduction-In-5-Steps/src/main/.DS_Store new file mode 100644 index 0000000..0ea0534 Binary files /dev/null and b/04.Mockito-Introduction-In-5-Steps/src/main/.DS_Store differ diff --git a/04.Mockito-Introduction-In-5-Steps/src/main/java/.DS_Store b/04.Mockito-Introduction-In-5-Steps/src/main/java/.DS_Store new file mode 100644 index 0000000..0a8bc21 Binary files /dev/null and b/04.Mockito-Introduction-In-5-Steps/src/main/java/.DS_Store differ diff --git a/04.Mockito-Introduction-In-5-Steps/src/main/java/com/.DS_Store b/04.Mockito-Introduction-In-5-Steps/src/main/java/com/.DS_Store new file mode 100644 index 0000000..aa7381c Binary files /dev/null and b/04.Mockito-Introduction-In-5-Steps/src/main/java/com/.DS_Store differ diff --git a/04.Mockito-Introduction-In-5-Steps/src/main/java/com/in28minutes/.DS_Store b/04.Mockito-Introduction-In-5-Steps/src/main/java/com/in28minutes/.DS_Store new file mode 100644 index 0000000..cd0e671 Binary files /dev/null and b/04.Mockito-Introduction-In-5-Steps/src/main/java/com/in28minutes/.DS_Store differ diff --git a/04.Mockito-Introduction-In-5-Steps/src/main/java/com/in28minutes/mockito/.DS_Store b/04.Mockito-Introduction-In-5-Steps/src/main/java/com/in28minutes/mockito/.DS_Store new file mode 100644 index 0000000..fb9ab74 Binary files /dev/null and b/04.Mockito-Introduction-In-5-Steps/src/main/java/com/in28minutes/mockito/.DS_Store differ diff --git a/04.Mockito-Introduction-In-5-Steps/src/main/java/com/in28minutes/mockito/mockitodemo/DataService.java b/04.Mockito-Introduction-In-5-Steps/src/main/java/com/in28minutes/mockito/mockitodemo/DataService.java new file mode 100644 index 0000000..9d841d0 --- /dev/null +++ b/04.Mockito-Introduction-In-5-Steps/src/main/java/com/in28minutes/mockito/mockitodemo/DataService.java @@ -0,0 +1,5 @@ +package com.in28minutes.mockito.mockitodemo; + +public interface DataService { + int[] retrieveAllData(); +} \ No newline at end of file diff --git a/04.Mockito-Introduction-In-5-Steps/src/main/java/com/in28minutes/mockito/mockitodemo/MockitoDemoApplication.java b/04.Mockito-Introduction-In-5-Steps/src/main/java/com/in28minutes/mockito/mockitodemo/MockitoDemoApplication.java new file mode 100644 index 0000000..3c89edf --- /dev/null +++ b/04.Mockito-Introduction-In-5-Steps/src/main/java/com/in28minutes/mockito/mockitodemo/MockitoDemoApplication.java @@ -0,0 +1,12 @@ +package com.in28minutes.mockito.mockitodemo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MockitoDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(MockitoDemoApplication.class, args); + } +} diff --git a/04.Mockito-Introduction-In-5-Steps/src/main/java/com/in28minutes/mockito/mockitodemo/SomeBusinessImpl.java b/04.Mockito-Introduction-In-5-Steps/src/main/java/com/in28minutes/mockito/mockitodemo/SomeBusinessImpl.java new file mode 100644 index 0000000..70ab4e7 --- /dev/null +++ b/04.Mockito-Introduction-In-5-Steps/src/main/java/com/in28minutes/mockito/mockitodemo/SomeBusinessImpl.java @@ -0,0 +1,22 @@ +package com.in28minutes.mockito.mockitodemo; + +public class SomeBusinessImpl { + private DataService dataService; + + public SomeBusinessImpl(DataService dataService) { + super(); + this.dataService = dataService; + } + + int findTheGreatestFromAllData() { + int[] data = dataService.retrieveAllData(); + int greatest = Integer.MIN_VALUE; + + for (int value : data) { + if (value > greatest) { + greatest = value; + } + } + return greatest; + } +} \ No newline at end of file diff --git a/04.Mockito-Introduction-In-5-Steps/src/main/resources/application.properties b/04.Mockito-Introduction-In-5-Steps/src/main/resources/application.properties new file mode 100644 index 0000000..e69de29 diff --git a/04.Mockito-Introduction-In-5-Steps/src/test/.DS_Store b/04.Mockito-Introduction-In-5-Steps/src/test/.DS_Store new file mode 100644 index 0000000..63b728b Binary files /dev/null and b/04.Mockito-Introduction-In-5-Steps/src/test/.DS_Store differ diff --git a/04.Mockito-Introduction-In-5-Steps/src/test/java/.DS_Store b/04.Mockito-Introduction-In-5-Steps/src/test/java/.DS_Store new file mode 100644 index 0000000..0a8bc21 Binary files /dev/null and b/04.Mockito-Introduction-In-5-Steps/src/test/java/.DS_Store differ diff --git a/04.Mockito-Introduction-In-5-Steps/src/test/java/com/.DS_Store b/04.Mockito-Introduction-In-5-Steps/src/test/java/com/.DS_Store new file mode 100644 index 0000000..aa7381c Binary files /dev/null and b/04.Mockito-Introduction-In-5-Steps/src/test/java/com/.DS_Store differ diff --git a/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/.DS_Store b/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/.DS_Store new file mode 100644 index 0000000..cd0e671 Binary files /dev/null and b/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/.DS_Store differ diff --git a/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/mockito/.DS_Store b/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/mockito/.DS_Store new file mode 100644 index 0000000..fb9ab74 Binary files /dev/null and b/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/mockito/.DS_Store differ diff --git a/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/mockito/mockitodemo/ListTest.java b/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/mockito/mockitodemo/ListTest.java new file mode 100644 index 0000000..ead71c3 --- /dev/null +++ b/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/mockito/mockitodemo/ListTest.java @@ -0,0 +1,46 @@ +package com.in28minutes.mockito.mockitodemo; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.List; + +import org.junit.Test; +import org.mockito.Mockito; + +public class ListTest { + + @Test + public void testSize() { + List listMock = mock(List.class); + when(listMock.size()).thenReturn(10); + assertEquals(10, listMock.size()); + assertEquals(10, listMock.size()); + } + + @Test + public void testSize_multipleReturns() { + List listMock = mock(List.class); + when(listMock.size()).thenReturn(10).thenReturn(20); + assertEquals(10, listMock.size()); + assertEquals(20, listMock.size()); + assertEquals(20, listMock.size()); + } + + @Test + public void testGet_SpecificParameter() { + List listMock = mock(List.class); + when(listMock.get(0)).thenReturn("SomeString"); + assertEquals("SomeString", listMock.get(0)); + assertEquals(null, listMock.get(1)); + } + + @Test + public void testGet_GenericParameter() { + List listMock = mock(List.class); + when(listMock.get(Mockito.anyInt())).thenReturn("SomeString"); + assertEquals("SomeString", listMock.get(0)); + assertEquals("SomeString", listMock.get(1)); + } +} diff --git a/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/mockito/mockitodemo/MockitoDemoApplicationTests.java b/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/mockito/mockitodemo/MockitoDemoApplicationTests.java new file mode 100644 index 0000000..7897a8a --- /dev/null +++ b/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/mockito/mockitodemo/MockitoDemoApplicationTests.java @@ -0,0 +1,16 @@ +package com.in28minutes.mockito.mockitodemo; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class MockitoDemoApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/mockito/mockitodemo/SomeBusinessMockAnnotationsTest.java b/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/mockito/mockitodemo/SomeBusinessMockAnnotationsTest.java new file mode 100644 index 0000000..2fe82c4 --- /dev/null +++ b/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/mockito/mockitodemo/SomeBusinessMockAnnotationsTest.java @@ -0,0 +1,38 @@ +package com.in28minutes.mockito.mockitodemo; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class SomeBusinessMockAnnotationsTest { + + @Mock + DataService dataServiceMock; + + @InjectMocks + SomeBusinessImpl businessImpl; + + @Test + public void testFindTheGreatestFromAllData() { + when(dataServiceMock.retrieveAllData()).thenReturn(new int[] { 24, 15, 3 }); + assertEquals(24, businessImpl.findTheGreatestFromAllData()); + } + + @Test + public void testFindTheGreatestFromAllData_ForOneValue() { + when(dataServiceMock.retrieveAllData()).thenReturn(new int[] { 15 }); + assertEquals(15, businessImpl.findTheGreatestFromAllData()); + } + + @Test + public void testFindTheGreatestFromAllData_NoValues() { + when(dataServiceMock.retrieveAllData()).thenReturn(new int[] {}); + assertEquals(Integer.MIN_VALUE, businessImpl.findTheGreatestFromAllData()); + } +} diff --git a/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/mockito/mockitodemo/SomeBusinessMockTest.java b/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/mockito/mockitodemo/SomeBusinessMockTest.java new file mode 100644 index 0000000..04bf3da --- /dev/null +++ b/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/mockito/mockitodemo/SomeBusinessMockTest.java @@ -0,0 +1,29 @@ +package com.in28minutes.mockito.mockitodemo; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.Test; + +public class SomeBusinessMockTest { + + @Test + public void testFindTheGreatestFromAllData() { + DataService dataServiceMock = mock(DataService.class); + when(dataServiceMock.retrieveAllData()).thenReturn(new int[] { 24, 15, 3 }); + SomeBusinessImpl businessImpl = new SomeBusinessImpl(dataServiceMock); + int result = businessImpl.findTheGreatestFromAllData(); + assertEquals(24, result); + } + + @Test + public void testFindTheGreatestFromAllData_ForOneValue() { + DataService dataServiceMock = mock(DataService.class); + when(dataServiceMock.retrieveAllData()).thenReturn(new int[] { 15 }); + SomeBusinessImpl businessImpl = new SomeBusinessImpl(dataServiceMock); + int result = businessImpl.findTheGreatestFromAllData(); + assertEquals(15, result); + } + +} diff --git a/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/mockito/mockitodemo/SomeBusinessStubTest.java b/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/mockito/mockitodemo/SomeBusinessStubTest.java new file mode 100644 index 0000000..6628e7d --- /dev/null +++ b/04.Mockito-Introduction-In-5-Steps/src/test/java/com/in28minutes/mockito/mockitodemo/SomeBusinessStubTest.java @@ -0,0 +1,23 @@ +package com.in28minutes.mockito.mockitodemo; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class SomeBusinessStubTest { + @Test + public void testFindTheGreatestFromAllData() { + SomeBusinessImpl businessImpl = new SomeBusinessImpl(new DataServiceStub()); + int result = businessImpl.findTheGreatestFromAllData(); + assertEquals(24, result); + + } + +} + +class DataServiceStub implements DataService { + @Override + public int[] retrieveAllData() { + return new int[] { 24, 6, 15 }; + } +} \ No newline at end of file diff --git a/05.Spring-Boot-Advanced/.DS_Store b/05.Spring-Boot-Advanced/.DS_Store new file mode 100644 index 0000000..0c7f5fb Binary files /dev/null and b/05.Spring-Boot-Advanced/.DS_Store differ diff --git a/05.Spring-Boot-Advanced/.classpath b/05.Spring-Boot-Advanced/.classpath new file mode 100644 index 0000000..fae1a2b --- /dev/null +++ b/05.Spring-Boot-Advanced/.classpath @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/05.Spring-Boot-Advanced/.project b/05.Spring-Boot-Advanced/.project new file mode 100644 index 0000000..45b2121 --- /dev/null +++ b/05.Spring-Boot-Advanced/.project @@ -0,0 +1,35 @@ + + + first-springboot-project + + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + + org.springframework.ide.eclipse.core.springnature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.wst.common.project.facet.core.nature + + diff --git a/05.Spring-Boot-Advanced/.settings/.DS_Store b/05.Spring-Boot-Advanced/.settings/.DS_Store new file mode 100644 index 0000000..cb3f4e6 Binary files /dev/null and b/05.Spring-Boot-Advanced/.settings/.DS_Store differ diff --git a/05.Spring-Boot-Advanced/.settings/.jsdtscope b/05.Spring-Boot-Advanced/.settings/.jsdtscope new file mode 100644 index 0000000..b72a6a4 --- /dev/null +++ b/05.Spring-Boot-Advanced/.settings/.jsdtscope @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/05.Spring-Boot-Advanced/.settings/org.eclipse.core.resources.prefs b/05.Spring-Boot-Advanced/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..29abf99 --- /dev/null +++ b/05.Spring-Boot-Advanced/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/05.Spring-Boot-Advanced/.settings/org.eclipse.jdt.core.prefs b/05.Spring-Boot-Advanced/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..6e80039 --- /dev/null +++ b/05.Spring-Boot-Advanced/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/05.Spring-Boot-Advanced/.settings/org.eclipse.m2e.core.prefs b/05.Spring-Boot-Advanced/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/05.Spring-Boot-Advanced/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/05.Spring-Boot-Advanced/.settings/org.eclipse.wst.common.component b/05.Spring-Boot-Advanced/.settings/org.eclipse.wst.common.component new file mode 100644 index 0000000..dd59085 --- /dev/null +++ b/05.Spring-Boot-Advanced/.settings/org.eclipse.wst.common.component @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/05.Spring-Boot-Advanced/.settings/org.eclipse.wst.common.project.facet.core.xml b/05.Spring-Boot-Advanced/.settings/org.eclipse.wst.common.project.facet.core.xml new file mode 100644 index 0000000..efa5461 --- /dev/null +++ b/05.Spring-Boot-Advanced/.settings/org.eclipse.wst.common.project.facet.core.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/05.Spring-Boot-Advanced/.settings/org.eclipse.wst.jsdt.ui.superType.container b/05.Spring-Boot-Advanced/.settings/org.eclipse.wst.jsdt.ui.superType.container new file mode 100644 index 0000000..3bd5d0a --- /dev/null +++ b/05.Spring-Boot-Advanced/.settings/org.eclipse.wst.jsdt.ui.superType.container @@ -0,0 +1 @@ +org.eclipse.wst.jsdt.launching.baseBrowserLibrary \ No newline at end of file diff --git a/05.Spring-Boot-Advanced/.settings/org.eclipse.wst.jsdt.ui.superType.name b/05.Spring-Boot-Advanced/.settings/org.eclipse.wst.jsdt.ui.superType.name new file mode 100644 index 0000000..05bd71b --- /dev/null +++ b/05.Spring-Boot-Advanced/.settings/org.eclipse.wst.jsdt.ui.superType.name @@ -0,0 +1 @@ +Window \ No newline at end of file diff --git a/05.Spring-Boot-Advanced/.settings/org.eclipse.wst.validation.prefs b/05.Spring-Boot-Advanced/.settings/org.eclipse.wst.validation.prefs new file mode 100644 index 0000000..04cad8c --- /dev/null +++ b/05.Spring-Boot-Advanced/.settings/org.eclipse.wst.validation.prefs @@ -0,0 +1,2 @@ +disabled=06target +eclipse.preferences.version=1 diff --git a/05.Spring-Boot-Advanced/README.md b/05.Spring-Boot-Advanced/README.md new file mode 100644 index 0000000..999d6eb --- /dev/null +++ b/05.Spring-Boot-Advanced/README.md @@ -0,0 +1,143 @@ +# SpringBootForBeginners +Spring Boot Tutorial For Beginners +* [Installing Eclipse, Maven and Java](#installing-tools) +* [Running Examples](#running-examples) +* [Course Overview](#course-overview) + - [Course Steps](#step-list) + - [Expectations](#expectations) +* [About in28Minutes](#about-in28minutes) + - [Our Beliefs](#our-beliefs) + - [Our Approach](#our-approach) + - [Find Us](#useful-links) + - [Other Courses](#other-courses) + +## Installing Tools +- PDF : https://github.com/in28minutes/SpringIn28Minutes/blob/master/InstallationGuide-JavaEclipseAndMaven_v2.pdf +- Video : https://www.youtube.com/playlist?list=PLBBog2r6uMCSmMVTW_QmDLyASBvovyAO3 +- GIT Repository : https://github.com/in28minutes/getting-started-in-5-steps + +## Course Overview + +### Introduction + +Spring Boot has a lot of magic going for it. Developing Services with it is cool and fun. + +Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”. Most Spring Boot applications need very little Spring configuration. + +In this course, you will learn the cool things about Spring Boot and Spring Boot Starter Projects. We will develop a basic REST Service to manage questions of a survey. + +You will learn about Spring Boot step by step - in more than 25 steps. This course would be a perfect first step as an introduction to Spring Boot. + +You will be using Spring Boot and get introduced to REST Services, Spring Security (Authentication and Authorization), Maven (dependencies management), Eclipse (IDE) and Tomcat Embedded Web Server. We will help you set up each one of these. + +You will learn about + +- Basics of Spring Boot +- Basics of Auto Configuration and Spring Boot Magic +- Spring Boot Starter Projects +- Spring Initializr +- Basic REST Services using Spring Boot Starter Web +- REST Service Content Negotiation with JSON and XML +- Embedded servlet containers : Tomcat, Jetty and Undertow +- Writing Unit and Integration tests using Spring Boot Starter Test +- Profiles and Dynamic Configuration with Spring Boot +- Spring Boot Data JPA +- Spring Boot Actuator +- Spring Security +- Spring Boot Developer Tools and LiveReload + +Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”. We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need very little Spring configuration. + +With Microservices, focus is shifting to getting started with developing web applications quickly. Spring Boot enables this. + + +### Goals +- Provide quick start for projects with Spring. +- Be opinionated but provide options. +- Provide a range of non-functional features that are common to large classes of projects (e.g. embedded servers, security, metrics, health checks, externalized configuration). +- Absolutely no code generation and no requirement for XML configuration. + +### Step List +- Step 01: Setup and Launch Spring Boot Application with Maven and Eclipse +- Step 02: Creating your first RestController +- Step 03: Understanding Spring Boot Magic : Spring Boot Starter Web +- Step 04: Understanding Spring Boot Magic : Spring Boot Starter Parent +- Step 05: Spring Boot vs Spring +- Step 06: Create all Services for Survey and Questions +- Step 07: What is REST? Creating REST Service with @GetMapping and @PathVariable +- Step 08: Second REST Service to retrieve a specific question +- Step 09: Spring Boot Developer Tools and LiveReload : Develop faster! +- Step 10: Create a REST Service to add a new question to survey : @PostMapping, Postman +- Step 11: Understand Content Negotiation. Deliver XML Responses from the REST Services +- Step 12: Spring Initializr : Create Spring Boot Projects on the fly! +- Step 13: Spring Boot Actuator : Monitor your Spring Boot applications! +- Step 14: Understanding Embedded servlet containers : Switch to Jetty or Undertow +- Step 15: Adding Dynamic Configuration to your application : YAML & More.. +- Step 16: Basics of Profiles +- Step 17: Advanced Application Configuration with Type Safe Configuration Properties +- Step 18: Spring Boot Starter : Spring Data JPA with CommandLineRunner +- Step 19: In Memory Database H2 Console and add a new JPA Repository Method +- Step 20: Spring Boot Starter : Introduction to Spring Data Rest +- Step 21: Spring Boot Integration Test +- Step 22: Adding Integration Test for POST Request +- Step 23: Small Refactoring to organise ourselves +- Step 24: Writing Unit Tests with Spring Boot and Mockito +- Step 25: Writing Unit test for createTodo +- Step 26: Securing our services with Basic Authentication using Spring Security +- Step 27: Configure Spring Security user roles for survey and other services +- Step 28: A Deep Dive into Spring Boot Auto Configuration + +### Expectations +- You should know Java. You should understand usage of Annotations. +- You should understand the basics of Spring framework. +- A basic understanding of JPA, Spring Security will be useful. +- You are NOT expected to have any experience with Eclipse or Maven. +- We will help you install Eclipse and get up and running with Maven. + +## Let's have some fun +- What are we waiting for? +- Let's have some fun with Spring Boot in 25 Steps. +- I had fun creating this course and hope you would too. +- Thanks for your interest in Our Course + - I hope you’re as excited as I am! + - If you’re ready to learn more and sign up for the course, + - go ahead and hit that Enroll button, + - or take a test drive by using the Free Preview feature. +- See you in the course! + +## About in28Minutes +- At in28Minutes, we ask ourselves one question everyday. How do we help you learn effectively - that is more quickly and retain more of what you have learnt? +- We use Problem-Solution based Step-By-Step Hands-on Approach With Practical, Real World Application Examples. +- Our success on Udemy and Youtube (2 Million Views & 12K Subscribers) speaks volumes about the success of our approach. +- While our primary expertise is on Development, Design & Architecture Java & Related Frameworks (Spring, Struts, Hibernate) we are expanding into the front-end world (Bootstrap, JQuery, Angular JS). + +### Our Beliefs +- Best Courses are interactive and fun. +- Foundations for building high quality applications are best laid down while learning. + +### Our Approach +- Problem Solution based Step by Step Hands-on Learning +- Practical, Real World Application Examples. +- We use 80-20 Rule. We discuss 20% things used 80% of time in depth. We touch upon other things briefly equipping you with enough knowledge to find out more on your own. +- We will be developing a demo application in the course, which could be reused in your projects, saving hours of your effort. +- We love open source and therefore, All our code is open source too and available on Github. + +### Other Courses + +- [Check out all our courses with 100,000 Students](https://courses.in28minutes.com/courses) +- [25 Videos and Articles for Beginners on Spring Boot](http://www.springboottutorial.com/spring-boot-tutorials-for-beginners) +- Our Best Courses with 66,000 Students and 4,000 5-Star Ratings + * [Java Interview Guide : 200+ Interview Questions and Answers](https://www.udemy.com/java-interview-questions-and-answers/?couponCode=JAVA_INTER_GIT) + * [Mockito Tutorial : Learn mocking with 25 Junit Examples](https://www.udemy.com/mockito-tutorial-with-junit-examples/?couponCode=MOCKITO_GIT) + * [Java EE Made Easy - Patterns, Architecture and Frameworks](https://www.udemy.com/java-ee-design-patterns-architecture-and-frameworks/?couponCode=EEPATTERNS-GIT) + * [Spring MVC For Beginners : Build Java Web App in 25 Steps](https://www.udemy.com/spring-mvc-tutorial-for-beginners-step-by-step/?couponCode=SPRINGMVC-GIT) + * [JSP Servlets For Beginners : Build Java Web App in 25 Steps](https://www.udemy.com/learn-java-servlets-and-jsp-web-application-in-25-steps/?couponCode=JSPSRVLT-GIT) + * [Maven Tutorial - Manage Java Dependencies in 25 Steps](https://www.udemy.com/learn-maven-java-dependency-management-in-20-steps/?couponCode=MAVEN_GIT) + * [Java OOPS in 1 Hours](https://www.udemy.com/learn-object-oriented-programming-in-java/?couponCode=OOPS-GIT) + * [C Puzzle for Interview](https://www.udemy.com/c-puzzles-for-beginners/?couponCode=CPUZZLES-GIT) + +### Useful Links +- [Our Website](http://www.in28minutes.com) +- [Facebook](http://facebook.com/in28minutes) +- [Twitter](http://twitter.com/in28minutes) +- [Google Plus](https://plus.google.com/u/3/110861829188024231119) diff --git a/05.Spring-Boot-Advanced/Step01.md b/05.Spring-Boot-Advanced/Step01.md new file mode 100644 index 0000000..34fea76 --- /dev/null +++ b/05.Spring-Boot-Advanced/Step01.md @@ -0,0 +1,74 @@ +## What You Will Learn during this Step: +- Set up an Maven Project with Eclipse. + - Intellij Link : https://www.jetbrains.com/help/idea/2016.2/getting-started-with-maven.html#create_maven_project +- Copy Two Files pom.xml and Application.java +- Launch Your First Spring Boot Application. +- You will be introduced to Maven + - Dependency Management + +## Cool thing to note! +- Without a lot of configuration, we are up and running with a web application + - Refer https://github.com/in28minutes/SpringMvcStepByStep/blob/master/Step15.md to understand the sort of stuff - web.xml, dispatcher servlet configuration, maven dependency management and plugins - that are need to launch a typical web application without Spring Boot! + +## What You Will NOT Learn during this Step: +- Spring Boot does a lot of magic. This magic is called Auto Configuration. We will discuss about different terms related to Spring Boot - Starter Parent, Starter projects, Auto configuration - in depth during our first 10 steps. +- As far as this step is concerned, we will focus on getting up and running with Spring Boot. We will understand all the magic a little later. +- We will copy a lot of code in this step - just to avoid typos + +## Exercises +- If you are comfortable with Spring, try to create a few dependencies and see if are automatically auto-wired! + +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + +} +``` diff --git a/05.Spring-Boot-Advanced/Step01.zip b/05.Spring-Boot-Advanced/Step01.zip new file mode 100644 index 0000000..54dc611 Binary files /dev/null and b/05.Spring-Boot-Advanced/Step01.zip differ diff --git a/05.Spring-Boot-Advanced/Step02.md b/05.Spring-Boot-Advanced/Step02.md new file mode 100644 index 0000000..092555c --- /dev/null +++ b/05.Spring-Boot-Advanced/Step02.md @@ -0,0 +1,134 @@ +## What You Will Learn during this Step: +- Lets add a RestController with a dependency and see Spring Boot Magic live + +## Theory Break : Quick Spring and Spring MVC Primer +- What is dependency? +- @Component +- @Autowired +- @RestController + +## Useful Snippets and References +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } +} + +@Component +class WelcomeService { + + public String retrieveWelcomeMessage() { + //Complex Method + return "Good Morning updated"; + } +} +``` +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/service/WelcomeService.java +``` +package com.in28minutes.service; + +import org.springframework.stereotype.Component; + +@Component +public class WelcomeService { + + public String retrieveWelcomeMessage() { + //Complex Method + return "Good Morning updated"; + } +} +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan("com.in28minutes") +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.in28minutes.service.WelcomeService; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } +} +``` diff --git a/05.Spring-Boot-Advanced/Step02.zip b/05.Spring-Boot-Advanced/Step02.zip new file mode 100644 index 0000000..ab760d7 Binary files /dev/null and b/05.Spring-Boot-Advanced/Step02.zip differ diff --git a/05.Spring-Boot-Advanced/Step03.md b/05.Spring-Boot-Advanced/Step03.md new file mode 100644 index 0000000..c917669 --- /dev/null +++ b/05.Spring-Boot-Advanced/Step03.md @@ -0,0 +1,117 @@ +## What You Will Learn during this Step: +- First installment of revealing how magic happens with Spring Boot. As a Spring Boot developer, you need to understand what's happening beneath the hood of Spring Boot! +- spring-boot-starter-web : starter for building applications with Spring MVC. Tomcat is default embedded container. +- We already added this starter in the first step! Now we will explore the features it provides +- We will enable logging in DEBUG mode to understand further + +##spring-boot-starter-web +- Spring Boot Starter Web brings all dependencies needed to build normal and RESTful web applications. Look at the dependency tree. + - All the dependencies are added in because of spring-boot-starter-web + - Also look at /META-INF/spring.provides inside the spring-boot-starter-web.jar +- Spring Boot Starter Web auto configures things needed to startup a web application. Look at the log + - Mapping servlet: 'dispatcherServlet' to [/] + - Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest) + - Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] + - Look at package org.springframework.boot.autoconfigure.web in spring-boot-autoconfigure-*.jar +- Go to url http://localhost:8080/some-non-existing-url + +## Useful Snippets +/src/main/resources/application.properties +``` +logging.level.org.springframework: DEBUG +``` + +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeService.java +``` +package com.in28minutes.springboot; + +import org.springframework.stereotype.Component; + +@Component +public class WelcomeService { + + public String retrieveWelcomeMessage() { + //Complex Method + return "Good Morning updated"; + } +} +``` +### src/main/resources/application.properties +``` +logging.level.org.springframework: DEBUG +``` diff --git a/05.Spring-Boot-Advanced/Step04.md b/05.Spring-Boot-Advanced/Step04.md new file mode 100644 index 0000000..30f4e37 --- /dev/null +++ b/05.Spring-Boot-Advanced/Step04.md @@ -0,0 +1,28 @@ +## What You Will Learn during this Step: +- Understand Starter Parent +- How to override things defined in Starter Parent +- Other starter projects + +## Starter Parent +- Dependency Versions +- Java Versions +- Default Plugins + +## Other Starter Projects +- spring-boot-starter-web-services +- spring-boot-starter-test +- spring-boot-starter-jdbc +- spring-boot-starter-security +- spring-boot-starter-data-jpa +- spring-boot-starter-data-rest +- More at https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#using-boot-starter + +## Useful Snippets and References +First Snippet +``` + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + +``` diff --git a/05.Spring-Boot-Advanced/Step05.md b/05.Spring-Boot-Advanced/Step05.md new file mode 100644 index 0000000..ea4164e --- /dev/null +++ b/05.Spring-Boot-Advanced/Step05.md @@ -0,0 +1,48 @@ +## What You Will Learn during this Step: +- Spring Boot vs Spring +- What Spring Boot is Not! + +## Spring Boot vs Spring + +### Spring +- Spring is just a dependency injection framework. Spring focuses on the "plumbing" of enterprise applications so that teams can focus on application-level business logic, without unnecessary ties to specific deployment environments. +- First half of the 2000 decade! EJBs +- EJBs were NOT easy to develop. + - Write a lot of xml and plumbing code to get EJBs running + - Impossible to Unit Test + - Alternative - Writing simple JDBC Code involved a lot of plumbing +- Spring framework started with aim of making Java EE development simpler. + - Goals + - Make applications testable. i.e. easier to write unit tests + - Reduce plumbing code of JDBC and JMS + - Simple architecture. Minus EJB. + - Integrates well with other popular frameworks. + +### Applications with Spring Framework +- Over the next few years, a number of applications were developed with Spring Framework + - Testable but + - Lot of configuration (XML and Java) + - Developing Spring Based application need configuration of a lot of beans! + - Integration with other frameworks need configuration as well! +- In the last few years, focus is moving from monolith applications to microservices. We need to be able to start project quickly. Minimum or Zero start up time + - Framework Setup + - Deployment - Configurability + - Logging, Transaction Management + - Monitoring + - Web Server Configuration + +### Spring Boot +- Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”. +- We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. +- Example Problem Statements + - You want to add Hibernate to your project. You dont worry about configuring a data source and a session factory. I will do if for you! +- Goals + - Provide quick start for projects with Spring. + - Be opinionated but provide options. + - Provide a range of non-functional features that are common to large classes of projects (e.g. embedded servers, security, metrics, health checks, externalized configuration). + +#### What Spring Boot is NOT? +- It’s not an app or a web server +- Does not implement any specific framework - for example, JPA or JMS +- Does not generate code + diff --git a/05.Spring-Boot-Advanced/Step06.md b/05.Spring-Boot-Advanced/Step06.md new file mode 100644 index 0000000..2d6e809 --- /dev/null +++ b/05.Spring-Boot-Advanced/Step06.md @@ -0,0 +1,344 @@ +## What You Will Learn during this Step: +- We want to prepare for creating a Rest Service + - Survey + - Question + - SurveyService +- We use hard-coded data to get started + +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Question.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Question other = (Question) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Survey.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + @Override + public String toString() { + return "Survey [id=" + id + ", title=" + title + ", description=" + + description + ", questions=" + questions + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/service/SurveyService.java +``` +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", Arrays + .asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeService.java +``` +package com.in28minutes.springboot; + +import org.springframework.stereotype.Component; + +@Component +public class WelcomeService { + + public String retrieveWelcomeMessage() { + //Complex Method + return "Good Morning updated"; + } +} +``` +### src/main/resources/application.properties +``` +logging.level.org.springframework: DEBUG +``` diff --git a/05.Spring-Boot-Advanced/Step06.zip b/05.Spring-Boot-Advanced/Step06.zip new file mode 100644 index 0000000..0ce0f73 Binary files /dev/null and b/05.Spring-Boot-Advanced/Step06.zip differ diff --git a/05.Spring-Boot-Advanced/Step07.md b/05.Spring-Boot-Advanced/Step07.md new file mode 100644 index 0000000..e955ea0 --- /dev/null +++ b/05.Spring-Boot-Advanced/Step07.md @@ -0,0 +1,407 @@ +## What You Will Learn during this Step: +- Create a REST Service for Retrieving all questions for a survey + - Autowire SurveyService + - Create @GetMapping("/surveys/{surveyId}/questions") + - Use @PathVariable String surveyId +- http://localhost:8080/surveys/Survey1/questions/ + - How does the Bean get converted to a JSON? + - Auto Configuration : If Jackson jar is on the class path, message converters are auto created! (Search in log :Creating shared instance of singleton bean 'mappingJackson2HttpMessageConverter') + +## Some Theory +- What is REST? +- Architectural style for the web. REST specifies a set of constraints. + - Client - Server : Server (service provider) should be different from a client (service consumer). + - Enables loose coupling and independent evolution of server and client as new technologies emerge. + - Each service should be stateless. + - Each Resource has a resource identifier. + - It should be possible to cache response. + - Consumer of the service may not have a direct connection to the Service Provider. Response might be sent from a middle layer cache. + - A resource can have multiple representations. Resource can modified through a message in any of the these representations. + +## Useful Snippets and References +- JSON View : https://jsonview.com/ +First Snippet +``` +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } +} +``` + +## Exercise +- Try to think about how the URI for retrieving the details of a specific question should be! + +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-devtools + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + +} +``` +### src/main/java/com/in28minutes/springboot/controller/SurveyController.java +``` +package com.in28minutes.springboot.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } +} +``` +### src/main/java/com/in28minutes/springboot/model/Question.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Question other = (Question) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Survey.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + @Override + public String toString() { + return "Survey [id=" + id + ", title=" + title + ", description=" + + description + ", questions=" + questions + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/service/SurveyService.java +``` +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", Arrays + .asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeService.java +``` +package com.in28minutes.springboot; + +import org.springframework.stereotype.Component; + +@Component +public class WelcomeService { + + public String retrieveWelcomeMessage() { + //Complex Method + return "Good Morning updated"; + } +} +``` +### src/main/resources/application.properties +``` +logging.level.org.springframework: DEBUG +``` diff --git a/05.Spring-Boot-Advanced/Step08.md b/05.Spring-Boot-Advanced/Step08.md new file mode 100644 index 0000000..5ecb3c2 --- /dev/null +++ b/05.Spring-Boot-Advanced/Step08.md @@ -0,0 +1,401 @@ +## What You Will Learn during this Step: +- Adding the second method to rest service to retrieve a specific question +- This will be a very short step +- http://localhost:8080/surveys/Survey1/questions/Question1 +- Different Request Methods + - GET - Retrieve details of a resource + - POST - Create a new resource + - PUT - Update an existing resource + - PATCH - Update part of a resource + - DELETE - Delete a resource + +## Useful Snippets and References +First Snippet +``` + @GetMapping(path = "/surveys/{surveyId}/questions/{questionId}") + public Question retrieveQuestion(@PathVariable String surveyId, + @PathVariable String questionId) { + return surveyService.retrieveQuestion(surveyId, questionId); + } + +``` + +## Exercises +- Write the method to retrieve all surveys! + +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-devtools + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + +} +``` +### src/main/java/com/in28minutes/springboot/controller/SurveyController.java +``` +package com.in28minutes.springboot.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } + + // GET "/surveys/{surveyId}/questions/{questionId}" + @GetMapping("/surveys/{surveyId}/questions/{questionId}") + public Question retrieveDetailsForQuestion(@PathVariable String surveyId, + @PathVariable String questionId) { + return surveyService.retrieveQuestion(surveyId, questionId); + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Question.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Question other = (Question) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Survey.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + @Override + public String toString() { + return "Survey [id=" + id + ", title=" + title + ", description=" + + description + ", questions=" + questions + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/service/SurveyService.java +``` +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", Arrays + .asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeService.java +``` +package com.in28minutes.springboot; + +import org.springframework.stereotype.Component; + +@Component +public class WelcomeService { + + public String retrieveWelcomeMessage() { + //Complex Method + return "Good Morning updated"; + } +} +``` +### src/main/resources/application.properties +``` +logging.level.org.springframework: DEBUG +``` diff --git a/05.Spring-Boot-Advanced/Step08.zip b/05.Spring-Boot-Advanced/Step08.zip new file mode 100644 index 0000000..2e1072c Binary files /dev/null and b/05.Spring-Boot-Advanced/Step08.zip differ diff --git a/05.Spring-Boot-Advanced/Step09.md b/05.Spring-Boot-Advanced/Step09.md new file mode 100644 index 0000000..88e75a7 --- /dev/null +++ b/05.Spring-Boot-Advanced/Step09.md @@ -0,0 +1,24 @@ +## What You Will Learn during this Step: +- I hate the fact that I've to stop and start the server each time. Can somebody save me? + - Yeah. Spring Boot Developer Tools + - By default, any entry on the classpath that points to a folder will be monitored for changes. + - These will not trigger restart - /META-INF/maven, /META-INF/resources ,/resources ,/static ,/public or /templates + - Folders can be configured : spring.devtools.restart.exclude=static/**,public/** + - Additional Paths : spring.devtools.restart.additional-paths + - LiveReload http://livereload.com/extensions/ + - Technology in progress!! So, expect a few problems! +- Programming Tip + - Become an expert at your IDE - https://www.youtube.com/watch?v=dN9GYsG1v_c + +## Useful Snippets and References +First Snippet +``` + + org.springframework.boot + spring-boot-devtools + true + +``` + +## Exercises +- Make changes and see if they reflect immediately diff --git a/05.Spring-Boot-Advanced/Step10.md b/05.Spring-Boot-Advanced/Step10.md new file mode 100644 index 0000000..bc29d42 --- /dev/null +++ b/05.Spring-Boot-Advanced/Step10.md @@ -0,0 +1,442 @@ +## What You Will Learn during this Step: +- Create a REST Service to add a new question to survey + - @PostMapping("/surveys/{surveyId}/questions") + - @RequestBody Question question + - What should be Response Status for create? + - ResponseEntity.created(location).build() + - ResponseEntity.noContent().build() + - Using Postman : https://www.getpostman.com + - URL to POST to - http://localhost:8080/surveys/Survey1/questions + +## Useful Snippets and References + +Sample Body for POST Request +``` +{"description":"Second Most Populous Country in the World","correctAnswer":"India","options":["India","Russia","United States","China"]} +``` + +First Snippet +``` + @PostMapping("/surveys/{surveyId}/questions") + ResponseEntity add(@PathVariable String surveyId, + @RequestBody Question question) { + + Question createdTodo = surveyService.addQuestion(surveyId, question); + + if (createdTodo == null) { + return ResponseEntity.noContent().build(); + } + + URI location = ServletUriComponentsBuilder.fromCurrentRequest() + .path("/{id}").buildAndExpand(createdTodo.getId()).toUri(); + + return ResponseEntity.created(location).build(); + + } +``` + +## Exercises +- Create more REST services of your choice + +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-devtools + true + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + +} +``` +### src/main/java/com/in28minutes/springboot/controller/SurveyController.java +``` +package com.in28minutes.springboot.controller; + +import java.net.URI; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } + + // GET "/surveys/{surveyId}/questions/{questionId}" + @GetMapping("/surveys/{surveyId}/questions/{questionId}") + public Question retrieveDetailsForQuestion(@PathVariable String surveyId, + @PathVariable String questionId) { + return surveyService.retrieveQuestion(surveyId, questionId); + } + + // /surveys/{surveyId}/questions + @PostMapping("/surveys/{surveyId}/questions") + public ResponseEntity addQuestionToSurvey( + @PathVariable String surveyId, @RequestBody Question newQuestion) { + + Question question = surveyService.addQuestion(surveyId, newQuestion); + + if (question == null) + return ResponseEntity.noContent().build(); + + // Success - URI of the new resource in Response Header + // Status - created + // URI -> /surveys/{surveyId}/questions/{questionId} + // question.getQuestionId() + URI location = ServletUriComponentsBuilder.fromCurrentRequest().path( + "/{id}").buildAndExpand(question.getId()).toUri(); + + // Status + return ResponseEntity.created(location).build(); + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Question.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Question other = (Question) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Survey.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + @Override + public String toString() { + return "Survey [id=" + id + ", title=" + title + ", description=" + + description + ", questions=" + questions + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/service/SurveyService.java +``` +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", Arrays + .asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeService.java +``` +package com.in28minutes.springboot; + +import org.springframework.stereotype.Component; + +@Component +public class WelcomeService { + + public String retrieveWelcomeMessage() { + //Complex Method + return "Good Morning updated"; + } +} +``` +### src/main/resources/application.properties +``` +logging.level.org.springframework: DEBUG +``` diff --git a/05.Spring-Boot-Advanced/Step10.zip b/05.Spring-Boot-Advanced/Step10.zip new file mode 100644 index 0000000..8700adb Binary files /dev/null and b/05.Spring-Boot-Advanced/Step10.zip differ diff --git a/05.Spring-Boot-Advanced/Step11.md b/05.Spring-Boot-Advanced/Step11.md new file mode 100644 index 0000000..4767c0c --- /dev/null +++ b/05.Spring-Boot-Advanced/Step11.md @@ -0,0 +1,69 @@ +## What You Will Learn during this Step: +- Understand Content Negotiation + - Accept:application/xml +- Deliver XML Responses from the REST Services +- http://localhost:8080/surveys/Survey1/questions/ + +## Useful Snippets and References +First Snippet +``` + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + + +``` +Second Snippet +``` + + + Question1 + Largest Country in the World + Russia + + India + Russia + United States + China + + + + Question2 + Most Populus Country in the World + China + + India + Russia + United States + China + + + + Question3 + Highest GDP in the World + United States + + India + Russia + United States + China + + + + Question4 + Second largest english speaking country + India + + India + Russia + United States + China + + + +``` + + + +## Exercises +- Execute other services using xml and see what happens! diff --git a/05.Spring-Boot-Advanced/Step12.md b/05.Spring-Boot-Advanced/Step12.md new file mode 100644 index 0000000..c740e4b --- /dev/null +++ b/05.Spring-Boot-Advanced/Step12.md @@ -0,0 +1,7 @@ +## What You Will Learn during this Step: +- Spring Initializr +- https://start.spring.io +- Create a few projects! + +## Exercises +- Create more projects with Spring Initializr and play around with it! diff --git a/05.Spring-Boot-Advanced/Step13.md b/05.Spring-Boot-Advanced/Step13.md new file mode 100644 index 0000000..d588a59 --- /dev/null +++ b/05.Spring-Boot-Advanced/Step13.md @@ -0,0 +1,437 @@ +## What You Will Learn during this Step: +- Spring Boot Actuator + - /env, /metrics, /trace, /dump, /shutdown, /beans, / autoconfig, /configprops, /mappings +- HAL Browser + - http://localhost:8080/actuator/ +- Execute individual REST Services for each of above +- Programming Tip + - Use static code analysis - https://www.youtube.com/watch?v=rB_BaftN3nE + +## Useful Snippets and References +First Snippet +``` + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + +``` + +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + +} +``` +### src/main/java/com/in28minutes/springboot/controller/SurveyController.java +``` +package com.in28minutes.springboot.controller; + +import java.net.URI; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } + + // GET "/surveys/{surveyId}/questions/{questionId}" + @GetMapping("/surveys/{surveyId}/questions/{questionId}") + public Question retrieveDetailsForQuestion(@PathVariable String surveyId, + @PathVariable String questionId) { + return surveyService.retrieveQuestion(surveyId, questionId); + } + + // /surveys/{surveyId}/questions + @PostMapping("/surveys/{surveyId}/questions") + public ResponseEntity addQuestionToSurvey( + @PathVariable String surveyId, @RequestBody Question newQuestion) { + + Question question = surveyService.addQuestion(surveyId, newQuestion); + + if (question == null) + return ResponseEntity.noContent().build(); + + // Success - URI of the new resource in Response Header + // Status - created + // URI -> /surveys/{surveyId}/questions/{questionId} + // question.getQuestionId() + URI location = ServletUriComponentsBuilder.fromCurrentRequest().path( + "/{id}").buildAndExpand(question.getId()).toUri(); + + // Status + return ResponseEntity.created(location).build(); + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Question.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Question other = (Question) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Survey.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + @Override + public String toString() { + return "Survey [id=" + id + ", title=" + title + ", description=" + + description + ", questions=" + questions + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/service/SurveyService.java +``` +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", Arrays + .asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeService.java +``` +package com.in28minutes.springboot; + +import org.springframework.stereotype.Component; + +@Component +public class WelcomeService { + + public String retrieveWelcomeMessage() { + //Complex Method + return "Good Morning updated"; + } +} +``` +### src/main/resources/application.properties +``` +logging.level.org.springframework: DEBUG +``` diff --git a/05.Spring-Boot-Advanced/Step13.zip b/05.Spring-Boot-Advanced/Step13.zip new file mode 100644 index 0000000..2b20c1a Binary files /dev/null and b/05.Spring-Boot-Advanced/Step13.zip differ diff --git a/05.Spring-Boot-Advanced/Step14.md b/05.Spring-Boot-Advanced/Step14.md new file mode 100644 index 0000000..291f6c1 --- /dev/null +++ b/05.Spring-Boot-Advanced/Step14.md @@ -0,0 +1,456 @@ +## What You Will Learn during this Step: +- Embedded servlet containers + - Default Tomcat + - We did not install Tomcat. Did we? Magic is done by Spring Boot! + - Switching to Jetty or Undertow +- Configuration + - server.port +- Programming Tip + - Always review code : https://www.youtube.com/watch?v=hVJGu0xdXII + +## Useful Snippets and References +First Snippet +``` + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + org.springframework.boot + spring-boot-starter-jetty + +``` + +## Exercises +- Find out from documentation (https://docs.spring.io/spring-boot/docs/current/reference/html/howto-embedded-servlet-containers.html) how to switch to undertow! + +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + + org.springframework.boot + spring-boot-starter-jetty + + + + org.springframework.boot + spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + +} +``` +### src/main/java/com/in28minutes/springboot/controller/SurveyController.java +``` +package com.in28minutes.springboot.controller; + +import java.net.URI; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } + + // GET "/surveys/{surveyId}/questions/{questionId}" + @GetMapping("/surveys/{surveyId}/questions/{questionId}") + public Question retrieveDetailsForQuestion(@PathVariable String surveyId, + @PathVariable String questionId) { + return surveyService.retrieveQuestion(surveyId, questionId); + } + + // /surveys/{surveyId}/questions + @PostMapping("/surveys/{surveyId}/questions") + public ResponseEntity addQuestionToSurvey( + @PathVariable String surveyId, @RequestBody Question newQuestion) { + + Question question = surveyService.addQuestion(surveyId, newQuestion); + + if (question == null) + return ResponseEntity.noContent().build(); + + // Success - URI of the new resource in Response Header + // Status - created + // URI -> /surveys/{surveyId}/questions/{questionId} + // question.getQuestionId() + URI location = ServletUriComponentsBuilder.fromCurrentRequest().path( + "/{id}").buildAndExpand(question.getId()).toUri(); + + // Status + return ResponseEntity.created(location).build(); + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Question.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Question other = (Question) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Survey.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + @Override + public String toString() { + return "Survey [id=" + id + ", title=" + title + ", description=" + + description + ", questions=" + questions + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/service/SurveyService.java +``` +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", Arrays + .asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeService.java +``` +package com.in28minutes.springboot; + +import org.springframework.stereotype.Component; + +@Component +public class WelcomeService { + + public String retrieveWelcomeMessage() { + //Complex Method + return "Good Morning updated"; + } +} +``` +### src/main/resources/application.properties +``` +logging.level.org.springframework: DEBUG +``` diff --git a/05.Spring-Boot-Advanced/Step14.zip b/05.Spring-Boot-Advanced/Step14.zip new file mode 100644 index 0000000..8ecf9cf Binary files /dev/null and b/05.Spring-Boot-Advanced/Step14.zip differ diff --git a/05.Spring-Boot-Advanced/Step15.md b/05.Spring-Boot-Advanced/Step15.md new file mode 100644 index 0000000..65318af --- /dev/null +++ b/05.Spring-Boot-Advanced/Step15.md @@ -0,0 +1,467 @@ +## What You Will Learn during this Step: +- Using Dynamic Configuration in your application +- Customize Welcome Message +- Different ways of configuration + - --welcome.message="SomethingElse" in Program Arguments + - --spring.config.location=classpath:/default.properties + - We will learn about profiles in next step +- Using Placeholders +- YAML + +## Snippets +First Snippet +``` +logging: + level: + org.springframework: DEBUG +app: + name: In28Minutes + description: ${app.name} is your first Spring Boot application +welcome: + message: Welcome to your first Spring Boot app! +``` + +Second Snippet +``` +@Value("${welcome.message}") +``` + +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + + org.springframework.boot + spring-boot-starter-jetty + + + + org.springframework.boot + spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + +} +``` +### src/main/java/com/in28minutes/springboot/controller/SurveyController.java +``` +package com.in28minutes.springboot.controller; + +import java.net.URI; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } + + // GET "/surveys/{surveyId}/questions/{questionId}" + @GetMapping("/surveys/{surveyId}/questions/{questionId}") + public Question retrieveDetailsForQuestion(@PathVariable String surveyId, + @PathVariable String questionId) { + return surveyService.retrieveQuestion(surveyId, questionId); + } + + // /surveys/{surveyId}/questions + @PostMapping("/surveys/{surveyId}/questions") + public ResponseEntity addQuestionToSurvey( + @PathVariable String surveyId, @RequestBody Question newQuestion) { + + Question question = surveyService.addQuestion(surveyId, newQuestion); + + if (question == null) + return ResponseEntity.noContent().build(); + + // Success - URI of the new resource in Response Header + // Status - created + // URI -> /surveys/{surveyId}/questions/{questionId} + // question.getQuestionId() + URI location = ServletUriComponentsBuilder.fromCurrentRequest().path( + "/{id}").buildAndExpand(question.getId()).toUri(); + + // Status + return ResponseEntity.created(location).build(); + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Question.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Question other = (Question) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Survey.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + @Override + public String toString() { + return "Survey [id=" + id + ", title=" + title + ", description=" + + description + ", questions=" + questions + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/service/SurveyService.java +``` +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", Arrays + .asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeService.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +//Spring to manage this bean and create an instance of this +@Component +public class WelcomeService { + + @Value("${welcome.message}") + private String welcomeMessage; + + public String retrieveWelcomeMessage() { + //Complex Method + return welcomeMessage; + } +} +``` +### src/main/resources/application.properties +``` +logging.level.org.springframework: DEBUG +logging.level.org.springframework.web.servlet: DEBUG +app.name=in28Minutes +welcome.message=Welcome message from property file! Welcome to ${app.name} +``` +### src/main/resources/application.yaml +``` +logging: + level: + org.springframework: INFO + org.springframework.web.servlet: DEBUG +``` diff --git a/05.Spring-Boot-Advanced/Step15.zip b/05.Spring-Boot-Advanced/Step15.zip new file mode 100644 index 0000000..1a31cba Binary files /dev/null and b/05.Spring-Boot-Advanced/Step15.zip differ diff --git a/05.Spring-Boot-Advanced/Step16.md b/05.Spring-Boot-Advanced/Step16.md new file mode 100644 index 0000000..c5f970a --- /dev/null +++ b/05.Spring-Boot-Advanced/Step16.md @@ -0,0 +1,460 @@ +## What You Will Learn during this Step: +- Understand Basics of Profiles +- Setting a profile + - Using -Dspring.profiles.active=prod in VM Arguments + - spring.profiles.active=prod +- Using a profile + - application-{profile-name}.properties + - @Profile("dev") on a bean +- Usage + - Configure Resources - Databases, Queues, External Services + +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + + org.springframework.boot + spring-boot-starter-jetty + + + + org.springframework.boot + spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + + @Profile("prod") + @Bean + public String dummy() { + return "something"; + } +} +``` +### src/main/java/com/in28minutes/springboot/controller/SurveyController.java +``` +package com.in28minutes.springboot.controller; + +import java.net.URI; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } + + // GET "/surveys/{surveyId}/questions/{questionId}" + @GetMapping("/surveys/{surveyId}/questions/{questionId}") + public Question retrieveDetailsForQuestion(@PathVariable String surveyId, + @PathVariable String questionId) { + return surveyService.retrieveQuestion(surveyId, questionId); + } + + // /surveys/{surveyId}/questions + @PostMapping("/surveys/{surveyId}/questions") + public ResponseEntity addQuestionToSurvey( + @PathVariable String surveyId, @RequestBody Question newQuestion) { + + Question question = surveyService.addQuestion(surveyId, newQuestion); + + if (question == null) + return ResponseEntity.noContent().build(); + + // Success - URI of the new resource in Response Header + // Status - created + // URI -> /surveys/{surveyId}/questions/{questionId} + // question.getQuestionId() + URI location = ServletUriComponentsBuilder.fromCurrentRequest().path( + "/{id}").buildAndExpand(question.getId()).toUri(); + + // Status + return ResponseEntity.created(location).build(); + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Question.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Question other = (Question) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Survey.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + @Override + public String toString() { + return "Survey [id=" + id + ", title=" + title + ", description=" + + description + ", questions=" + questions + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/service/SurveyService.java +``` +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", Arrays + .asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeService.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +//Spring to manage this bean and create an instance of this +@Component +public class WelcomeService { + + @Value("${welcome.message}") + private String welcomeMessage; + + public String retrieveWelcomeMessage() { + //Complex Method + return welcomeMessage; + } +} +``` +### src/main/resources/application-dev.properties +``` +logging.level.org.springframework: TRACE +``` +### src/main/resources/application-prod.properties +``` +logging.level.org.springframework: INFO +``` +### src/main/resources/application.properties +``` +spring.profiles.active=prod + +logging.level.org.springframework: DEBUG +app.name=in28Minutes +welcome.message=Welcome message from property file! Welcome to ${app.name} +``` + diff --git a/05.Spring-Boot-Advanced/Step16.zip b/05.Spring-Boot-Advanced/Step16.zip new file mode 100644 index 0000000..9d6d3b2 Binary files /dev/null and b/05.Spring-Boot-Advanced/Step16.zip differ diff --git a/05.Spring-Boot-Advanced/Step17.md b/05.Spring-Boot-Advanced/Step17.md new file mode 100644 index 0000000..1f0fe5f --- /dev/null +++ b/05.Spring-Boot-Advanced/Step17.md @@ -0,0 +1,607 @@ +## What You Will Learn during this Step: +- Even better configuration management than @Value +- Type-safe Configuration Properties +- http://localhost:8080/dynamic-configuration +- Also look at http://localhost:8080/actuator/#http://localhost:8080/configprops + +## Useful Snippets and References +First Snippet +``` +package com.in28minutes.springboot.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties("basic") +public class BasicConfiguration { + private boolean value; + private String message; + private int number; + + public boolean isValue() { + return value; + } + + public void setValue(boolean value) { + this.value = value; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } + +} + +``` +Second Snippet +``` + @Autowired + private BasicConfiguration configuration; + + @RequestMapping("/dynamic-configuration") + public Map dynamicConfiguration() { + // Not the best practice to use a map to store differnt types! + Map map = new HashMap(); + map.put("message", configuration.getMessage()); + map.put("number", configuration.getNumber()); + map.put("key", configuration.isValue()); + return map; + } + +``` +Third Snippet +``` +basic.value: true +basic.message: Dynamic Message +basic.number: 100 +``` +Fourth Snippet +``` +basic: + value: true + message: Dynamic Message YAML + number: 100 +``` + +## Exercises +- Understand Type Safety +``` +*************************** +APPLICATION FAILED TO START +*************************** + +Description: + +Binding to target com.in28minutes.springboot.configuration.BasicConfiguration@391b8545 failed: + + Property: basic.number + Value: ABC + Reason: Failed to convert property value of type [java.lang.String] to required type [int] for property 'number'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [int] + + +Action: + +Update your application's configuration +``` + +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + + org.springframework.boot + spring-boot-starter-jetty + + + + org.springframework.boot + spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + + @Profile("prod") + @Bean + public String dummy() { + return "something"; + } +} +``` +### src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java +``` +package com.in28minutes.springboot.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties("basic") +public class BasicConfiguration { + private boolean value; + private String message; + private int number; + + public boolean isValue() { + return value; + } + + public void setValue(boolean value) { + this.value = value; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } +} +``` +### src/main/java/com/in28minutes/springboot/controller/SurveyController.java +``` +package com.in28minutes.springboot.controller; + +import java.net.URI; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } + + // GET "/surveys/{surveyId}/questions/{questionId}" + @GetMapping("/surveys/{surveyId}/questions/{questionId}") + public Question retrieveDetailsForQuestion(@PathVariable String surveyId, + @PathVariable String questionId) { + return surveyService.retrieveQuestion(surveyId, questionId); + } + + // /surveys/{surveyId}/questions + @PostMapping("/surveys/{surveyId}/questions") + public ResponseEntity addQuestionToSurvey( + @PathVariable String surveyId, @RequestBody Question newQuestion) { + + Question question = surveyService.addQuestion(surveyId, newQuestion); + + if (question == null) + return ResponseEntity.noContent().build(); + + // Success - URI of the new resource in Response Header + // Status - created + // URI -> /surveys/{surveyId}/questions/{questionId} + // question.getQuestionId() + URI location = ServletUriComponentsBuilder.fromCurrentRequest().path( + "/{id}").buildAndExpand(question.getId()).toUri(); + + // Status + return ResponseEntity.created(location).build(); + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Question.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Question other = (Question) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Survey.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + @Override + public String toString() { + return "Survey [id=" + id + ", title=" + title + ", description=" + + description + ", questions=" + questions + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/service/SurveyService.java +``` +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", Arrays + .asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.in28minutes.springboot.configuration.BasicConfiguration; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @Autowired + private BasicConfiguration configuration; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } + + @RequestMapping("/dynamic-configuration") + public Map dynamicConfiguration() { + Map map = new HashMap(); + map.put("message", configuration.getMessage()); + map.put("number", configuration.getNumber()); + map.put("value", configuration.isValue()); + + return map; + } + +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeService.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +//Spring to manage this bean and create an instance of this +@Component +public class WelcomeService { + + @Value("${welcome.message}") + private String welcomeMessage; + + public String retrieveWelcomeMessage() { + //Complex Method + return welcomeMessage; + } +} +``` +### src/main/resources/application-dev.properties +``` +logging.level.org.springframework: TRACE +``` +### src/main/resources/application-prod.properties +``` +logging.level.org.springframework: INFO +``` +### src/main/resources/application.properties +``` +logging.level.org.springframework: DEBUG +app.name=in28Minutes +welcome.message=Welcome message from property file! Welcome to ${app.name} + +basic.value=true +basic.message=Welcome to in28minutes +basic.number=200 +``` diff --git a/05.Spring-Boot-Advanced/Step17.zip b/05.Spring-Boot-Advanced/Step17.zip new file mode 100644 index 0000000..0334c61 Binary files /dev/null and b/05.Spring-Boot-Advanced/Step17.zip differ diff --git a/05.Spring-Boot-Advanced/Step18.md b/05.Spring-Boot-Advanced/Step18.md new file mode 100644 index 0000000..37021f6 --- /dev/null +++ b/05.Spring-Boot-Advanced/Step18.md @@ -0,0 +1,738 @@ +## What You Will Learn during this Step: +- Let's switch back to tomcat first! +- Get introduced to Spring Data JPA +- Create a very simple example with Spring Data JPA +- Use CommandLineRunner! + +## Some Notes +- Useful Properties + - spring.datasource.driver-class-name=com.mysql.jdbc.Driver + - spring.datasource.url=jdbc:mysql://localhost:3306/test + - spring.datasource.username=root + - spring.datasource.password=admin + - spring.datasource.initialize=true + - spring.jpa.hibernate.ddl-auto=update + - spring.jpa.show-sql=true + +## Useful Snippets and References +First Snippet - Add H2 Later after showing the error +``` + + org.springframework.boot + spring-boot-starter-data-jpa + + + + com.h2database + h2 + + +``` +Second Snippet +``` +package com.in28minutes.springboot.jpa; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String name;// Not perfect!! Should be a proper object! + private String role;// Not perfect!! An enum should be a better choice! + + protected User() { + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return String.format("User [id=%s, name=%s, role=%s]", id, name, role); + } + +} + +``` +Third Snippet +``` +package com.in28minutes.springboot.jpa; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +public class UserCommandLineRunner implements CommandLineRunner { + + private static final Logger log = LoggerFactory + .getLogger(UserCommandLineRunner.class); + + @Autowired + private UserRepository repository; + + @Override + public void run(String... args) { + // save a couple of customers + repository.save(new User("Ranga", "Admin")); + repository.save(new User("Ravi", "User")); + repository.save(new User("Satish", "Admin")); + repository.save(new User("Raghu", "User")); + + log.info("-------------------------------"); + log.info("Finding all users"); + log.info("-------------------------------"); + for (User user : repository.findAll()) { + log.info(user.toString()); + } + + } + +} + +``` +Fourth Snippet +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.CrudRepository; + +public interface UserRepository extends CrudRepository { +} +``` + +## Exercises +- Look at other methods provided by the UserRepository + +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + com.h2database + h2 + + + + org.springframework.boot + spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + + @Profile("prod") + @Bean + public String dummy() { + return "something"; + } +} +``` +### src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java +``` +package com.in28minutes.springboot.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties("basic") +public class BasicConfiguration { + private boolean value; + private String message; + private int number; + + public boolean isValue() { + return value; + } + + public void setValue(boolean value) { + this.value = value; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } +} +``` +### src/main/java/com/in28minutes/springboot/controller/SurveyController.java +``` +package com.in28minutes.springboot.controller; + +import java.net.URI; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } + + // GET "/surveys/{surveyId}/questions/{questionId}" + @GetMapping("/surveys/{surveyId}/questions/{questionId}") + public Question retrieveDetailsForQuestion(@PathVariable String surveyId, + @PathVariable String questionId) { + return surveyService.retrieveQuestion(surveyId, questionId); + } + + // /surveys/{surveyId}/questions + @PostMapping("/surveys/{surveyId}/questions") + public ResponseEntity addQuestionToSurvey( + @PathVariable String surveyId, @RequestBody Question newQuestion) { + + Question question = surveyService.addQuestion(surveyId, newQuestion); + + if (question == null) + return ResponseEntity.noContent().build(); + + // Success - URI of the new resource in Response Header + // Status - created + // URI -> /surveys/{surveyId}/questions/{questionId} + // question.getQuestionId() + URI location = ServletUriComponentsBuilder.fromCurrentRequest().path( + "/{id}").buildAndExpand(question.getId()).toUri(); + + // Status + return ResponseEntity.created(location).build(); + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/User.java +``` +package com.in28minutes.springboot.jpa; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String name; + private String role; + + protected User() { + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return "User [id=" + id + ", name=" + name + ", role=" + role + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserCommandLineRunner.java +``` +package com.in28minutes.springboot.jpa; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +public class UserCommandLineRunner implements CommandLineRunner { + + private static final Logger log = LoggerFactory + .getLogger(UserCommandLineRunner.class); + + @Autowired + private UserRepository repository; + + @Override + public void run(String... args) throws Exception { + + repository.save(new User("Ranga", "Admin")); + repository.save(new User("Ravi", "User")); + repository.save(new User("Satish", "Admin")); + repository.save(new User("Raghu", "User")); + + for (User user : repository.findAll()) { + log.info(user.toString()); + } + + log.info("Admin users are....."); + log.info("____________________"); + for (User user : repository.findByRole("Admin")) { + log.info(user.toString()); + } + + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserRepository.java +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.CrudRepository; + +public interface UserRepository extends CrudRepository { +} +``` +### src/main/java/com/in28minutes/springboot/model/Question.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Question other = (Question) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Survey.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + @Override + public String toString() { + return "Survey [id=" + id + ", title=" + title + ", description=" + + description + ", questions=" + questions + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/service/SurveyService.java +``` +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", Arrays + .asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.in28minutes.springboot.configuration.BasicConfiguration; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @Autowired + private BasicConfiguration configuration; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } + + @RequestMapping("/dynamic-configuration") + public Map dynamicConfiguration() { + Map map = new HashMap(); + map.put("message", configuration.getMessage()); + map.put("number", configuration.getNumber()); + map.put("value", configuration.isValue()); + + return map; + } + +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeService.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +//Spring to manage this bean and create an instance of this +@Component +public class WelcomeService { + + @Value("${welcome.message}") + private String welcomeMessage; + + public String retrieveWelcomeMessage() { + //Complex Method + return welcomeMessage; + } +} +``` +### src/main/resources/application-dev.properties +``` +logging.level.org.springframework: TRACE +``` +### src/main/resources/application-prod.properties +``` +logging.level.org.springframework: INFO +``` +### src/main/resources/application.properties +``` +logging.level.org.springframework: DEBUG +app.name=in28Minutes +welcome.message=Welcome message from property file! Welcome to ${app.name} + +basic.value=true +basic.message=Welcome to in28minutes +basic.number=200 +``` diff --git a/05.Spring-Boot-Advanced/Step18.zip b/05.Spring-Boot-Advanced/Step18.zip new file mode 100644 index 0000000..dbe10a0 Binary files /dev/null and b/05.Spring-Boot-Advanced/Step18.zip differ diff --git a/05.Spring-Boot-Advanced/Step19.md b/05.Spring-Boot-Advanced/Step19.md new file mode 100644 index 0000000..9bc1db7 --- /dev/null +++ b/05.Spring-Boot-Advanced/Step19.md @@ -0,0 +1,655 @@ +## What You Will Learn during this Step: +- Look at H2 Console : http://localhost:8080/h2-console + - Use db url jdbc:h2:mem:testdb +- Add findByRole method + +## Some Notes +- Useful Properties + - spring.datasource.driver-class-name=com.mysql.jdbc.Driver + - spring.datasource.url=jdbc:mysql://localhost:3306/test + - spring.datasource.username=root + - spring.datasource.password=admin + - spring.datasource.initialize=true + - spring.jpa.hibernate.ddl-auto=update + - spring.jpa.show-sql=true + +## Useful Snippets and References + +First Snippet +``` + + log.info("-------------------------------"); + log.info("Finding user with id 1"); + log.info("-------------------------------"); + User user = repository.findOne(1L); + log.info(user.toString()); + + log.info("-------------------------------"); + log.info("Finding all Admins"); + log.info("-------------------------------"); + for (User admin : repository.findByRole("Admin")) { + log.info(admin.toString()); + // Do something... + } + +``` +Second Snippet +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.CrudRepository; + +public interface UserRepository extends CrudRepository { + List findByRole(String description); +} +``` + +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + com.h2database + h2 + + + + org.springframework.boot + spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + + @Profile("prod") + @Bean + public String dummy() { + return "something"; + } +} +``` +### src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java +``` +package com.in28minutes.springboot.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties("basic") +public class BasicConfiguration { + private boolean value; + private String message; + private int number; + + public boolean isValue() { + return value; + } + + public void setValue(boolean value) { + this.value = value; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } +} +``` +### src/main/java/com/in28minutes/springboot/controller/SurveyController.java +``` +package com.in28minutes.springboot.controller; + +import java.net.URI; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } + + // GET "/surveys/{surveyId}/questions/{questionId}" + @GetMapping("/surveys/{surveyId}/questions/{questionId}") + public Question retrieveDetailsForQuestion(@PathVariable String surveyId, + @PathVariable String questionId) { + return surveyService.retrieveQuestion(surveyId, questionId); + } + + // /surveys/{surveyId}/questions + @PostMapping("/surveys/{surveyId}/questions") + public ResponseEntity addQuestionToSurvey( + @PathVariable String surveyId, @RequestBody Question newQuestion) { + + Question question = surveyService.addQuestion(surveyId, newQuestion); + + if (question == null) + return ResponseEntity.noContent().build(); + + // Success - URI of the new resource in Response Header + // Status - created + // URI -> /surveys/{surveyId}/questions/{questionId} + // question.getQuestionId() + URI location = ServletUriComponentsBuilder.fromCurrentRequest().path( + "/{id}").buildAndExpand(question.getId()).toUri(); + + // Status + return ResponseEntity.created(location).build(); + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/User.java +``` +package com.in28minutes.springboot.jpa; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String name; + private String role; + + protected User() { + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return "User [id=" + id + ", name=" + name + ", role=" + role + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserCommandLineRunner.java +``` +package com.in28minutes.springboot.jpa; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +public class UserCommandLineRunner implements CommandLineRunner { + + private static final Logger log = LoggerFactory + .getLogger(UserCommandLineRunner.class); + + @Autowired + private UserRepository repository; + + @Override + public void run(String... args) throws Exception { + + repository.save(new User("Ranga", "Admin")); + repository.save(new User("Ravi", "User")); + repository.save(new User("Satish", "Admin")); + repository.save(new User("Raghu", "User")); + + for (User user : repository.findAll()) { + log.info(user.toString()); + } + + log.info("Admin users are....."); + log.info("____________________"); + for (User user : repository.findByRole("Admin")) { + log.info(user.toString()); + } + + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserRepository.java +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.CrudRepository; + +public interface UserRepository extends CrudRepository { + List findByRole(String role); +} +``` +### src/main/java/com/in28minutes/springboot/model/Question.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Question other = (Question) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Survey.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + @Override + public String toString() { + return "Survey [id=" + id + ", title=" + title + ", description=" + + description + ", questions=" + questions + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/service/SurveyService.java +``` +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", Arrays + .asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.in28minutes.springboot.configuration.BasicConfiguration; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @Autowired + private BasicConfiguration configuration; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } + + @RequestMapping("/dynamic-configuration") + public Map dynamicConfiguration() { + Map map = new HashMap(); + map.put("message", configuration.getMessage()); + map.put("number", configuration.getNumber()); + map.put("value", configuration.isValue()); + + return map; + } + +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeService.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +//Spring to manage this bean and create an instance of this +@Component +public class WelcomeService { + + @Value("${welcome.message}") + private String welcomeMessage; + + public String retrieveWelcomeMessage() { + //Complex Method + return welcomeMessage; + } +} +``` +### src/main/resources/application-dev.properties +``` +logging.level.org.springframework: TRACE +``` +### src/main/resources/application-prod.properties +``` +logging.level.org.springframework: INFO +``` +### src/main/resources/application.properties +``` +logging.level.org.springframework: DEBUG +app.name=in28Minutes +welcome.message=Welcome message from property file! Welcome to ${app.name} + +basic.value=true +basic.message=Welcome to in28minutes +basic.number=200 +``` diff --git a/05.Spring-Boot-Advanced/Step19.zip b/05.Spring-Boot-Advanced/Step19.zip new file mode 100644 index 0000000..7100510 Binary files /dev/null and b/05.Spring-Boot-Advanced/Step19.zip differ diff --git a/05.Spring-Boot-Advanced/Step20.md b/05.Spring-Boot-Advanced/Step20.md new file mode 100644 index 0000000..f6284d5 --- /dev/null +++ b/05.Spring-Boot-Advanced/Step20.md @@ -0,0 +1,686 @@ +## What You Will Learn during this Step: +- Introduction to Spring Data Rest + - Hit http://localhost:8080/users in POSTMAN + - http://localhost:8080/users/1 + - http://localhost:8080/users/?size=4 + - http://localhost:8080/users/?sort=name,desc + - @Param("role") + - http://localhost:8080/users/search/findByRole?role=Admin +- Good for quick prototype! Be cautious about using this in Big applications! + +## Useful Snippets and References +First Snippet +``` + + org.springframework.boot + spring-boot-starter-data-rest + + +``` +Second Snippet +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; + +@RepositoryRestResource(collectionResourceRel = "users", path = "users") +public interface UserRestRepository extends +PagingAndSortingRepository { + List findByRole(@Param("role") String role); +} +``` + +Third Snippet : POST to http://localhost:8080/users +``` +HEADER +Content-Type:application/json + +BODY +{ + "name": "Raja", + "role": "Admin" +} +``` +Fourth Snippet : POST to http://localhost:8080/users/5 +``` +HEADER +Content-Type:application/json + +BODY +{ + "name": "Raja-Updated", + "role": "Admin" +} +``` +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-data-rest + + + + com.h2database + h2 + + + + org.springframework.boot + spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + + @Profile("prod") + @Bean + public String dummy() { + return "something"; + } +} +``` +### src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java +``` +package com.in28minutes.springboot.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties("basic") +public class BasicConfiguration { + private boolean value; + private String message; + private int number; + + public boolean isValue() { + return value; + } + + public void setValue(boolean value) { + this.value = value; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } +} +``` +### src/main/java/com/in28minutes/springboot/controller/SurveyController.java +``` +package com.in28minutes.springboot.controller; + +import java.net.URI; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } + + // GET "/surveys/{surveyId}/questions/{questionId}" + @GetMapping("/surveys/{surveyId}/questions/{questionId}") + public Question retrieveDetailsForQuestion(@PathVariable String surveyId, + @PathVariable String questionId) { + return surveyService.retrieveQuestion(surveyId, questionId); + } + + // /surveys/{surveyId}/questions + @PostMapping("/surveys/{surveyId}/questions") + public ResponseEntity addQuestionToSurvey( + @PathVariable String surveyId, @RequestBody Question newQuestion) { + + Question question = surveyService.addQuestion(surveyId, newQuestion); + + if (question == null) + return ResponseEntity.noContent().build(); + + // Success - URI of the new resource in Response Header + // Status - created + // URI -> /surveys/{surveyId}/questions/{questionId} + // question.getQuestionId() + URI location = ServletUriComponentsBuilder.fromCurrentRequest().path( + "/{id}").buildAndExpand(question.getId()).toUri(); + + // Status + return ResponseEntity.created(location).build(); + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/User.java +``` +package com.in28minutes.springboot.jpa; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String name; + private String role; + + protected User() { + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return "User [id=" + id + ", name=" + name + ", role=" + role + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserCommandLineRunner.java +``` +package com.in28minutes.springboot.jpa; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +public class UserCommandLineRunner implements CommandLineRunner { + + private static final Logger log = LoggerFactory + .getLogger(UserCommandLineRunner.class); + + @Autowired + private UserRepository repository; + + @Override + public void run(String... args) throws Exception { + + repository.save(new User("Ranga", "Admin")); + repository.save(new User("Ravi", "User")); + repository.save(new User("Satish", "Admin")); + repository.save(new User("Raghu", "User")); + + for (User user : repository.findAll()) { + log.info(user.toString()); + } + + log.info("Admin users are....."); + log.info("____________________"); + for (User user : repository.findByRole("Admin")) { + log.info(user.toString()); + } + + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserRepository.java +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.CrudRepository; + +public interface UserRepository extends CrudRepository { + List findByRole(String role); +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserRestRepository.java +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; + +@RepositoryRestResource(path = "users", collectionResourceRel = "users") +public interface UserRestRepository extends + PagingAndSortingRepository { + List findByRole(@Param("role") String role); +} +``` +### src/main/java/com/in28minutes/springboot/model/Question.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Question other = (Question) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Survey.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + @Override + public String toString() { + return "Survey [id=" + id + ", title=" + title + ", description=" + + description + ", questions=" + questions + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/service/SurveyService.java +``` +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", Arrays + .asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.in28minutes.springboot.configuration.BasicConfiguration; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @Autowired + private BasicConfiguration configuration; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } + + @RequestMapping("/dynamic-configuration") + public Map dynamicConfiguration() { + Map map = new HashMap(); + map.put("message", configuration.getMessage()); + map.put("number", configuration.getNumber()); + map.put("value", configuration.isValue()); + + return map; + } + +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeService.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +//Spring to manage this bean and create an instance of this +@Component +public class WelcomeService { + + @Value("${welcome.message}") + private String welcomeMessage; + + public String retrieveWelcomeMessage() { + //Complex Method + return welcomeMessage; + } +} +``` +### src/main/resources/application-dev.properties +``` +logging.level.org.springframework: TRACE +``` +### src/main/resources/application-prod.properties +``` +logging.level.org.springframework: INFO +``` +### src/main/resources/application.properties +``` +logging.level.org.springframework: DEBUG +app.name=in28Minutes +welcome.message=Welcome message from property file! Welcome to ${app.name} + +basic.value=true +basic.message=Welcome to in28minutes +basic.number=200 +``` diff --git a/05.Spring-Boot-Advanced/Step20.zip b/05.Spring-Boot-Advanced/Step20.zip new file mode 100644 index 0000000..ccabbae Binary files /dev/null and b/05.Spring-Boot-Advanced/Step20.zip differ diff --git a/05.Spring-Boot-Advanced/Step21.md b/05.Spring-Boot-Advanced/Step21.md new file mode 100644 index 0000000..6720551 --- /dev/null +++ b/05.Spring-Boot-Advanced/Step21.md @@ -0,0 +1,821 @@ +## What You Will Learn during this Step: +- First thing first : I hate the fact that we did not write tests until Step 20 of this course + - I love TDD and use it in all my projects. However, when learning something new, I think it is important to focus on one thing at a time! +- You can learn more about unit testing here - https://www.youtube.com/playlist?list=PL83C941BB0D27A6AF +- Let's write a Integration Test for our service + +## Useful Snippets and References +First Snippet +``` + + org.springframework.boot + spring-boot-starter-test + test + + +``` +UnRefactored Snippet +``` +package com.in28minutes.springboot.controller; + +import java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import com.in28minutes.springboot.Application; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class SurveyControllerIT { + + @LocalServerPort + private int port; + + @Test + public void testRetrieveSurveyQuestion() { + + String url = "http://localhost:" + port + + "/surveys/Survey1/questions/Question1"; + + TestRestTemplate restTemplate = new TestRestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + + HttpEntity entity = new HttpEntity(null, headers); + + ResponseEntity response = restTemplate.exchange(url, + HttpMethod.GET, entity, String.class); + + String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia}"; + + JSONAssert.assertEquals(expected, response.getBody(), false); + } +} + + +``` +Refactored Snippet +``` +package com.in28minutes.springboot.controller; + +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import com.in28minutes.springboot.Application; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class SurveyControllerIT { + + @LocalServerPort + private int port; + + private TestRestTemplate template = new TestRestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + + @Before + public void setupJSONAcceptType() { + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + } + + @Test + public void retrieveSurveyQuestion() throws Exception { + + String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia,options:[India,Russia,United States,China]}"; + + ResponseEntity response = template.exchange( + createUrl("/surveys/Survey1/questions/Question1"), + HttpMethod.GET, new HttpEntity("DUMMY_DOESNT_MATTER", + headers), String.class); + + JSONAssert.assertEquals(expected, response.getBody(), false); + } + + private String createUrl(String uri) { + return "http://localhost:" + port + uri; + } + +} +``` + +## Exercises +- Try writing integration test for retrieveAllQuestions + +## Files List + +### /pom.xml +``` + + 4.0.0 + com.in28minutes + springboot-for-beginners-example + 0.0.1-SNAPSHOT + Your First Spring Boot Example + jar + + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + com.h2database + h2 + + + + org.springframework.boot + spring-boot-starter-data-rest + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + + + org.springframework.boot + spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + 1.8 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` +### /src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.in28minutes.springboot.configuration.BasicConfiguration; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + + ApplicationContext ctx = SpringApplication.run(Application.class, args); + } + + @RestController + class SomeBean { + + @Autowired + private SomeDependency someDependency; + + @Autowired + private BasicConfiguration configuration; + + @RequestMapping("/") + public String index() { + return someDependency.getSomething(); + } + + @RequestMapping("/dynamic-configuration") + public Map dynamicConfiguration() { + // Not the best practice to use a map to store differnt types! + Map map = new HashMap(); + map.put("message", configuration.getMessage()); + map.put("number", configuration.getNumber()); + map.put("key", configuration.isValue()); + return map; + } + + } + + @Component + class SomeDependency { + + @Value("${welcome.message}") + private String welcomeMessage; + + public String getSomething() { + return welcomeMessage; + } + + } + +} +``` +### /src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java +``` +package com.in28minutes.springboot.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties("basic") +public class BasicConfiguration { + private boolean value; + private String message; + private int number; + + public boolean isValue() { + return value; + } + + public void setValue(boolean value) { + this.value = value; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } + +} +``` +### /src/main/java/com/in28minutes/springboot/controller/SurveyController.java +``` +package com.in28minutes.springboot.controller; + +import java.net.URI; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } + + @GetMapping(path = "/surveys/{surveyId}/questions/{questionId}") + public Question retrieveQuestion(@PathVariable String surveyId, + @PathVariable String questionId) { + return surveyService.retrieveQuestion(surveyId, questionId); + } + + @PostMapping("/surveys/{surveyId}/questions") + ResponseEntity add(@PathVariable String surveyId, + @RequestBody Question question) { + + Question createdTodo = surveyService.addQuestion(surveyId, question); + + if (createdTodo == null) { + return ResponseEntity.noContent().build(); + } + + URI location = ServletUriComponentsBuilder.fromCurrentRequest() + .path("/{id}").buildAndExpand(createdTodo.getId()).toUri(); + + return ResponseEntity.created(location).build(); + + } + +} +``` +### /src/main/java/com/in28minutes/springboot/jpa/User.java +``` +package com.in28minutes.springboot.jpa; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String name;// Not perfect!! Should be a proper object! + private String role;// Not perfect!! An enum should be a better choice! + + protected User() { + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return String.format("User [id=%s, name=%s, role=%s]", id, name, role); + } + +} +``` +### /src/main/java/com/in28minutes/springboot/jpa/UserCommandLineRunner.java +``` +package com.in28minutes.springboot.jpa; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +public class UserCommandLineRunner implements CommandLineRunner { + + private static final Logger log = LoggerFactory + .getLogger(UserCommandLineRunner.class); + + @Autowired + private UserRepository repository; + + @Override + public void run(String... args) { + // save a couple of customers + repository.save(new User("Ranga", "Admin")); + repository.save(new User("Ravi", "User")); + repository.save(new User("Satish", "Admin")); + repository.save(new User("Raghu", "User")); + + log.info("-------------------------------"); + log.info("Finding all users"); + log.info("-------------------------------"); + for (User user : repository.findAll()) { + log.info(user.toString()); + } + + log.info("-------------------------------"); + log.info("Finding user with id 1"); + log.info("-------------------------------"); + User user = repository.findOne(1L); + log.info(user.toString()); + + log.info("-------------------------------"); + log.info("Finding all Admins"); + log.info("-------------------------------"); + for (User admin : repository.findByRole("Admin")) { + log.info(admin.toString()); + // Do something... + } + } + +} +``` +### /src/main/java/com/in28minutes/springboot/jpa/UserRepository.java +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.CrudRepository; + +public interface UserRepository extends CrudRepository { + List findByRole(String description); +} +``` +### /src/main/java/com/in28minutes/springboot/jpa/UserRestRepository.java +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; + +@RepositoryRestResource(collectionResourceRel = "users", path = "users") +public interface UserRestRepository extends +PagingAndSortingRepository { + List findByRole(@Param("role") String role); +} +``` +### /src/main/java/com/in28minutes/springboot/model/Question.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (id == null ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Question other = (Question) obj; + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + return true; + } + +} +``` +### /src/main/java/com/in28minutes/springboot/model/Survey.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public String getTitle() { + return title; + } + + public String getDescription() { + return description; + } + + public List getQuestions() { + return questions; + } + + @Override + public String toString() { + return String.format( + "Survey [id=%s, title=%s, description=%s, questions=%s]", id, + title, description, questions); + } + +} +``` +### /src/main/java/com/in28minutes/springboot/service/SurveyService.java +``` +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", + Arrays.asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} +``` +### /src/main/resources/application.properties +``` +logging.level.org.springframework: INFO +app.name: In28Minutes +app.description: ${app.name} is your first Spring Boot application Properties +welcome.message: Welcome to your first Spring Boot application +basic.value: true +basic.message: Dynamic Message +basic.number: 123 +``` +### /src/main/resources/application.yaml +``` +logging: + level: + org.springframework: DEBUG + +app: + name: In28Minutes + description: ${app.name} is your first Spring Boot application + +welcome: + message: Welcome to your first Spring Boot app! + +basic: + value: true + message: Dynamic Message YAML + number: 100 +``` +### /src/test/java/com/in28minutes/springboot/controller/SurveyControllerIT.java +``` +package com.in28minutes.springboot.controller; + +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import com.in28minutes.springboot.Application; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class SurveyControllerIT { + + @LocalServerPort + private int port; + + private TestRestTemplate template = new TestRestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + + @Before + public void setupJSONAcceptType() { + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + } + + @Test + public void retrieveSurveyQuestion() throws Exception { + + String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia,options:[India,Russia,United States,China]}"; + + ResponseEntity response = template.exchange( + createUrl("/surveys/Survey1/questions/Question1"), + HttpMethod.GET, new HttpEntity("DUMMY_DOESNT_MATTER", + headers), String.class); + + JSONAssert.assertEquals(expected, response.getBody(), false); + } + + private String createUrl(String uri) { + return "http://localhost:" + port + uri; + } + +} +``` diff --git a/05.Spring-Boot-Advanced/Step21.zip b/05.Spring-Boot-Advanced/Step21.zip new file mode 100644 index 0000000..f216df5 Binary files /dev/null and b/05.Spring-Boot-Advanced/Step21.zip differ diff --git a/05.Spring-Boot-Advanced/Step22.md b/05.Spring-Boot-Advanced/Step22.md new file mode 100644 index 0000000..e573365 --- /dev/null +++ b/05.Spring-Boot-Advanced/Step22.md @@ -0,0 +1,812 @@ +## What You Will Learn during this Step: +- Exercise from previous step +- Integration Test for POST Request - Add To do + +## Useful Snippets and References +First Snippet +``` + @Test + public void retrieveSurveyQuestions() throws Exception { + ResponseEntity> response = template.exchange( + createUrl("/surveys/Survey1/questions/"), HttpMethod.GET, + new HttpEntity("DUMMY_DOESNT_MATTER", headers), + new ParameterizedTypeReference>() { + }); + + Question sampleQuestion = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + + assertTrue(response.getBody().contains(sampleQuestion)); + } + +``` +Before Refactoring Snippet +``` + //NEEDS REFACTORING + @Test + public void retrieveAllSurveyQuestions() throws Exception { + + String url = "http://localhost:" + port + "/surveys/Survey1/questions"; + + TestRestTemplate restTemplate = new TestRestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + + ResponseEntity> response = restTemplate.exchange(url, + HttpMethod.GET, new HttpEntity("DUMMY_DOESNT_MATTER", + headers), + new ParameterizedTypeReference>() { + }); + + Question sampleQuestion = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + + assertTrue(response.getBody().contains(sampleQuestion)); + } + +``` +After Refactoring Snippet - We will discuss this in Step 23 +``` + + @Test + public void createSurveyQuestion() throws Exception { + Question question = new Question("DOESN'T MATTER", "Smallest Number", + "1", Arrays.asList("1", "2", "3", "4")); + + ResponseEntity response = template.exchange( + createUrl("/surveys/Survey1/questions/"), HttpMethod.POST, + new HttpEntity(question, headers), String.class); + + assertThat(response.getHeaders().get(HttpHeaders.LOCATION).get(0), + containsString("/surveys/Survey1/questions/")); + } + +``` + +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-data-rest + + + + com.h2database + h2 + + + + org.springframework.boot + spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + + @Profile("prod") + @Bean + public String dummy() { + return "something"; + } +} +``` +### src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java +``` +package com.in28minutes.springboot.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties("basic") +public class BasicConfiguration { + private boolean value; + private String message; + private int number; + + public boolean isValue() { + return value; + } + + public void setValue(boolean value) { + this.value = value; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } +} +``` +### src/main/java/com/in28minutes/springboot/controller/SurveyController.java +``` +package com.in28minutes.springboot.controller; + +import java.net.URI; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } + + // GET "/surveys/{surveyId}/questions/{questionId}" + @GetMapping("/surveys/{surveyId}/questions/{questionId}") + public Question retrieveDetailsForQuestion(@PathVariable String surveyId, + @PathVariable String questionId) { + return surveyService.retrieveQuestion(surveyId, questionId); + } + + // /surveys/{surveyId}/questions + @PostMapping("/surveys/{surveyId}/questions") + public ResponseEntity addQuestionToSurvey( + @PathVariable String surveyId, @RequestBody Question newQuestion) { + + Question question = surveyService.addQuestion(surveyId, newQuestion); + + if (question == null) + return ResponseEntity.noContent().build(); + + // Success - URI of the new resource in Response Header + // Status - created + // URI -> /surveys/{surveyId}/questions/{questionId} + // question.getQuestionId() + URI location = ServletUriComponentsBuilder.fromCurrentRequest().path( + "/{id}").buildAndExpand(question.getId()).toUri(); + + // Status + return ResponseEntity.created(location).build(); + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/User.java +``` +package com.in28minutes.springboot.jpa; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String name; + private String role; + + protected User() { + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return "User [id=" + id + ", name=" + name + ", role=" + role + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserCommandLineRunner.java +``` +package com.in28minutes.springboot.jpa; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +public class UserCommandLineRunner implements CommandLineRunner { + + private static final Logger log = LoggerFactory + .getLogger(UserCommandLineRunner.class); + + @Autowired + private UserRepository repository; + + @Override + public void run(String... args) throws Exception { + + repository.save(new User("Ranga", "Admin")); + repository.save(new User("Ravi", "User")); + repository.save(new User("Satish", "Admin")); + repository.save(new User("Raghu", "User")); + + for (User user : repository.findAll()) { + log.info(user.toString()); + } + + log.info("Admin users are....."); + log.info("____________________"); + for (User user : repository.findByRole("Admin")) { + log.info(user.toString()); + } + + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserRepository.java +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.CrudRepository; + +public interface UserRepository extends CrudRepository { + List findByRole(String role); +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserRestRepository.java +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; + +@RepositoryRestResource(path = "users", collectionResourceRel = "users") +public interface UserRestRepository extends + PagingAndSortingRepository { + List findByRole(@Param("role") String role); +} +``` +### src/main/java/com/in28minutes/springboot/model/Question.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Question other = (Question) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Survey.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + @Override + public String toString() { + return "Survey [id=" + id + ", title=" + title + ", description=" + + description + ", questions=" + questions + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/service/SurveyService.java +``` +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", Arrays + .asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.in28minutes.springboot.configuration.BasicConfiguration; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @Autowired + private BasicConfiguration configuration; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } + + @RequestMapping("/dynamic-configuration") + public Map dynamicConfiguration() { + Map map = new HashMap(); + map.put("message", configuration.getMessage()); + map.put("number", configuration.getNumber()); + map.put("value", configuration.isValue()); + + return map; + } + +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeService.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +//Spring to manage this bean and create an instance of this +@Component +public class WelcomeService { + + @Value("${welcome.message}") + private String welcomeMessage; + + public String retrieveWelcomeMessage() { + //Complex Method + return welcomeMessage; + } +} +``` +### src/main/resources/application-dev.properties +``` +logging.level.org.springframework: TRACE +``` +### src/main/resources/application-prod.properties +``` +logging.level.org.springframework: INFO +``` +### src/main/resources/application.properties +``` +logging.level.org.springframework: DEBUG +app.name=in28Minutes +welcome.message=Welcome message from property file! Welcome to ${app.name} + +basic.value=true +basic.message=Welcome to in28minutes +basic.number=200 +``` +### src/test/java/com/in28minutes/springboot/controller/SurveyControllerIT.java +``` +package com.in28minutes.springboot.controller; + +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import com.in28minutes.springboot.Application; +import com.in28minutes.springboot.model.Question; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class SurveyControllerIT { + + @LocalServerPort + private int port; + + //NEEDS REFACTORING + @Test + public void testRetrieveSurveyQuestion() { + + String url = "http://localhost:" + port + + "/surveys/Survey1/questions/Question1"; + + TestRestTemplate restTemplate = new TestRestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + + HttpEntity entity = new HttpEntity(null, headers); + + ResponseEntity response = restTemplate.exchange(url, + HttpMethod.GET, entity, String.class); + + String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia}"; + + JSONAssert.assertEquals(expected, response.getBody(), false); + } + + //NEEDS REFACTORING + @Test + public void retrieveAllSurveyQuestions() throws Exception { + + String url = "http://localhost:" + port + "/surveys/Survey1/questions"; + + TestRestTemplate restTemplate = new TestRestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + + ResponseEntity> response = restTemplate.exchange(url, + HttpMethod.GET, new HttpEntity("DUMMY_DOESNT_MATTER", + headers), + new ParameterizedTypeReference>() { + }); + + Question sampleQuestion = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + + assertTrue(response.getBody().contains(sampleQuestion)); + } + + //NEEDS REFACTORING + @Test + public void addQuestion() { + + String url = "http://localhost:" + port + "/surveys/Survey1/questions"; + + TestRestTemplate restTemplate = new TestRestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + + Question question = new Question("DOESNTMATTER", "Question1", "Russia", + Arrays.asList("India", "Russia", "United States", "China")); + + HttpEntity entity = new HttpEntity(question, headers); + + ResponseEntity response = restTemplate.exchange(url, + HttpMethod.POST, entity, String.class); + + String actual = response.getHeaders().get(HttpHeaders.LOCATION).get(0); + + assertTrue(actual.contains("/surveys/Survey1/questions/")); + + } + +} +``` diff --git a/05.Spring-Boot-Advanced/Step22.zip b/05.Spring-Boot-Advanced/Step22.zip new file mode 100644 index 0000000..d0679f6 Binary files /dev/null and b/05.Spring-Boot-Advanced/Step22.zip differ diff --git a/05.Spring-Boot-Advanced/Step23.md b/05.Spring-Boot-Advanced/Step23.md new file mode 100644 index 0000000..03b264f --- /dev/null +++ b/05.Spring-Boot-Advanced/Step23.md @@ -0,0 +1,741 @@ +## What You Will Learn during this Step: +- Lets do some cleanup + - Lets Refactor the SurveyControllerIT.java + +## Exercises +- Test and make sure everything is working fine + +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-data-rest + + + + com.h2database + h2 + + + + org.springframework.boot + spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + + @Profile("prod") + @Bean + public String dummy() { + return "something"; + } +} +``` +### src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java +``` +package com.in28minutes.springboot.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties("basic") +public class BasicConfiguration { + private boolean value; + private String message; + private int number; + + public boolean isValue() { + return value; + } + + public void setValue(boolean value) { + this.value = value; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } +} +``` +### src/main/java/com/in28minutes/springboot/controller/SurveyController.java +``` +package com.in28minutes.springboot.controller; + +import java.net.URI; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } + + // GET "/surveys/{surveyId}/questions/{questionId}" + @GetMapping("/surveys/{surveyId}/questions/{questionId}") + public Question retrieveDetailsForQuestion(@PathVariable String surveyId, + @PathVariable String questionId) { + return surveyService.retrieveQuestion(surveyId, questionId); + } + + // /surveys/{surveyId}/questions + @PostMapping("/surveys/{surveyId}/questions") + public ResponseEntity addQuestionToSurvey( + @PathVariable String surveyId, @RequestBody Question newQuestion) { + + Question question = surveyService.addQuestion(surveyId, newQuestion); + + if (question == null) + return ResponseEntity.noContent().build(); + + // Success - URI of the new resource in Response Header + // Status - created + // URI -> /surveys/{surveyId}/questions/{questionId} + // question.getQuestionId() + URI location = ServletUriComponentsBuilder.fromCurrentRequest().path( + "/{id}").buildAndExpand(question.getId()).toUri(); + + // Status + return ResponseEntity.created(location).build(); + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/User.java +``` +package com.in28minutes.springboot.jpa; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String name; + private String role; + + protected User() { + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return "User [id=" + id + ", name=" + name + ", role=" + role + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserCommandLineRunner.java +``` +package com.in28minutes.springboot.jpa; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +public class UserCommandLineRunner implements CommandLineRunner { + + private static final Logger log = LoggerFactory + .getLogger(UserCommandLineRunner.class); + + @Autowired + private UserRepository repository; + + @Override + public void run(String... args) throws Exception { + + repository.save(new User("Ranga", "Admin")); + repository.save(new User("Ravi", "User")); + repository.save(new User("Satish", "Admin")); + repository.save(new User("Raghu", "User")); + + for (User user : repository.findAll()) { + log.info(user.toString()); + } + + log.info("Admin users are....."); + log.info("____________________"); + for (User user : repository.findByRole("Admin")) { + log.info(user.toString()); + } + + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserRepository.java +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.CrudRepository; + +public interface UserRepository extends CrudRepository { + List findByRole(String role); +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserRestRepository.java +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; + +@RepositoryRestResource(path = "users", collectionResourceRel = "users") +public interface UserRestRepository extends + PagingAndSortingRepository { + List findByRole(@Param("role") String role); +} +``` +### src/main/java/com/in28minutes/springboot/model/Question.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Question other = (Question) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Survey.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + @Override + public String toString() { + return "Survey [id=" + id + ", title=" + title + ", description=" + + description + ", questions=" + questions + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/service/SurveyService.java +``` +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", Arrays + .asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.in28minutes.springboot.configuration.BasicConfiguration; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @Autowired + private BasicConfiguration configuration; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } + + @RequestMapping("/dynamic-configuration") + public Map dynamicConfiguration() { + Map map = new HashMap(); + map.put("message", configuration.getMessage()); + map.put("number", configuration.getNumber()); + map.put("value", configuration.isValue()); + + return map; + } + +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeService.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +//Spring to manage this bean and create an instance of this +@Component +public class WelcomeService { + + @Value("${welcome.message}") + private String welcomeMessage; + + public String retrieveWelcomeMessage() { + //Complex Method + return welcomeMessage; + } +} +``` +### src/main/resources/application-dev.properties +``` +logging.level.org.springframework: TRACE +``` +### src/main/resources/application-prod.properties +``` +logging.level.org.springframework: INFO +``` +### src/main/resources/application.properties +``` +logging.level.org.springframework: DEBUG +app.name=in28Minutes +welcome.message=Welcome message from property file! Welcome to ${app.name} + +basic.value=true +basic.message=Welcome to in28minutes +basic.number=200 +``` +### src/test/java/com/in28minutes/springboot/controller/SurveyControllerIT.java +``` +package com.in28minutes.springboot.controller; + +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import com.in28minutes.springboot.Application; +import com.in28minutes.springboot.model.Question; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class SurveyControllerIT { + + @LocalServerPort + private int port; + + TestRestTemplate restTemplate = new TestRestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + + @Before + public void before() { + + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + + } + + @Test + public void testRetrieveSurveyQuestion() { + + HttpEntity entity = new HttpEntity(null, headers); + + ResponseEntity response = restTemplate.exchange( + createURLWithPort("/surveys/Survey1/questions/Question1"), + HttpMethod.GET, entity, String.class); + + String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia}"; + + JSONAssert.assertEquals(expected, response.getBody(), false); + } + + @Test + public void retrieveAllSurveyQuestions() throws Exception { + + ResponseEntity> response = restTemplate.exchange( + createURLWithPort("/surveys/Survey1/questions"), + HttpMethod.GET, new HttpEntity("DUMMY_DOESNT_MATTER", + headers), + new ParameterizedTypeReference>() { + }); + + Question sampleQuestion = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + + assertTrue(response.getBody().contains(sampleQuestion)); + } + + @Test + public void addQuestion() { + + Question question = new Question("DOESNTMATTER", "Question1", "Russia", + Arrays.asList("India", "Russia", "United States", "China")); + + HttpEntity entity = new HttpEntity(question, headers); + + ResponseEntity response = restTemplate.exchange( + createURLWithPort("/surveys/Survey1/questions"), + HttpMethod.POST, entity, String.class); + + String actual = response.getHeaders().get(HttpHeaders.LOCATION).get(0); + + assertTrue(actual.contains("/surveys/Survey1/questions/")); + + } + + private String createURLWithPort(final String uri) { + return "http://localhost:" + port + uri; + } + +} +``` diff --git a/05.Spring-Boot-Advanced/Step23.zip b/05.Spring-Boot-Advanced/Step23.zip new file mode 100644 index 0000000..70524c7 Binary files /dev/null and b/05.Spring-Boot-Advanced/Step23.zip differ diff --git a/05.Spring-Boot-Advanced/Step24.md b/05.Spring-Boot-Advanced/Step24.md new file mode 100644 index 0000000..e6f90b9 --- /dev/null +++ b/05.Spring-Boot-Advanced/Step24.md @@ -0,0 +1,867 @@ +## What You Will Learn during this Step: +- Write a Unit Test for retrieving a specific question from a survey. +- Different between Unit Test and Integration Test +- Basics of Mocking +- MockMvc framework +- @MockBean +- Programming Tip + - Be an expert at Mockito - https://courses.in28minutes.com/p/mockito-for-beginner-in-5-steps + +## Useful Snippets and References +First Snippet +``` +package com.in28minutes.springboot.controller; + +import java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RunWith(SpringRunner.class) +@WebMvcTest(value = SurveyController.class) +public class SurveyControllerTest { + + @Autowired + private MockMvc mockMvc; + + // Mock @Autowired + @MockBean + private SurveyService surveyService; + + @Test + public void retrieveDetailsForQuestion() throws Exception { + Question mockQuestion = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + + Mockito.when( + surveyService.retrieveQuestion(Mockito.anyString(), Mockito + .anyString())).thenReturn(mockQuestion); + + RequestBuilder requestBuilder = MockMvcRequestBuilders.get( + "/surveys/Survey1/questions/Question1").accept( + MediaType.APPLICATION_JSON); + + MvcResult result = mockMvc.perform(requestBuilder).andReturn(); + + String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia}"; + + JSONAssert.assertEquals(expected, result.getResponse() + .getContentAsString(), false); + + // Assert + } +} + +``` + +## Exercises +- Write unit test for retrieve all questions for a survey + +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-data-rest + + + + com.h2database + h2 + + + + org.springframework.boot + spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + + @Profile("prod") + @Bean + public String dummy() { + return "something"; + } +} +``` +### src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java +``` +package com.in28minutes.springboot.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties("basic") +public class BasicConfiguration { + private boolean value; + private String message; + private int number; + + public boolean isValue() { + return value; + } + + public void setValue(boolean value) { + this.value = value; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } +} +``` +### src/main/java/com/in28minutes/springboot/controller/SurveyController.java +``` +package com.in28minutes.springboot.controller; + +import java.net.URI; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } + + // GET "/surveys/{surveyId}/questions/{questionId}" + @GetMapping("/surveys/{surveyId}/questions/{questionId}") + public Question retrieveDetailsForQuestion(@PathVariable String surveyId, + @PathVariable String questionId) { + return surveyService.retrieveQuestion(surveyId, questionId); + } + + // /surveys/{surveyId}/questions + @PostMapping("/surveys/{surveyId}/questions") + public ResponseEntity addQuestionToSurvey( + @PathVariable String surveyId, @RequestBody Question newQuestion) { + + Question question = surveyService.addQuestion(surveyId, newQuestion); + + if (question == null) + return ResponseEntity.noContent().build(); + + // Success - URI of the new resource in Response Header + // Status - created + // URI -> /surveys/{surveyId}/questions/{questionId} + // question.getQuestionId() + URI location = ServletUriComponentsBuilder.fromCurrentRequest().path( + "/{id}").buildAndExpand(question.getId()).toUri(); + + // Status + return ResponseEntity.created(location).build(); + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/User.java +``` +package com.in28minutes.springboot.jpa; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String name; + private String role; + + protected User() { + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return "User [id=" + id + ", name=" + name + ", role=" + role + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserCommandLineRunner.java +``` +package com.in28minutes.springboot.jpa; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +public class UserCommandLineRunner implements CommandLineRunner { + + private static final Logger log = LoggerFactory + .getLogger(UserCommandLineRunner.class); + + @Autowired + private UserRepository repository; + + @Override + public void run(String... args) throws Exception { + + repository.save(new User("Ranga", "Admin")); + repository.save(new User("Ravi", "User")); + repository.save(new User("Satish", "Admin")); + repository.save(new User("Raghu", "User")); + + for (User user : repository.findAll()) { + log.info(user.toString()); + } + + log.info("Admin users are....."); + log.info("____________________"); + for (User user : repository.findByRole("Admin")) { + log.info(user.toString()); + } + + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserRepository.java +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.CrudRepository; + +public interface UserRepository extends CrudRepository { + List findByRole(String role); +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserRestRepository.java +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; + +@RepositoryRestResource(path = "users", collectionResourceRel = "users") +public interface UserRestRepository extends + PagingAndSortingRepository { + List findByRole(@Param("role") String role); +} +``` +### src/main/java/com/in28minutes/springboot/model/Question.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Question other = (Question) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Survey.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + @Override + public String toString() { + return "Survey [id=" + id + ", title=" + title + ", description=" + + description + ", questions=" + questions + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/service/SurveyService.java +``` +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", Arrays + .asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.in28minutes.springboot.configuration.BasicConfiguration; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @Autowired + private BasicConfiguration configuration; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } + + @RequestMapping("/dynamic-configuration") + public Map dynamicConfiguration() { + Map map = new HashMap(); + map.put("message", configuration.getMessage()); + map.put("number", configuration.getNumber()); + map.put("value", configuration.isValue()); + + return map; + } + +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeService.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +//Spring to manage this bean and create an instance of this +@Component +public class WelcomeService { + + @Value("${welcome.message}") + private String welcomeMessage; + + public String retrieveWelcomeMessage() { + //Complex Method + return welcomeMessage; + } +} +``` +### src/main/resources/application-dev.properties +``` +logging.level.org.springframework: TRACE +``` +### src/main/resources/application-prod.properties +``` +logging.level.org.springframework: INFO +``` +### src/main/resources/application.properties +``` +logging.level.org.springframework: DEBUG +app.name=in28Minutes +welcome.message=Welcome message from property file! Welcome to ${app.name} + +basic.value=true +basic.message=Welcome to in28minutes +basic.number=200 +``` +### src/test/java/com/in28minutes/springboot/controller/SurveyControllerIT.java +``` +package com.in28minutes.springboot.controller; + +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import com.in28minutes.springboot.Application; +import com.in28minutes.springboot.model.Question; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class SurveyControllerIT { + + @LocalServerPort + private int port; + + TestRestTemplate restTemplate = new TestRestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + + @Before + public void before() { + + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + + } + + @Test + public void testRetrieveSurveyQuestion() { + + HttpEntity entity = new HttpEntity(null, headers); + + ResponseEntity response = restTemplate.exchange( + createURLWithPort("/surveys/Survey1/questions/Question1"), + HttpMethod.GET, entity, String.class); + + String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia}"; + + JSONAssert.assertEquals(expected, response.getBody(), false); + } + + @Test + public void retrieveAllSurveyQuestions() throws Exception { + + ResponseEntity> response = restTemplate.exchange( + createURLWithPort("/surveys/Survey1/questions"), + HttpMethod.GET, new HttpEntity("DUMMY_DOESNT_MATTER", + headers), + new ParameterizedTypeReference>() { + }); + + Question sampleQuestion = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + + assertTrue(response.getBody().contains(sampleQuestion)); + } + + @Test + public void addQuestion() { + + Question question = new Question("DOESNTMATTER", "Question1", "Russia", + Arrays.asList("India", "Russia", "United States", "China")); + + HttpEntity entity = new HttpEntity(question, headers); + + ResponseEntity response = restTemplate.exchange( + createURLWithPort("/surveys/Survey1/questions"), + HttpMethod.POST, entity, String.class); + + String actual = response.getHeaders().get(HttpHeaders.LOCATION).get(0); + + assertTrue(actual.contains("/surveys/Survey1/questions/")); + + } + + private String createURLWithPort(final String uri) { + return "http://localhost:" + port + uri; + } + +} +``` +### src/test/java/com/in28minutes/springboot/controller/SurveyControllerTest.java +``` +package com.in28minutes.springboot.controller; + +import java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RunWith(SpringRunner.class) +@WebMvcTest(value = SurveyController.class) +public class SurveyControllerTest { + + @Autowired + private MockMvc mockMvc; + + // Mock @Autowired + @MockBean + private SurveyService surveyService; + + @Test + public void retrieveDetailsForQuestion() throws Exception { + Question mockQuestion = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + + Mockito.when( + surveyService.retrieveQuestion(Mockito.anyString(), Mockito + .anyString())).thenReturn(mockQuestion); + + RequestBuilder requestBuilder = MockMvcRequestBuilders.get( + "/surveys/Survey1/questions/Question1").accept( + MediaType.APPLICATION_JSON); + + MvcResult result = mockMvc.perform(requestBuilder).andReturn(); + + String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia}"; + + JSONAssert.assertEquals(expected, result.getResponse() + .getContentAsString(), false); + + // Assert + } +} +``` diff --git a/05.Spring-Boot-Advanced/Step25.md b/05.Spring-Boot-Advanced/Step25.md new file mode 100644 index 0000000..b2ec2bd --- /dev/null +++ b/05.Spring-Boot-Advanced/Step25.md @@ -0,0 +1,889 @@ +## What You Will Learn during this Step: +- Exercise from previous step +- Unit test for createTodo + +## Useful Snippets and References +First Snippet +``` + @Test + public void retrieveSurveyQuestions() throws Exception { + List mockList = Arrays.asList( + new Question("Question1", "First Alphabet", "A", Arrays.asList( + "A", "B", "C", "D")), + new Question("Question2", "Last Alphabet", "Z", Arrays.asList( + "A", "X", "Y", "Z"))); + + when(service.retrieveQuestions(anyString())).thenReturn(mockList); + + MvcResult result = mvc + .perform( + MockMvcRequestBuilders + .get("/surveys/Survey1/questions").accept( + MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn(); + + String expected = "[" + + "{id:Question1,description:First Alphabet,correctAnswer:A,options:[A,B,C,D]}," + + "{id:Question2,description:Last Alphabet,correctAnswer:Z,options:[A,X,Y,Z]}" + + "]"; + + JSONAssert.assertEquals(expected, result.getResponse() + .getContentAsString(), false); + } +``` +Second Snippet +``` + @Test + public void createSurveyQuestion() throws Exception { + Question mockQuestion = new Question("1", "Smallest Number", "1", + Arrays.asList("1", "2", "3", "4")); + + String questionJson = "{\"description\":\"Smallest Number\",\"correctAnswer\":\"1\",\"options\":[\"1\",\"2\",\"3\",\"4\"]}"; + //surveyService.addQuestion to respond back with mockQuestion + Mockito.when( + surveyService.addQuestion(Mockito.anyString(), Mockito + .any(Question.class))).thenReturn(mockQuestion); + + //Send question as body to /surveys/Survey1/questions + RequestBuilder requestBuilder = MockMvcRequestBuilders.post( + "/surveys/Survey1/questions") + .accept(MediaType.APPLICATION_JSON).content(questionJson) + .contentType(MediaType.APPLICATION_JSON); + + MvcResult result = mockMvc.perform(requestBuilder).andReturn(); + + MockHttpServletResponse response = result.getResponse(); + + assertEquals(HttpStatus.CREATED.value(), response.getStatus()); + + assertEquals("http://localhost/surveys/Survey1/questions/1", response + .getHeader(HttpHeaders.LOCATION)); + + } +``` + +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-data-rest + + + + com.h2database + h2 + + + + org.springframework.boot + spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + + @Profile("prod") + @Bean + public String dummy() { + return "something"; + } +} +``` +### src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java +``` +package com.in28minutes.springboot.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties("basic") +public class BasicConfiguration { + private boolean value; + private String message; + private int number; + + public boolean isValue() { + return value; + } + + public void setValue(boolean value) { + this.value = value; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } +} +``` +### src/main/java/com/in28minutes/springboot/controller/SurveyController.java +``` +package com.in28minutes.springboot.controller; + +import java.net.URI; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } + + // GET "/surveys/{surveyId}/questions/{questionId}" + @GetMapping("/surveys/{surveyId}/questions/{questionId}") + public Question retrieveDetailsForQuestion(@PathVariable String surveyId, + @PathVariable String questionId) { + return surveyService.retrieveQuestion(surveyId, questionId); + } + + // /surveys/{surveyId}/questions + @PostMapping("/surveys/{surveyId}/questions") + public ResponseEntity addQuestionToSurvey( + @PathVariable String surveyId, @RequestBody Question newQuestion) { + + Question question = surveyService.addQuestion(surveyId, newQuestion); + + if (question == null) + return ResponseEntity.noContent().build(); + + // Success - URI of the new resource in Response Header + // Status - created + // URI -> /surveys/{surveyId}/questions/{questionId} + // question.getQuestionId() + URI location = ServletUriComponentsBuilder.fromCurrentRequest().path( + "/{id}").buildAndExpand(question.getId()).toUri(); + + // Status + return ResponseEntity.created(location).build(); + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/User.java +``` +package com.in28minutes.springboot.jpa; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String name; + private String role; + + protected User() { + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return "User [id=" + id + ", name=" + name + ", role=" + role + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserCommandLineRunner.java +``` +package com.in28minutes.springboot.jpa; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +public class UserCommandLineRunner implements CommandLineRunner { + + private static final Logger log = LoggerFactory + .getLogger(UserCommandLineRunner.class); + + @Autowired + private UserRepository repository; + + @Override + public void run(String... args) throws Exception { + + repository.save(new User("Ranga", "Admin")); + repository.save(new User("Ravi", "User")); + repository.save(new User("Satish", "Admin")); + repository.save(new User("Raghu", "User")); + + for (User user : repository.findAll()) { + log.info(user.toString()); + } + + log.info("Admin users are....."); + log.info("____________________"); + for (User user : repository.findByRole("Admin")) { + log.info(user.toString()); + } + + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserRepository.java +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.CrudRepository; + +public interface UserRepository extends CrudRepository { + List findByRole(String role); +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserRestRepository.java +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; + +@RepositoryRestResource(path = "users", collectionResourceRel = "users") +public interface UserRestRepository extends + PagingAndSortingRepository { + List findByRole(@Param("role") String role); +} +``` +### src/main/java/com/in28minutes/springboot/model/Question.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Question other = (Question) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Survey.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + @Override + public String toString() { + return "Survey [id=" + id + ", title=" + title + ", description=" + + description + ", questions=" + questions + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/service/SurveyService.java +``` +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", Arrays + .asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.in28minutes.springboot.configuration.BasicConfiguration; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @Autowired + private BasicConfiguration configuration; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } + + @RequestMapping("/dynamic-configuration") + public Map dynamicConfiguration() { + Map map = new HashMap(); + map.put("message", configuration.getMessage()); + map.put("number", configuration.getNumber()); + map.put("value", configuration.isValue()); + + return map; + } + +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeService.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +//Spring to manage this bean and create an instance of this +@Component +public class WelcomeService { + + @Value("${welcome.message}") + private String welcomeMessage; + + public String retrieveWelcomeMessage() { + //Complex Method + return welcomeMessage; + } +} +``` +### src/main/resources/application-dev.properties +``` +logging.level.org.springframework: TRACE +``` +### src/main/resources/application-prod.properties +``` +logging.level.org.springframework: INFO +``` +### src/main/resources/application.properties +``` +logging.level.org.springframework: DEBUG +app.name=in28Minutes +welcome.message=Welcome message from property file! Welcome to ${app.name} + +basic.value=true +basic.message=Welcome to in28minutes +basic.number=200 +``` +### src/test/java/com/in28minutes/springboot/controller/SurveyControllerIT.java +``` +package com.in28minutes.springboot.controller; + +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; + +import com.in28minutes.springboot.Application; +import com.in28minutes.springboot.model.Question; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class SurveyControllerIT { + + @LocalServerPort + private int port; + + TestRestTemplate restTemplate = new TestRestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + + @Before + public void before() { + + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + + } + + @Test + public void testRetrieveSurveyQuestion() { + + HttpEntity entity = new HttpEntity(null, headers); + + ResponseEntity response = restTemplate.exchange( + createURLWithPort("/surveys/Survey1/questions/Question1"), + HttpMethod.GET, entity, String.class); + + String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia}"; + + JSONAssert.assertEquals(expected, response.getBody(), false); + } + + @Test + public void retrieveAllSurveyQuestions() throws Exception { + + ResponseEntity> response = restTemplate.exchange( + createURLWithPort("/surveys/Survey1/questions"), + HttpMethod.GET, new HttpEntity("DUMMY_DOESNT_MATTER", + headers), + new ParameterizedTypeReference>() { + }); + + Question sampleQuestion = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + + assertTrue(response.getBody().contains(sampleQuestion)); + } + + @Test + public void addQuestion() { + + Question question = new Question("DOESNTMATTER", "Question1", "Russia", + Arrays.asList("India", "Russia", "United States", "China")); + + HttpEntity entity = new HttpEntity(question, headers); + + ResponseEntity response = restTemplate.exchange( + createURLWithPort("/surveys/Survey1/questions"), + HttpMethod.POST, entity, String.class); + + String actual = response.getHeaders().get(HttpHeaders.LOCATION).get(0); + + assertTrue(actual.contains("/surveys/Survey1/questions/")); + + } + + private String createURLWithPort(final String uri) { + return "http://localhost:" + port + uri; + } + +} +``` +### src/test/java/com/in28minutes/springboot/controller/SurveyControllerTest.java +``` +package com.in28minutes.springboot.controller; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RunWith(SpringRunner.class) +@WebMvcTest(value = SurveyController.class) +public class SurveyControllerTest { + + @Autowired + private MockMvc mockMvc; + + // Mock @Autowired + @MockBean + private SurveyService surveyService; + + @Test + public void retrieveDetailsForQuestion() throws Exception { + Question mockQuestion = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + + Mockito.when( + surveyService.retrieveQuestion(Mockito.anyString(), Mockito + .anyString())).thenReturn(mockQuestion); + + RequestBuilder requestBuilder = MockMvcRequestBuilders.get( + "/surveys/Survey1/questions/Question1").accept( + MediaType.APPLICATION_JSON); + + MvcResult result = mockMvc.perform(requestBuilder).andReturn(); + + String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia}"; + + JSONAssert.assertEquals(expected, result.getResponse() + .getContentAsString(), false); + + // Assert + } + + @Test + public void createSurveyQuestion() throws Exception { + Question mockQuestion = new Question("1", "Smallest Number", "1", + Arrays.asList("1", "2", "3", "4")); + + String questionJson = "{\"description\":\"Smallest Number\",\"correctAnswer\":\"1\",\"options\":[\"1\",\"2\",\"3\",\"4\"]}"; + //surveyService.addQuestion to respond back with mockQuestion + Mockito.when( + surveyService.addQuestion(Mockito.anyString(), Mockito + .any(Question.class))).thenReturn(mockQuestion); + + //Send question as body to /surveys/Survey1/questions + RequestBuilder requestBuilder = MockMvcRequestBuilders.post( + "/surveys/Survey1/questions") + .accept(MediaType.APPLICATION_JSON).content(questionJson) + .contentType(MediaType.APPLICATION_JSON); + + MvcResult result = mockMvc.perform(requestBuilder).andReturn(); + + MockHttpServletResponse response = result.getResponse(); + + assertEquals(HttpStatus.CREATED.value(), response.getStatus()); + + assertEquals("http://localhost/surveys/Survey1/questions/1", response + .getHeader(HttpHeaders.LOCATION)); + } +} +``` diff --git a/05.Spring-Boot-Advanced/Step25.zip b/05.Spring-Boot-Advanced/Step25.zip new file mode 100644 index 0000000..099999c Binary files /dev/null and b/05.Spring-Boot-Advanced/Step25.zip differ diff --git a/05.Spring-Boot-Advanced/Step26.md b/05.Spring-Boot-Advanced/Step26.md new file mode 100644 index 0000000..03516d0 --- /dev/null +++ b/05.Spring-Boot-Advanced/Step26.md @@ -0,0 +1,30 @@ +## What You Will Learn during this Step: +- Securing our services with Basic Authentication using Spring Security +- Executing Requests using Basic Authentication with Postman + - default user name is user + - default security password is printed in console + +## Useful Snippets and References +First Snippet +``` + + org.springframework.boot + spring-boot-starter-security + +``` + +Second Snippet +``` +Using default security password: +``` + +Third Snippet : Executing a GET to http://localhost:8080/surveys/Survey1/questions/ +``` +{ + "timestamp": 1483514297025, + "status": 401, + "error": "Unauthorized", + "message": "Full authentication is required to access this resource", + "path": "/surveys/Survey1/questions/" +} +``` diff --git a/05.Spring-Boot-Advanced/Step27.md b/05.Spring-Boot-Advanced/Step27.md new file mode 100644 index 0000000..5770429 --- /dev/null +++ b/05.Spring-Boot-Advanced/Step27.md @@ -0,0 +1,933 @@ +## What You Will Learn during this Step: +- Configure different user roles for survey and other services +- Update integration tests +- Update unit tests + +## Useful Snippets and References +First Snippet +``` +package com.in28minutes.springboot.security; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +@EnableWebSecurity +public class SecurityConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(AuthenticationManagerBuilder auth) + throws Exception { + auth.inMemoryAuthentication().withUser("user1").password("secret1") + .roles("USER").and().withUser("admin1").password("secret1") + .roles("ADMIN"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.httpBasic().and().authorizeRequests().antMatchers("/surveys/**") + .hasRole("USER").antMatchers("/users/**").hasRole("USER") + .antMatchers("/**").hasRole("ADMIN").and().csrf().disable() + .headers().frameOptions().disable(); + } +} +``` +Second Snippet +``` + HttpHeaders headers = createHeaders("user1", "secret1"); + + HttpHeaders createHeaders(String username, String password) { + return new HttpHeaders() { + { + String auth = username + ":" + password; + byte[] encodedAuth = Base64.encode(auth.getBytes(Charset + .forName("US-ASCII"))); + String authHeader = "Basic " + new String(encodedAuth); + set("Authorization", authHeader); + } + }; + } + +``` + +Third Snippet +``` +@WebMvcTest(value = SurveyController.class, secure = false) +``` +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-data-rest + + + + com.h2database + h2 + + + + org.springframework.boot + spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + + @Profile("prod") + @Bean + public String dummy() { + return "something"; + } +} +``` +### src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java +``` +package com.in28minutes.springboot.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties("basic") +public class BasicConfiguration { + private boolean value; + private String message; + private int number; + + public boolean isValue() { + return value; + } + + public void setValue(boolean value) { + this.value = value; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } +} +``` +### src/main/java/com/in28minutes/springboot/controller/SurveyController.java +``` +package com.in28minutes.springboot.controller; + +import java.net.URI; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } + + // GET "/surveys/{surveyId}/questions/{questionId}" + @GetMapping("/surveys/{surveyId}/questions/{questionId}") + public Question retrieveDetailsForQuestion(@PathVariable String surveyId, + @PathVariable String questionId) { + return surveyService.retrieveQuestion(surveyId, questionId); + } + + // /surveys/{surveyId}/questions + @PostMapping("/surveys/{surveyId}/questions") + public ResponseEntity addQuestionToSurvey( + @PathVariable String surveyId, @RequestBody Question newQuestion) { + + Question question = surveyService.addQuestion(surveyId, newQuestion); + + if (question == null) + return ResponseEntity.noContent().build(); + + // Success - URI of the new resource in Response Header + // Status - created + // URI -> /surveys/{surveyId}/questions/{questionId} + // question.getQuestionId() + URI location = ServletUriComponentsBuilder.fromCurrentRequest().path( + "/{id}").buildAndExpand(question.getId()).toUri(); + + // Status + return ResponseEntity.created(location).build(); + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/User.java +``` +package com.in28minutes.springboot.jpa; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String name; + private String role; + + protected User() { + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return "User [id=" + id + ", name=" + name + ", role=" + role + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserCommandLineRunner.java +``` +package com.in28minutes.springboot.jpa; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +public class UserCommandLineRunner implements CommandLineRunner { + + private static final Logger log = LoggerFactory + .getLogger(UserCommandLineRunner.class); + + @Autowired + private UserRepository repository; + + @Override + public void run(String... args) throws Exception { + + repository.save(new User("Ranga", "Admin")); + repository.save(new User("Ravi", "User")); + repository.save(new User("Satish", "Admin")); + repository.save(new User("Raghu", "User")); + + for (User user : repository.findAll()) { + log.info(user.toString()); + } + + log.info("Admin users are....."); + log.info("____________________"); + for (User user : repository.findByRole("Admin")) { + log.info(user.toString()); + } + + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserRepository.java +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.CrudRepository; + +public interface UserRepository extends CrudRepository { + List findByRole(String role); +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserRestRepository.java +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; + +@RepositoryRestResource(path = "users", collectionResourceRel = "users") +public interface UserRestRepository extends + PagingAndSortingRepository { + List findByRole(@Param("role") String role); +} +``` +### src/main/java/com/in28minutes/springboot/model/Question.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Question other = (Question) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Survey.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + @Override + public String toString() { + return "Survey [id=" + id + ", title=" + title + ", description=" + + description + ", questions=" + questions + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/security/SecurityConfig.java +``` +package com.in28minutes.springboot.security; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +public class SecurityConfig extends WebSecurityConfigurerAdapter { + // Authentication : User --> Roles + protected void configure(AuthenticationManagerBuilder auth) + throws Exception { + auth.inMemoryAuthentication().withUser("user1").password("secret1") + .roles("USER").and().withUser("admin1").password("secret1") + .roles("USER", "ADMIN"); + } + + // Authorization : Role -> Access + // survey -> USER + protected void configure(HttpSecurity http) throws Exception { + http.httpBasic().and().authorizeRequests().antMatchers("/surveys/**") + .hasRole("USER").antMatchers("/users/**").hasRole("USER") + .antMatchers("/**").hasRole("ADMIN").and().csrf().disable() + .headers().frameOptions().disable(); + } + +} +``` +### src/main/java/com/in28minutes/springboot/service/SurveyService.java +``` +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", Arrays + .asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.in28minutes.springboot.configuration.BasicConfiguration; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @Autowired + private BasicConfiguration configuration; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } + + @RequestMapping("/dynamic-configuration") + public Map dynamicConfiguration() { + Map map = new HashMap(); + map.put("message", configuration.getMessage()); + map.put("number", configuration.getNumber()); + map.put("value", configuration.isValue()); + + return map; + } + +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeService.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +//Spring to manage this bean and create an instance of this +@Component +public class WelcomeService { + + @Value("${welcome.message}") + private String welcomeMessage; + + public String retrieveWelcomeMessage() { + //Complex Method + return welcomeMessage; + } +} +``` +### src/main/resources/application-dev.properties +``` +logging.level.org.springframework: TRACE +``` +### src/main/resources/application-prod.properties +``` +logging.level.org.springframework: INFO +``` +### src/main/resources/application.properties +``` +logging.level.org.springframework: DEBUG +app.name=in28Minutes +welcome.message=Welcome message from property file! Welcome to ${app.name} + +basic.value=true +basic.message=Welcome to in28minutes +basic.number=200 +``` +### src/test/java/com/in28minutes/springboot/controller/SurveyControllerIT.java +``` +package com.in28minutes.springboot.controller; + +import static org.junit.Assert.assertTrue; + +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.crypto.codec.Base64; +import org.springframework.test.context.junit4.SpringRunner; + +import com.in28minutes.springboot.Application; +import com.in28minutes.springboot.model.Question; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class SurveyControllerIT { + + @LocalServerPort + private int port; + + TestRestTemplate restTemplate = new TestRestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + + @Before + public void before() { + headers.add("Authorization", createHttpAuthenticationHeaderValue( + "user1", "secret1")); + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + } + + @Test + public void testRetrieveSurveyQuestion() { + + HttpEntity entity = new HttpEntity(null, headers); + + ResponseEntity response = restTemplate.exchange( + createURLWithPort("/surveys/Survey1/questions/Question1"), + HttpMethod.GET, entity, String.class); + + String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia}"; + + JSONAssert.assertEquals(expected, response.getBody(), false); + } + + @Test + public void retrieveAllSurveyQuestions() throws Exception { + + ResponseEntity> response = restTemplate.exchange( + createURLWithPort("/surveys/Survey1/questions"), + HttpMethod.GET, new HttpEntity("DUMMY_DOESNT_MATTER", + headers), + new ParameterizedTypeReference>() { + }); + + Question sampleQuestion = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + + assertTrue(response.getBody().contains(sampleQuestion)); + } + + @Test + public void addQuestion() { + + Question question = new Question("DOESNTMATTER", "Question1", "Russia", + Arrays.asList("India", "Russia", "United States", "China")); + + HttpEntity entity = new HttpEntity(question, headers); + + ResponseEntity response = restTemplate.exchange( + createURLWithPort("/surveys/Survey1/questions"), + HttpMethod.POST, entity, String.class); + + String actual = response.getHeaders().get(HttpHeaders.LOCATION).get(0); + + assertTrue(actual.contains("/surveys/Survey1/questions/")); + + } + + private String createURLWithPort(final String uri) { + return "http://localhost:" + port + uri; + } + + private String createHttpAuthenticationHeaderValue(String userId, + String password) { + + String auth = userId + ":" + password; + + byte[] encodedAuth = Base64.encode(auth.getBytes(Charset + .forName("US-ASCII"))); + + String headerValue = "Basic " + new String(encodedAuth); + + return headerValue; + } + +} +``` +### src/test/java/com/in28minutes/springboot/controller/SurveyControllerTest.java +``` +package com.in28minutes.springboot.controller; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RunWith(SpringRunner.class) +@WebMvcTest(value = SurveyController.class, secure = false) +public class SurveyControllerTest { + + @Autowired + private MockMvc mockMvc; + + // Mock @Autowired + @MockBean + private SurveyService surveyService; + + @Test + public void retrieveDetailsForQuestion() throws Exception { + Question mockQuestion = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + + Mockito.when( + surveyService.retrieveQuestion(Mockito.anyString(), Mockito + .anyString())).thenReturn(mockQuestion); + + RequestBuilder requestBuilder = MockMvcRequestBuilders.get( + "/surveys/Survey1/questions/Question1").accept( + MediaType.APPLICATION_JSON); + + MvcResult result = mockMvc.perform(requestBuilder).andReturn(); + + String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia}"; + + JSONAssert.assertEquals(expected, result.getResponse() + .getContentAsString(), false); + + // Assert + } + + @Test + public void createSurveyQuestion() throws Exception { + Question mockQuestion = new Question("1", "Smallest Number", "1", + Arrays.asList("1", "2", "3", "4")); + + String questionJson = "{\"description\":\"Smallest Number\",\"correctAnswer\":\"1\",\"options\":[\"1\",\"2\",\"3\",\"4\"]}"; + //surveyService.addQuestion to respond back with mockQuestion + Mockito.when( + surveyService.addQuestion(Mockito.anyString(), Mockito + .any(Question.class))).thenReturn(mockQuestion); + + //Send question as body to /surveys/Survey1/questions + RequestBuilder requestBuilder = MockMvcRequestBuilders.post( + "/surveys/Survey1/questions") + .accept(MediaType.APPLICATION_JSON).content(questionJson) + .contentType(MediaType.APPLICATION_JSON); + + MvcResult result = mockMvc.perform(requestBuilder).andReturn(); + + MockHttpServletResponse response = result.getResponse(); + + assertEquals(HttpStatus.CREATED.value(), response.getStatus()); + + assertEquals("http://localhost/surveys/Survey1/questions/1", response + .getHeader(HttpHeaders.LOCATION)); + } +} +``` diff --git a/05.Spring-Boot-Advanced/Step27.zip b/05.Spring-Boot-Advanced/Step27.zip new file mode 100644 index 0000000..05cb15e Binary files /dev/null and b/05.Spring-Boot-Advanced/Step27.zip differ diff --git a/05.Spring-Boot-Advanced/Step28.md b/05.Spring-Boot-Advanced/Step28.md new file mode 100644 index 0000000..cbaf406 --- /dev/null +++ b/05.Spring-Boot-Advanced/Step28.md @@ -0,0 +1,899 @@ +## What You Will Learn during this Step: +- A Deep Dive into Autoconfiguration + - spring-boot-autoconfigure-1.4.0.RELEASE.jar + - /META-INF/spring.factories + - Package org.springframework.boot.autoconfigure +- Lets look at the log in debug mode! +- Examples + - JdbcTemplateAutoConfiguration + - HttpMessageConvertersAutoConfiguration +- Programming Tips + - Understand Design Patterns + - https://www.youtube.com/watch?v=Vp7q_pE7Fzg + - Understand Modern Development Practices + - https://www.youtube.com/watch?v=0Kqzfyp-w4s + +## Useful Snippets and References +``` + String[] beanNames = ctx.getBeanDefinitionNames(); + Arrays.sort(beanNames); + + for (String beanName : beanNames) { + System.out.println(beanName); + } +``` +## Files List +### pom.xml +``` + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-data-rest + + + + com.h2database + h2 + + + + org.springframework.boot + spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +### src/main/java/com/in28minutes/springboot/Application.java +``` +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + + @Profile("prod") + @Bean + public String dummy() { + return "something"; + } +} +``` +### src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java +``` +package com.in28minutes.springboot.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties("basic") +public class BasicConfiguration { + private boolean value; + private String message; + private int number; + + public boolean isValue() { + return value; + } + + public void setValue(boolean value) { + this.value = value; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } +} +``` +### src/main/java/com/in28minutes/springboot/controller/SurveyController.java +``` +package com.in28minutes.springboot.controller; + +import java.net.URI; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } + + // GET "/surveys/{surveyId}/questions/{questionId}" + @GetMapping("/surveys/{surveyId}/questions/{questionId}") + public Question retrieveDetailsForQuestion(@PathVariable String surveyId, + @PathVariable String questionId) { + return surveyService.retrieveQuestion(surveyId, questionId); + } + + // /surveys/{surveyId}/questions + @PostMapping("/surveys/{surveyId}/questions") + public ResponseEntity addQuestionToSurvey( + @PathVariable String surveyId, @RequestBody Question newQuestion) { + + Question question = surveyService.addQuestion(surveyId, newQuestion); + + if (question == null) + return ResponseEntity.noContent().build(); + + // Success - URI of the new resource in Response Header + // Status - created + // URI -> /surveys/{surveyId}/questions/{questionId} + // question.getQuestionId() + URI location = ServletUriComponentsBuilder.fromCurrentRequest().path( + "/{id}").buildAndExpand(question.getId()).toUri(); + + // Status + return ResponseEntity.created(location).build(); + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/User.java +``` +package com.in28minutes.springboot.jpa; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String name; + private String role; + + protected User() { + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return "User [id=" + id + ", name=" + name + ", role=" + role + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserCommandLineRunner.java +``` +package com.in28minutes.springboot.jpa; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +public class UserCommandLineRunner implements CommandLineRunner { + + private static final Logger log = LoggerFactory + .getLogger(UserCommandLineRunner.class); + + @Autowired + private UserRepository repository; + + @Override + public void run(String... args) throws Exception { + + repository.save(new User("Ranga", "Admin")); + repository.save(new User("Ravi", "User")); + repository.save(new User("Satish", "Admin")); + repository.save(new User("Raghu", "User")); + + for (User user : repository.findAll()) { + log.info(user.toString()); + } + + log.info("Admin users are....."); + log.info("____________________"); + for (User user : repository.findByRole("Admin")) { + log.info(user.toString()); + } + + } + +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserRepository.java +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.CrudRepository; + +public interface UserRepository extends CrudRepository { + List findByRole(String role); +} +``` +### src/main/java/com/in28minutes/springboot/jpa/UserRestRepository.java +``` +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; + +@RepositoryRestResource(path = "users", collectionResourceRel = "users") +public interface UserRestRepository extends + PagingAndSortingRepository { + List findByRole(@Param("role") String role); +} +``` +### src/main/java/com/in28minutes/springboot/model/Question.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Question other = (Question) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} +``` +### src/main/java/com/in28minutes/springboot/model/Survey.java +``` +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + @Override + public String toString() { + return "Survey [id=" + id + ", title=" + title + ", description=" + + description + ", questions=" + questions + "]"; + } + +} +``` +### src/main/java/com/in28minutes/springboot/security/SecurityConfig.java +``` +package com.in28minutes.springboot.security; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +public class SecurityConfig extends WebSecurityConfigurerAdapter { + // Authentication : User --> Roles + protected void configure(AuthenticationManagerBuilder auth) + throws Exception { + auth.inMemoryAuthentication().withUser("user1").password("secret1") + .roles("USER").and().withUser("admin1").password("secret1") + .roles("USER", "ADMIN"); + } + + // Authorization : Role -> Access + // survey -> USER + protected void configure(HttpSecurity http) throws Exception { + http.httpBasic().and().authorizeRequests().antMatchers("/surveys/**") + .hasRole("USER").antMatchers("/users/**").hasRole("USER") + .antMatchers("/**").hasRole("ADMIN").and().csrf().disable() + .headers().frameOptions().disable(); + } + +} +``` +### src/main/java/com/in28minutes/springboot/service/SurveyService.java +``` +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", Arrays + .asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeController.java +``` +package com.in28minutes.springboot; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.in28minutes.springboot.configuration.BasicConfiguration; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @Autowired + private BasicConfiguration configuration; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } + + @RequestMapping("/dynamic-configuration") + public Map dynamicConfiguration() { + Map map = new HashMap(); + map.put("message", configuration.getMessage()); + map.put("number", configuration.getNumber()); + map.put("value", configuration.isValue()); + + return map; + } + +} +``` +### src/main/java/com/in28minutes/springboot/WelcomeService.java +``` +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +//Spring to manage this bean and create an instance of this +@Component +public class WelcomeService { + + @Value("${welcome.message}") + private String welcomeMessage; + + public String retrieveWelcomeMessage() { + //Complex Method + return welcomeMessage; + } +} +``` +### src/main/resources/application-dev.properties +``` +logging.level.org.springframework: TRACE +``` +### src/main/resources/application-prod.properties +``` +logging.level.org.springframework: INFO +``` +### src/main/resources/application.properties +``` +logging.level.org.springframework: DEBUG +app.name=in28Minutes +welcome.message=Welcome message from property file! Welcome to ${app.name} + +basic.value=true +basic.message=Welcome to in28minutes +basic.number=200 +``` +### src/test/java/com/in28minutes/springboot/controller/SurveyControllerIT.java +``` +package com.in28minutes.springboot.controller; + +import static org.junit.Assert.assertTrue; + +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.crypto.codec.Base64; +import org.springframework.test.context.junit4.SpringRunner; + +import com.in28minutes.springboot.Application; +import com.in28minutes.springboot.model.Question; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class SurveyControllerIT { + + @LocalServerPort + private int port; + + TestRestTemplate restTemplate = new TestRestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + + @Before + public void before() { + headers.add("Authorization", createHttpAuthenticationHeaderValue( + "user1", "secret1")); + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + } + + @Test + public void testRetrieveSurveyQuestion() { + + HttpEntity entity = new HttpEntity(null, headers); + + ResponseEntity response = restTemplate.exchange( + createURLWithPort("/surveys/Survey1/questions/Question1"), + HttpMethod.GET, entity, String.class); + + String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia}"; + + JSONAssert.assertEquals(expected, response.getBody(), false); + } + + @Test + public void retrieveAllSurveyQuestions() throws Exception { + + ResponseEntity> response = restTemplate.exchange( + createURLWithPort("/surveys/Survey1/questions"), + HttpMethod.GET, new HttpEntity("DUMMY_DOESNT_MATTER", + headers), + new ParameterizedTypeReference>() { + }); + + Question sampleQuestion = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + + assertTrue(response.getBody().contains(sampleQuestion)); + } + + @Test + public void addQuestion() { + + Question question = new Question("DOESNTMATTER", "Question1", "Russia", + Arrays.asList("India", "Russia", "United States", "China")); + + HttpEntity entity = new HttpEntity(question, headers); + + ResponseEntity response = restTemplate.exchange( + createURLWithPort("/surveys/Survey1/questions"), + HttpMethod.POST, entity, String.class); + + String actual = response.getHeaders().get(HttpHeaders.LOCATION).get(0); + + assertTrue(actual.contains("/surveys/Survey1/questions/")); + + } + + private String createURLWithPort(final String uri) { + return "http://localhost:" + port + uri; + } + + private String createHttpAuthenticationHeaderValue(String userId, + String password) { + + String auth = userId + ":" + password; + + byte[] encodedAuth = Base64.encode(auth.getBytes(Charset + .forName("US-ASCII"))); + + String headerValue = "Basic " + new String(encodedAuth); + + return headerValue; + } + +} +``` +### src/test/java/com/in28minutes/springboot/controller/SurveyControllerTest.java +``` +package com.in28minutes.springboot.controller; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RunWith(SpringRunner.class) +@WebMvcTest(value = SurveyController.class, secure = false) +public class SurveyControllerTest { + + @Autowired + private MockMvc mockMvc; + + // Mock @Autowired + @MockBean + private SurveyService surveyService; + + @Test + public void retrieveDetailsForQuestion() throws Exception { + Question mockQuestion = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + + Mockito.when( + surveyService.retrieveQuestion(Mockito.anyString(), Mockito + .anyString())).thenReturn(mockQuestion); + + RequestBuilder requestBuilder = MockMvcRequestBuilders.get( + "/surveys/Survey1/questions/Question1").accept( + MediaType.APPLICATION_JSON); + + MvcResult result = mockMvc.perform(requestBuilder).andReturn(); + + String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia}"; + + JSONAssert.assertEquals(expected, result.getResponse() + .getContentAsString(), false); + + // Assert + } + + @Test + public void createSurveyQuestion() throws Exception { + Question mockQuestion = new Question("1", "Smallest Number", "1", + Arrays.asList("1", "2", "3", "4")); + + String questionJson = "{\"description\":\"Smallest Number\",\"correctAnswer\":\"1\",\"options\":[\"1\",\"2\",\"3\",\"4\"]}"; + //surveyService.addQuestion to respond back with mockQuestion + Mockito.when( + surveyService.addQuestion(Mockito.anyString(), Mockito + .any(Question.class))).thenReturn(mockQuestion); + + //Send question as body to /surveys/Survey1/questions + RequestBuilder requestBuilder = MockMvcRequestBuilders.post( + "/surveys/Survey1/questions") + .accept(MediaType.APPLICATION_JSON).content(questionJson) + .contentType(MediaType.APPLICATION_JSON); + + MvcResult result = mockMvc.perform(requestBuilder).andReturn(); + + MockHttpServletResponse response = result.getResponse(); + + assertEquals(HttpStatus.CREATED.value(), response.getStatus()); + + assertEquals("http://localhost/surveys/Survey1/questions/1", response + .getHeader(HttpHeaders.LOCATION)); + } +} +``` diff --git a/05.Spring-Boot-Advanced/Step28.zip b/05.Spring-Boot-Advanced/Step28.zip new file mode 100644 index 0000000..05cb15e Binary files /dev/null and b/05.Spring-Boot-Advanced/Step28.zip differ diff --git a/05.Spring-Boot-Advanced/StepReference.md b/05.Spring-Boot-Advanced/StepReference.md new file mode 100644 index 0000000..f409414 --- /dev/null +++ b/05.Spring-Boot-Advanced/StepReference.md @@ -0,0 +1,16 @@ +## What You Will Learn during this Step: +- First +- Second +- Third + +## Useful Snippets and References +First Snippet +``` +``` +Second Snippet +``` +``` + +## Exercises + +## Files List diff --git a/05.Spring-Boot-Advanced/pom.xml b/05.Spring-Boot-Advanced/pom.xml new file mode 100644 index 0000000..06c481e --- /dev/null +++ b/05.Spring-Boot-Advanced/pom.xml @@ -0,0 +1,74 @@ + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-data-rest + + + + com.h2database + h2 + + + + org.springframework.boot + spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file diff --git a/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/Application.java b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/Application.java new file mode 100644 index 0000000..b972283 --- /dev/null +++ b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/Application.java @@ -0,0 +1,22 @@ +package com.in28minutes.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + ApplicationContext ctx = SpringApplication.run(Application.class, args); + + } + + @Profile("prod") + @Bean + public String dummy() { + return "something"; + } +} \ No newline at end of file diff --git a/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/WelcomeController.java b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/WelcomeController.java new file mode 100644 index 0000000..fcb9f38 --- /dev/null +++ b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/WelcomeController.java @@ -0,0 +1,37 @@ +package com.in28minutes.springboot; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.in28minutes.springboot.configuration.BasicConfiguration; + +@RestController +public class WelcomeController { + + //Auto wiring + @Autowired + private WelcomeService service; + + @Autowired + private BasicConfiguration configuration; + + @RequestMapping("/welcome") + public String welcome() { + return service.retrieveWelcomeMessage(); + } + + @RequestMapping("/dynamic-configuration") + public Map dynamicConfiguration() { + Map map = new HashMap(); + map.put("message", configuration.getMessage()); + map.put("number", configuration.getNumber()); + map.put("value", configuration.isValue()); + + return map; + } + +} diff --git a/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/WelcomeService.java b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/WelcomeService.java new file mode 100644 index 0000000..8f4353b --- /dev/null +++ b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/WelcomeService.java @@ -0,0 +1,17 @@ +package com.in28minutes.springboot; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +//Spring to manage this bean and create an instance of this +@Component +public class WelcomeService { + + @Value("${welcome.message}") + private String welcomeMessage; + + public String retrieveWelcomeMessage() { + //Complex Method + return welcomeMessage; + } +} \ No newline at end of file diff --git a/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java new file mode 100644 index 0000000..af16020 --- /dev/null +++ b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java @@ -0,0 +1,36 @@ +package com.in28minutes.springboot.configuration; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties("basic") +public class BasicConfiguration { + private boolean value; + private String message; + private int number; + + public boolean isValue() { + return value; + } + + public void setValue(boolean value) { + this.value = value; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } +} diff --git a/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/controller/SurveyController.java b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/controller/SurveyController.java new file mode 100644 index 0000000..dcc5be3 --- /dev/null +++ b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/controller/SurveyController.java @@ -0,0 +1,56 @@ +package com.in28minutes.springboot.controller; + +import java.net.URI; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RestController +class SurveyController { + @Autowired + private SurveyService surveyService; + + @GetMapping("/surveys/{surveyId}/questions") + public List retrieveQuestions(@PathVariable String surveyId) { + return surveyService.retrieveQuestions(surveyId); + } + + // GET "/surveys/{surveyId}/questions/{questionId}" + @GetMapping("/surveys/{surveyId}/questions/{questionId}") + public Question retrieveDetailsForQuestion(@PathVariable String surveyId, + @PathVariable String questionId) { + return surveyService.retrieveQuestion(surveyId, questionId); + } + + // /surveys/{surveyId}/questions + @PostMapping("/surveys/{surveyId}/questions") + public ResponseEntity addQuestionToSurvey( + @PathVariable String surveyId, @RequestBody Question newQuestion) { + + Question question = surveyService.addQuestion(surveyId, newQuestion); + + if (question == null) + return ResponseEntity.noContent().build(); + + // Success - URI of the new resource in Response Header + // Status - created + // URI -> /surveys/{surveyId}/questions/{questionId} + // question.getQuestionId() + URI location = ServletUriComponentsBuilder.fromCurrentRequest().path( + "/{id}").buildAndExpand(question.getId()).toUri(); + + // Status + return ResponseEntity.created(location).build(); + } + +} diff --git a/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/jpa/User.java b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/jpa/User.java new file mode 100644 index 0000000..ae622c9 --- /dev/null +++ b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/jpa/User.java @@ -0,0 +1,44 @@ +package com.in28minutes.springboot.jpa; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String name; + private String role; + + protected User() { + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return "User [id=" + id + ", name=" + name + ", role=" + role + "]"; + } + +} diff --git a/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/jpa/UserCommandLineRunner.java b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/jpa/UserCommandLineRunner.java new file mode 100644 index 0000000..af728d1 --- /dev/null +++ b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/jpa/UserCommandLineRunner.java @@ -0,0 +1,38 @@ +package com.in28minutes.springboot.jpa; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +public class UserCommandLineRunner implements CommandLineRunner { + + private static final Logger log = LoggerFactory + .getLogger(UserCommandLineRunner.class); + + @Autowired + private UserRepository repository; + + @Override + public void run(String... args) throws Exception { + + repository.save(new User("Ranga", "Admin")); + repository.save(new User("Ravi", "User")); + repository.save(new User("Satish", "Admin")); + repository.save(new User("Raghu", "User")); + + for (User user : repository.findAll()) { + log.info(user.toString()); + } + + log.info("Admin users are....."); + log.info("____________________"); + for (User user : repository.findByRole("Admin")) { + log.info(user.toString()); + } + + } + +} diff --git a/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/jpa/UserRepository.java b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/jpa/UserRepository.java new file mode 100644 index 0000000..d5daf67 --- /dev/null +++ b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/jpa/UserRepository.java @@ -0,0 +1,9 @@ +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.CrudRepository; + +public interface UserRepository extends CrudRepository { + List findByRole(String role); +} diff --git a/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/jpa/UserRestRepository.java b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/jpa/UserRestRepository.java new file mode 100644 index 0000000..bb67556 --- /dev/null +++ b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/jpa/UserRestRepository.java @@ -0,0 +1,13 @@ +package com.in28minutes.springboot.jpa; + +import java.util.List; + +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; + +@RepositoryRestResource(path = "users", collectionResourceRel = "users") +public interface UserRestRepository extends + PagingAndSortingRepository { + List findByRole(@Param("role") String role); +} diff --git a/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/model/Question.java b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/model/Question.java new file mode 100644 index 0000000..9c83090 --- /dev/null +++ b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/model/Question.java @@ -0,0 +1,81 @@ +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Question { + private String id; + private String description; + private String correctAnswer; + private List options; + + // Needed by Caused by: com.fasterxml.jackson.databind.JsonMappingException: + // Can not construct instance of com.in28minutes.springboot.model.Question: + // no suitable constructor found, can not deserialize from Object value + // (missing default constructor or creator, or perhaps need to add/enable + // type information?) + public Question() { + + } + + public Question(String id, String description, String correctAnswer, + List options) { + super(); + this.id = id; + this.description = description; + this.correctAnswer = correctAnswer; + this.options = options; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDescription() { + return description; + } + + public String getCorrectAnswer() { + return correctAnswer; + } + + public List getOptions() { + return options; + } + + @Override + public String toString() { + return String + .format("Question [id=%s, description=%s, correctAnswer=%s, options=%s]", + id, description, correctAnswer, options); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Question other = (Question) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + +} \ No newline at end of file diff --git a/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/model/Survey.java b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/model/Survey.java new file mode 100644 index 0000000..6a650aa --- /dev/null +++ b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/model/Survey.java @@ -0,0 +1,58 @@ +package com.in28minutes.springboot.model; + +import java.util.List; + +public class Survey { + private String id; + private String title; + private String description; + private List questions; + + public Survey(String id, String title, String description, + List questions) { + super(); + this.id = id; + this.title = title; + this.description = description; + this.questions = questions; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getQuestions() { + return questions; + } + + public void setQuestions(List questions) { + this.questions = questions; + } + + @Override + public String toString() { + return "Survey [id=" + id + ", title=" + title + ", description=" + + description + ", questions=" + questions + "]"; + } + +} \ No newline at end of file diff --git a/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/security/SecurityConfig.java b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/security/SecurityConfig.java new file mode 100644 index 0000000..7547748 --- /dev/null +++ b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/security/SecurityConfig.java @@ -0,0 +1,27 @@ +package com.in28minutes.springboot.security; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +public class SecurityConfig extends WebSecurityConfigurerAdapter { + // Authentication : User --> Roles + protected void configure(AuthenticationManagerBuilder auth) + throws Exception { + auth.inMemoryAuthentication().withUser("user1").password("secret1") + .roles("USER").and().withUser("admin1").password("secret1") + .roles("USER", "ADMIN"); + } + + // Authorization : Role -> Access + // survey -> USER + protected void configure(HttpSecurity http) throws Exception { + http.httpBasic().and().authorizeRequests().antMatchers("/surveys/**") + .hasRole("USER").antMatchers("/users/**").hasRole("USER") + .antMatchers("/**").hasRole("ADMIN").and().csrf().disable() + .headers().frameOptions().disable(); + } + +} diff --git a/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/service/SurveyService.java b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/service/SurveyService.java new file mode 100644 index 0000000..b3f3ccb --- /dev/null +++ b/05.Spring-Boot-Advanced/src/main/java/com/in28minutes/springboot/service/SurveyService.java @@ -0,0 +1,95 @@ +package com.in28minutes.springboot.service; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.model.Survey; + +@Component +public class SurveyService { + private static List surveys = new ArrayList<>(); + static { + Question question1 = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question2 = new Question("Question2", + "Most Populus Country in the World", "China", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question3 = new Question("Question3", + "Highest GDP in the World", "United States", Arrays.asList( + "India", "Russia", "United States", "China")); + Question question4 = new Question("Question4", + "Second largest english speaking country", "India", Arrays + .asList("India", "Russia", "United States", "China")); + + List questions = new ArrayList<>(Arrays.asList(question1, + question2, question3, question4)); + + Survey survey = new Survey("Survey1", "My Favorite Survey", + "Description of the Survey", questions); + + surveys.add(survey); + } + + public List retrieveAllSurveys() { + return surveys; + } + + public Survey retrieveSurvey(String surveyId) { + for (Survey survey : surveys) { + if (survey.getId().equals(surveyId)) { + return survey; + } + } + return null; + } + + public List retrieveQuestions(String surveyId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + return survey.getQuestions(); + } + + public Question retrieveQuestion(String surveyId, String questionId) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + for (Question question : survey.getQuestions()) { + if (question.getId().equals(questionId)) { + return question; + } + } + + return null; + } + + private SecureRandom random = new SecureRandom(); + + public Question addQuestion(String surveyId, Question question) { + Survey survey = retrieveSurvey(surveyId); + + if (survey == null) { + return null; + } + + String randomId = new BigInteger(130, random).toString(32); + question.setId(randomId); + + survey.getQuestions().add(question); + + return question; + } +} \ No newline at end of file diff --git a/05.Spring-Boot-Advanced/src/main/resources/application-dev.properties b/05.Spring-Boot-Advanced/src/main/resources/application-dev.properties new file mode 100644 index 0000000..9fe78d9 --- /dev/null +++ b/05.Spring-Boot-Advanced/src/main/resources/application-dev.properties @@ -0,0 +1 @@ +logging.level.org.springframework: TRACE diff --git a/05.Spring-Boot-Advanced/src/main/resources/application-prod.properties b/05.Spring-Boot-Advanced/src/main/resources/application-prod.properties new file mode 100644 index 0000000..d71ef41 --- /dev/null +++ b/05.Spring-Boot-Advanced/src/main/resources/application-prod.properties @@ -0,0 +1 @@ +logging.level.org.springframework: INFO diff --git a/05.Spring-Boot-Advanced/src/main/resources/application.properties b/05.Spring-Boot-Advanced/src/main/resources/application.properties new file mode 100644 index 0000000..68b4ac4 --- /dev/null +++ b/05.Spring-Boot-Advanced/src/main/resources/application.properties @@ -0,0 +1,7 @@ +logging.level.org.springframework: DEBUG +app.name=in28Minutes +welcome.message=Welcome message from property file! Welcome to ${app.name} + +basic.value=true +basic.message=Welcome to in28minutes +basic.number=200 diff --git a/05.Spring-Boot-Advanced/src/test/java/com/in28minutes/springboot/controller/SurveyControllerIT.java b/05.Spring-Boot-Advanced/src/test/java/com/in28minutes/springboot/controller/SurveyControllerIT.java new file mode 100644 index 0000000..85557ff --- /dev/null +++ b/05.Spring-Boot-Advanced/src/test/java/com/in28minutes/springboot/controller/SurveyControllerIT.java @@ -0,0 +1,113 @@ +package com.in28minutes.springboot.controller; + +import static org.junit.Assert.assertTrue; + +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.boot.context.embedded.LocalServerPort; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.crypto.codec.Base64; +import org.springframework.test.context.junit4.SpringRunner; + +import com.in28minutes.springboot.Application; +import com.in28minutes.springboot.model.Question; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class SurveyControllerIT { + + @LocalServerPort + private int port; + + TestRestTemplate restTemplate = new TestRestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + + @Before + public void before() { + headers.add("Authorization", createHttpAuthenticationHeaderValue( + "user1", "secret1")); + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + } + + @Test + public void testRetrieveSurveyQuestion() { + + HttpEntity entity = new HttpEntity(null, headers); + + ResponseEntity response = restTemplate.exchange( + createURLWithPort("/surveys/Survey1/questions/Question1"), + HttpMethod.GET, entity, String.class); + + String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia}"; + + JSONAssert.assertEquals(expected, response.getBody(), false); + } + + @Test + public void retrieveAllSurveyQuestions() throws Exception { + + ResponseEntity> response = restTemplate.exchange( + createURLWithPort("/surveys/Survey1/questions"), + HttpMethod.GET, new HttpEntity("DUMMY_DOESNT_MATTER", + headers), + new ParameterizedTypeReference>() { + }); + + Question sampleQuestion = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + + assertTrue(response.getBody().contains(sampleQuestion)); + } + + @Test + public void addQuestion() { + + Question question = new Question("DOESNTMATTER", "Question1", "Russia", + Arrays.asList("India", "Russia", "United States", "China")); + + HttpEntity entity = new HttpEntity(question, headers); + + ResponseEntity response = restTemplate.exchange( + createURLWithPort("/surveys/Survey1/questions"), + HttpMethod.POST, entity, String.class); + + String actual = response.getHeaders().get(HttpHeaders.LOCATION).get(0); + + assertTrue(actual.contains("/surveys/Survey1/questions/")); + + } + + private String createURLWithPort(final String uri) { + return "http://localhost:" + port + uri; + } + + private String createHttpAuthenticationHeaderValue(String userId, + String password) { + + String auth = userId + ":" + password; + + byte[] encodedAuth = Base64.encode(auth.getBytes(Charset + .forName("US-ASCII"))); + + String headerValue = "Basic " + new String(encodedAuth); + + return headerValue; + } + +} diff --git a/05.Spring-Boot-Advanced/src/test/java/com/in28minutes/springboot/controller/SurveyControllerTest.java b/05.Spring-Boot-Advanced/src/test/java/com/in28minutes/springboot/controller/SurveyControllerTest.java new file mode 100644 index 0000000..c98f5d0 --- /dev/null +++ b/05.Spring-Boot-Advanced/src/test/java/com/in28minutes/springboot/controller/SurveyControllerTest.java @@ -0,0 +1,88 @@ +package com.in28minutes.springboot.controller; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +import com.in28minutes.springboot.model.Question; +import com.in28minutes.springboot.service.SurveyService; + +@RunWith(SpringRunner.class) +@WebMvcTest(value = SurveyController.class, secure = false) +public class SurveyControllerTest { + + @Autowired + private MockMvc mockMvc; + + // Mock @Autowired + @MockBean + private SurveyService surveyService; + + @Test + public void retrieveDetailsForQuestion() throws Exception { + Question mockQuestion = new Question("Question1", + "Largest Country in the World", "Russia", Arrays.asList( + "India", "Russia", "United States", "China")); + + Mockito.when( + surveyService.retrieveQuestion(Mockito.anyString(), Mockito + .anyString())).thenReturn(mockQuestion); + + RequestBuilder requestBuilder = MockMvcRequestBuilders.get( + "/surveys/Survey1/questions/Question1").accept( + MediaType.APPLICATION_JSON); + + MvcResult result = mockMvc.perform(requestBuilder).andReturn(); + + String expected = "{id:Question1,description:Largest Country in the World,correctAnswer:Russia}"; + + JSONAssert.assertEquals(expected, result.getResponse() + .getContentAsString(), false); + + // Assert + } + + @Test + public void createSurveyQuestion() throws Exception { + Question mockQuestion = new Question("1", "Smallest Number", "1", + Arrays.asList("1", "2", "3", "4")); + + String questionJson = "{\"description\":\"Smallest Number\",\"correctAnswer\":\"1\",\"options\":[\"1\",\"2\",\"3\",\"4\"]}"; + //surveyService.addQuestion to respond back with mockQuestion + Mockito.when( + surveyService.addQuestion(Mockito.anyString(), Mockito + .any(Question.class))).thenReturn(mockQuestion); + + //Send question as body to /surveys/Survey1/questions + RequestBuilder requestBuilder = MockMvcRequestBuilders.post( + "/surveys/Survey1/questions") + .accept(MediaType.APPLICATION_JSON).content(questionJson) + .contentType(MediaType.APPLICATION_JSON); + + MvcResult result = mockMvc.perform(requestBuilder).andReturn(); + + MockHttpServletResponse response = result.getResponse(); + + assertEquals(HttpStatus.CREATED.value(), response.getStatus()); + + assertEquals("http://localhost/surveys/Survey1/questions/1", response + .getHeader(HttpHeaders.LOCATION)); + } +} diff --git a/05.Spring-Boot-Advanced/target/classes/META-INF/MANIFEST.MF b/05.Spring-Boot-Advanced/target/classes/META-INF/MANIFEST.MF new file mode 100644 index 0000000..980cfa4 --- /dev/null +++ b/05.Spring-Boot-Advanced/target/classes/META-INF/MANIFEST.MF @@ -0,0 +1,11 @@ +Manifest-Version: 1.0 +Implementation-Title: first-springboot-project +Implementation-Version: 0.0.1-SNAPSHOT +Built-By: rangaraokaranam +Implementation-Vendor-Id: com.in28minutes.springboot +Build-Jdk: 1.8.0_31 +Implementation-URL: http://projects.spring.io/spring-boot/first-spring + boot-project/ +Created-By: Maven Integration for Eclipse +Implementation-Vendor: Pivotal Software, Inc. + diff --git a/05.Spring-Boot-Advanced/target/classes/META-INF/maven/com.in28minutes.springboot/first-springboot-project/pom.properties b/05.Spring-Boot-Advanced/target/classes/META-INF/maven/com.in28minutes.springboot/first-springboot-project/pom.properties new file mode 100644 index 0000000..907440b --- /dev/null +++ b/05.Spring-Boot-Advanced/target/classes/META-INF/maven/com.in28minutes.springboot/first-springboot-project/pom.properties @@ -0,0 +1,7 @@ +#Generated by Maven Integration for Eclipse +#Sun Jul 30 12:40:01 IST 2017 +version=0.0.1-SNAPSHOT +groupId=com.in28minutes.springboot +m2e.projectName=first-springboot-project +m2e.projectLocation=/in28Minutes/git/SpringBootForBeginners +artifactId=first-springboot-project diff --git a/05.Spring-Boot-Advanced/target/classes/META-INF/maven/com.in28minutes.springboot/first-springboot-project/pom.xml b/05.Spring-Boot-Advanced/target/classes/META-INF/maven/com.in28minutes.springboot/first-springboot-project/pom.xml new file mode 100644 index 0000000..06c481e --- /dev/null +++ b/05.Spring-Boot-Advanced/target/classes/META-INF/maven/com.in28minutes.springboot/first-springboot-project/pom.xml @@ -0,0 +1,74 @@ + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-data-rest + + + + com.h2database + h2 + + + + org.springframework.boot + spring-boot-devtools + true + + + + org.springframework.boot + spring-boot-starter-actuator + + + + org.springframework.data + spring-data-rest-hal-browser + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file diff --git a/05.Spring-Boot-Advanced/target/classes/application-dev.properties b/05.Spring-Boot-Advanced/target/classes/application-dev.properties new file mode 100644 index 0000000..9fe78d9 --- /dev/null +++ b/05.Spring-Boot-Advanced/target/classes/application-dev.properties @@ -0,0 +1 @@ +logging.level.org.springframework: TRACE diff --git a/05.Spring-Boot-Advanced/target/classes/application-prod.properties b/05.Spring-Boot-Advanced/target/classes/application-prod.properties new file mode 100644 index 0000000..d71ef41 --- /dev/null +++ b/05.Spring-Boot-Advanced/target/classes/application-prod.properties @@ -0,0 +1 @@ +logging.level.org.springframework: INFO diff --git a/05.Spring-Boot-Advanced/target/classes/application.properties b/05.Spring-Boot-Advanced/target/classes/application.properties new file mode 100644 index 0000000..68b4ac4 --- /dev/null +++ b/05.Spring-Boot-Advanced/target/classes/application.properties @@ -0,0 +1,7 @@ +logging.level.org.springframework: DEBUG +app.name=in28Minutes +welcome.message=Welcome message from property file! Welcome to ${app.name} + +basic.value=true +basic.message=Welcome to in28minutes +basic.number=200 diff --git a/05.Spring-Boot-Advanced/target/m2e-wtp/web-resources/META-INF/MANIFEST.MF b/05.Spring-Boot-Advanced/target/m2e-wtp/web-resources/META-INF/MANIFEST.MF new file mode 100644 index 0000000..ea381f6 --- /dev/null +++ b/05.Spring-Boot-Advanced/target/m2e-wtp/web-resources/META-INF/MANIFEST.MF @@ -0,0 +1,11 @@ +Manifest-Version: 1.0 +Implementation-Title: Your First Spring Boot Example +Implementation-Version: 0.0.1-SNAPSHOT +Built-By: rangaraokaranam +Implementation-Vendor-Id: com.in28minutes +Build-Jdk: 1.8.0_31 +Implementation-URL: http://projects.spring.io/spring-boot/springboot-f + or-beginners-example/ +Created-By: Maven Integration for Eclipse +Implementation-Vendor: Pivotal Software, Inc. + diff --git a/05.Spring-Boot-Advanced/target/m2e-wtp/web-resources/META-INF/maven/com.in28minutes/springboot-for-beginners-example/pom.properties b/05.Spring-Boot-Advanced/target/m2e-wtp/web-resources/META-INF/maven/com.in28minutes/springboot-for-beginners-example/pom.properties new file mode 100644 index 0000000..0cf6b50 --- /dev/null +++ b/05.Spring-Boot-Advanced/target/m2e-wtp/web-resources/META-INF/maven/com.in28minutes/springboot-for-beginners-example/pom.properties @@ -0,0 +1,7 @@ +#Generated by Maven Integration for Eclipse +#Tue Jan 03 17:15:01 IST 2017 +version=0.0.1-SNAPSHOT +groupId=com.in28minutes +m2e.projectName=springboot-for-beginners-example +m2e.projectLocation=/in28Minutes/git/SpringBootForBeginners +artifactId=springboot-for-beginners-example diff --git a/05.Spring-Boot-Advanced/target/m2e-wtp/web-resources/META-INF/maven/com.in28minutes/springboot-for-beginners-example/pom.xml b/05.Spring-Boot-Advanced/target/m2e-wtp/web-resources/META-INF/maven/com.in28minutes/springboot-for-beginners-example/pom.xml new file mode 100644 index 0000000..e21e191 --- /dev/null +++ b/05.Spring-Boot-Advanced/target/m2e-wtp/web-resources/META-INF/maven/com.in28minutes/springboot-for-beginners-example/pom.xml @@ -0,0 +1,35 @@ + + 4.0.0 + com.in28minutes + springboot-for-beginners-example + 0.0.1-SNAPSHOT + Your First Spring Boot Example + war + + + org.springframework.boot + spring-boot-starter-parent + 1.4.0.RELEASE + + + + + org.springframework.boot + spring-boot-starter-web + + + + + 1.8 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file diff --git a/05.Spring-Boot-Advanced/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/05.Spring-Boot-Advanced/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/05.Spring-Boot-Advanced/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/05.Spring-Boot-Advanced/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..76def39 --- /dev/null +++ b/05.Spring-Boot-Advanced/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,12 @@ +/in28Minutes/git/SpringBootForBeginners/src/main/java/com/in28minutes/springboot/jpa/User.java +/in28Minutes/git/SpringBootForBeginners/src/main/java/com/in28minutes/springboot/Application.java +/in28Minutes/git/SpringBootForBeginners/src/main/java/com/in28minutes/springboot/jpa/UserRestRepository.java +/in28Minutes/git/SpringBootForBeginners/src/main/java/com/in28minutes/springboot/security/SecurityConfig.java +/in28Minutes/git/SpringBootForBeginners/src/main/java/com/in28minutes/springboot/jpa/UserRepository.java +/in28Minutes/git/SpringBootForBeginners/src/main/java/com/in28minutes/springboot/model/Survey.java +/in28Minutes/git/SpringBootForBeginners/src/main/java/com/in28minutes/springboot/jpa/UserCommandLineRunner.java +/in28Minutes/git/SpringBootForBeginners/src/main/java/com/in28minutes/springboot/controller/SurveyController.java +/in28Minutes/git/SpringBootForBeginners/src/main/java/com/in28minutes/springboot/model/Question.java +/in28Minutes/git/SpringBootForBeginners/src/main/java/com/in28minutes/springboot/service/SurveyService.java +/in28Minutes/git/SpringBootForBeginners/src/main/java/com/in28minutes/springboot/WelcomeController.java +/in28Minutes/git/SpringBootForBeginners/src/main/java/com/in28minutes/springboot/configuration/BasicConfiguration.java diff --git a/05.Spring-Boot-Advanced/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/05.Spring-Boot-Advanced/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst new file mode 100644 index 0000000..e69de29 diff --git a/05.Spring-Boot-Advanced/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/05.Spring-Boot-Advanced/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst new file mode 100644 index 0000000..b5b8817 --- /dev/null +++ b/05.Spring-Boot-Advanced/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst @@ -0,0 +1,2 @@ +/in28Minutes/git/SpringBootForBeginners/src/test/java/com/in28minutes/springboot/controller/SurveyControllerTest.java +/in28Minutes/git/SpringBootForBeginners/src/test/java/com/in28minutes/springboot/controller/SurveyControllerIT.java diff --git a/05.Spring-Boot-Advanced/target/surefire-reports/TEST-com.in28minutes.springboot.controller.SurveyControllerTest.xml b/05.Spring-Boot-Advanced/target/surefire-reports/TEST-com.in28minutes.springboot.controller.SurveyControllerTest.xml new file mode 100644 index 0000000..2b167c1 --- /dev/null +++ b/05.Spring-Boot-Advanced/target/surefire-reports/TEST-com.in28minutes.springboot.controller.SurveyControllerTest.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/05.Spring-Boot-Advanced/target/surefire-reports/com.in28minutes.springboot.controller.SurveyControllerTest.txt b/05.Spring-Boot-Advanced/target/surefire-reports/com.in28minutes.springboot.controller.SurveyControllerTest.txt new file mode 100644 index 0000000..e9d4bbe --- /dev/null +++ b/05.Spring-Boot-Advanced/target/surefire-reports/com.in28minutes.springboot.controller.SurveyControllerTest.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.in28minutes.springboot.controller.SurveyControllerTest +------------------------------------------------------------------------------- +Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.782 sec - in com.in28minutes.springboot.controller.SurveyControllerTest diff --git a/06.JPA-Introduction-In-10-Steps/.DS_Store b/06.JPA-Introduction-In-10-Steps/.DS_Store new file mode 100644 index 0000000..9437bd4 Binary files /dev/null and b/06.JPA-Introduction-In-10-Steps/.DS_Store differ diff --git a/06.JPA-Introduction-In-10-Steps/.gitignore b/06.JPA-Introduction-In-10-Steps/.gitignore new file mode 100644 index 0000000..2af7cef --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/.gitignore @@ -0,0 +1,24 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ \ No newline at end of file diff --git a/06.JPA-Introduction-In-10-Steps/.mvn/.DS_Store b/06.JPA-Introduction-In-10-Steps/.mvn/.DS_Store new file mode 100644 index 0000000..cee7780 Binary files /dev/null and b/06.JPA-Introduction-In-10-Steps/.mvn/.DS_Store differ diff --git a/06.JPA-Introduction-In-10-Steps/.mvn/wrapper/maven-wrapper.jar b/06.JPA-Introduction-In-10-Steps/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..9cc84ea Binary files /dev/null and b/06.JPA-Introduction-In-10-Steps/.mvn/wrapper/maven-wrapper.jar differ diff --git a/06.JPA-Introduction-In-10-Steps/.mvn/wrapper/maven-wrapper.properties b/06.JPA-Introduction-In-10-Steps/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..c315043 --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip diff --git a/06.JPA-Introduction-In-10-Steps/code-21July2017.zip b/06.JPA-Introduction-In-10-Steps/code-21July2017.zip new file mode 100644 index 0000000..0b8b4ac Binary files /dev/null and b/06.JPA-Introduction-In-10-Steps/code-21July2017.zip differ diff --git a/06.JPA-Introduction-In-10-Steps/jpa-in-10-steps-all-code.md b/06.JPA-Introduction-In-10-Steps/jpa-in-10-steps-all-code.md new file mode 100644 index 0000000..322559b --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/jpa-in-10-steps-all-code.md @@ -0,0 +1,371 @@ +## Complete Code Example +### /notes.txt + +``` +Questions +- Where is the database created? +- What schema is used to create the tables? +- Where are the tables created? +- Can I see the data in the database? +- Where is Hibernate coming in from? +- How is a datasource created? + +Magic of Spring Boot and in Memory Database +- Zero project setup or infrastructure +- Zero Configuration +- Zero Maintainance +- Easy to use for Learning and Unit Tests +- Simple Configuration to switch to a real database + +# Restrictions of using in-memory database +- Data is not persisted between restarts + +Spring Boot chooses a default value for you based on whether it thinks your database is embedded (default create-drop) or not (default none). + +HibernateJpaAutoConfiguration matched: + - @ConditionalOnClass found required classes 'org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean', 'javax.persistence.EntityManager'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) + - HibernateEntityManager found class 'org.hibernate.ejb.HibernateEntityManager' (HibernateJpaAutoConfiguration.HibernateEntityManagerCondition) + +DataSourceAutoConfiguration matched: + - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) + +JpaBaseConfiguration#entityManagerFactory matched: + - @ConditionalOnMissingBean (types: org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean,javax.persistence.EntityManagerFactory; SearchStrategy: all) did not find any beans (OnBeanCondition) + +JpaBaseConfiguration#transactionManager matched: + - @ConditionalOnMissingBean (types: org.springframework.transaction.PlatformTransactionManager; SearchStrategy: all) did not find any beans (OnBeanCondition) +``` +--- + +### /pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.learning.jpa + jpa-in-10-steps + 0.0.1-SNAPSHOT + jar + + jpa-in-10-steps + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.BUILD-SNAPSHOT + + + + + UTF-8 + UTF-8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-web + + + + com.h2database + h2 + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/entity/User.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.entity; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +//Table - User +@Entity +public class User { + + @Id + @GeneratedValue + private long id; + + private String name; + + private String role; + + protected User() { + + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return String.format("User [id=%s, name=%s, role=%s]", id, name, role); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplication.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class JpaIn10StepsApplication { + + public static void main(String[] args) { + SpringApplication.run(JpaIn10StepsApplication.class, args); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserDAOService.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.service; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.transaction.Transactional; + +import org.springframework.stereotype.Repository; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; + +@Repository +@Transactional +public class UserDAOService { + + @PersistenceContext + private EntityManager entityManager; + + public long insert(User user){ + entityManager.persist(user); + return user.getId(); + } +} + +/* + * Spring Data JPA + * + * + * + */ +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserRepository.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.service; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; + +public interface UserRepository extends JpaRepository{ + +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserDaoServiceCommandLineRunner.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; +import com.in28minutes.learning.jpa.jpain10steps.service.UserDAOService; + +@Component +public class UserDaoServiceCommandLineRunner implements CommandLineRunner{ + + private static final Logger log = + LoggerFactory.getLogger(UserDaoServiceCommandLineRunner.class); + + @Autowired + private UserDAOService userDaoService; + + @Override + public void run(String... arg0) throws Exception { + User user = new User("Jack", "Admin"); + //New User is created : User [id=1, name=Jack, role=Admin] + long insert = userDaoService.insert(user); + log.info("New User is created : " + user); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserRepositoryCommandLineRunner.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; +import com.in28minutes.learning.jpa.jpain10steps.service.UserRepository; + +@Component +public class UserRepositoryCommandLineRunner implements CommandLineRunner{ + + private static final Logger log = + LoggerFactory.getLogger(UserRepositoryCommandLineRunner.class); + + @Autowired + private UserRepository userRepository; + + @Override + public void run(String... arg0) throws Exception { + User user = new User("Jill", "Admin"); + userRepository.save(user); + log.info("New User is created : " + user); + + Optional userWithIdOne = userRepository.findById(1L); + log.info("User is retrived : " + userWithIdOne); + + List users = userRepository.findAll(); + log.info("All Users : " + users); + } + +} +``` +--- + +### /src/main/resources/application.properties + +``` +spring.jpa.show-sql=true +spring.h2.console.enabled=true +logging.level.org.springframework=debug +``` +--- + +### /src/test/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplicationTests.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class JpaIn10StepsApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- \ No newline at end of file diff --git a/06.JPA-Introduction-In-10-Steps/mvnw b/06.JPA-Introduction-In-10-Steps/mvnw new file mode 100755 index 0000000..5bf251c --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/mvnw @@ -0,0 +1,225 @@ +#!/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 +# +# http://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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + 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" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +echo $MAVEN_PROJECTBASEDIR +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/06.JPA-Introduction-In-10-Steps/mvnw.cmd b/06.JPA-Introduction-In-10-Steps/mvnw.cmd new file mode 100644 index 0000000..019bd74 --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/mvnw.cmd @@ -0,0 +1,143 @@ +@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 http://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 Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/06.JPA-Introduction-In-10-Steps/notes.txt b/06.JPA-Introduction-In-10-Steps/notes.txt new file mode 100644 index 0000000..77a081d --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/notes.txt @@ -0,0 +1,32 @@ +Questions +- Where is the database created? +- What schema is used to create the tables? +- Where are the tables created? +- Can I see the data in the database? +- Where is Hibernate coming in from? +- How is a datasource created? + +Magic of Spring Boot and in Memory Database +- Zero project setup or infrastructure +- Zero Configuration +- Zero Maintainance +- Easy to use for Learning and Unit Tests +- Simple Configuration to switch to a real database + +# Restrictions of using in-memory database +- Data is not persisted between restarts + +Spring Boot chooses a default value for you based on whether it thinks your database is embedded (default create-drop) or not (default none). + +HibernateJpaAutoConfiguration matched: + - @ConditionalOnClass found required classes 'org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean', 'javax.persistence.EntityManager'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) + - HibernateEntityManager found class 'org.hibernate.ejb.HibernateEntityManager' (HibernateJpaAutoConfiguration.HibernateEntityManagerCondition) + +DataSourceAutoConfiguration matched: + - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) + +JpaBaseConfiguration#entityManagerFactory matched: + - @ConditionalOnMissingBean (types: org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean,javax.persistence.EntityManagerFactory; SearchStrategy: all) did not find any beans (OnBeanCondition) + +JpaBaseConfiguration#transactionManager matched: + - @ConditionalOnMissingBean (types: org.springframework.transaction.PlatformTransactionManager; SearchStrategy: all) did not find any beans (OnBeanCondition) diff --git a/06.JPA-Introduction-In-10-Steps/pom.xml b/06.JPA-Introduction-In-10-Steps/pom.xml new file mode 100644 index 0000000..fe27988 --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/pom.xml @@ -0,0 +1,100 @@ + + + 4.0.0 + + com.in28minutes.learning.jpa + jpa-in-10-steps + 0.0.1-SNAPSHOT + jar + + jpa-in-10-steps + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.BUILD-SNAPSHOT + + + + + UTF-8 + UTF-8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-web + + + + com.h2database + h2 + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + diff --git a/06.JPA-Introduction-In-10-Steps/readme.md b/06.JPA-Introduction-In-10-Steps/readme.md new file mode 100644 index 0000000..56d2a9f --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/readme.md @@ -0,0 +1,407 @@ +## First 10 Steps in JPA with H2 in-memory database + +- Step 1 : Object Relational Impedence Mismatch - Understanding the problem that JPA solves +- Step 2 : World before JPA - JDBC, Spring JDBC and myBatis +- Step 3 : Introduction to JPA +- Step 4 : Creating a JPA Project using Spring Initializr +- Step 5 : Defining a JPA Entity - User +- Step 6 : Defining a Service to manage the Entity - UserService and EntityManager +- Step 7 : Using a Command Line Runner to save the User to database. +- Step 8 : Magic of Spring Boot and In Memory Database H2 +- Step 9 : Introduction to Spring Data JPA +- Step 10 : More JPA Repository : findById and findAll +- Next Steps + +Notes +- http://localhost:8080/h2-console +- Use db url jdbc:h2:mem:testdb +## Complete Code Example + + +### /notes.txt + +``` +Questions +- Where is the database created? +- What schema is used to create the tables? +- Where are the tables created? +- Can I see the data in the database? +- Where is Hibernate coming in from? +- How is a datasource created? + +Magic of Spring Boot and in Memory Database +- Zero project setup or infrastructure +- Zero Configuration +- Zero Maintainance +- Easy to use for Learning and Unit Tests +- Simple Configuration to switch to a real database + +# Restrictions of using in-memory database +- Data is not persisted between restarts + +Spring Boot chooses a default value for you based on whether it thinks your database is embedded (default create-drop) or not (default none). + +HibernateJpaAutoConfiguration matched: + - @ConditionalOnClass found required classes 'org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean', 'javax.persistence.EntityManager'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) + - HibernateEntityManager found class 'org.hibernate.ejb.HibernateEntityManager' (HibernateJpaAutoConfiguration.HibernateEntityManagerCondition) + +DataSourceAutoConfiguration matched: + - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) + +JpaBaseConfiguration#entityManagerFactory matched: + - @ConditionalOnMissingBean (types: org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean,javax.persistence.EntityManagerFactory; SearchStrategy: all) did not find any beans (OnBeanCondition) + +JpaBaseConfiguration#transactionManager matched: + - @ConditionalOnMissingBean (types: org.springframework.transaction.PlatformTransactionManager; SearchStrategy: all) did not find any beans (OnBeanCondition) +``` +--- + +### /pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.learning.jpa + jpa-in-10-steps + 0.0.1-SNAPSHOT + jar + + jpa-in-10-steps + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.BUILD-SNAPSHOT + + + + + UTF-8 + UTF-8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-web + + + + com.h2database + h2 + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/entity/User.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.entity; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +//Table - User +@Entity +public class User { + + @Id + @GeneratedValue + private long id; + + private String name; + + private String role; + + protected User() { + + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return String.format("User [id=%s, name=%s, role=%s]", id, name, role); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplication.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class JpaIn10StepsApplication { + + public static void main(String[] args) { + SpringApplication.run(JpaIn10StepsApplication.class, args); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserDAOService.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.service; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.transaction.Transactional; + +import org.springframework.stereotype.Repository; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; + +@Repository +@Transactional +public class UserDAOService { + + @PersistenceContext + private EntityManager entityManager; + + public long insert(User user){ + entityManager.persist(user); + return user.getId(); + } +} + +/* + * Spring Data JPA + * + * + * + */ +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserRepository.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.service; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; + +public interface UserRepository extends JpaRepository{ + +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserDaoServiceCommandLineRunner.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; +import com.in28minutes.learning.jpa.jpain10steps.service.UserDAOService; + +@Component +public class UserDaoServiceCommandLineRunner implements CommandLineRunner{ + + private static final Logger log = + LoggerFactory.getLogger(UserDaoServiceCommandLineRunner.class); + + @Autowired + private UserDAOService userDaoService; + + @Override + public void run(String... arg0) throws Exception { + User user = new User("Jack", "Admin"); + //New User is created : User [id=1, name=Jack, role=Admin] + long insert = userDaoService.insert(user); + log.info("New User is created : " + user); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserRepositoryCommandLineRunner.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; +import com.in28minutes.learning.jpa.jpain10steps.service.UserRepository; + +@Component +public class UserRepositoryCommandLineRunner implements CommandLineRunner{ + + private static final Logger log = + LoggerFactory.getLogger(UserRepositoryCommandLineRunner.class); + + @Autowired + private UserRepository userRepository; + + @Override + public void run(String... arg0) throws Exception { + User user = new User("Jill", "Admin"); + userRepository.save(user); + log.info("New User is created : " + user); + + Optional userWithIdOne = userRepository.findById(1L); + log.info("User is retrived : " + userWithIdOne); + + List users = userRepository.findAll(); + log.info("All Users : " + users); + } + +} +``` +--- + +### /src/main/resources/application.properties + +```properties +spring.jpa.show-sql=true +spring.h2.console.enabled=true +logging.level.org.springframework=debug +``` +--- + +### /src/test/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplicationTests.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class JpaIn10StepsApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- + +### /step-completed.sh + +``` +java -cp /ProgrammingExcellence/Workspaces/Rithus.com/ListDirectoryContentInGitFormat/bin test.ListDirectoryContentInGitFormat $PWD >> $1.md +zip -r $1.zip . -x "target/*" -x ".*/*" -x ".*" -x "*.md" -x "mvn*" -x "*.zip" +git add *; git commit -m "$1"; git push; +``` +--- + +### /take-step-backup.sh + +``` +java -cp /ProgrammingExcellence/Workspaces/Rithus.com/ListDirectoryContentInGitFormat/bin test.ListDirectoryContentInGitFormat $PWD >> $1.md +zip -r $1.zip . -x "target/*" -x ".*/*" -x ".*" -x "*.md" -x "mvn*" -x "*.zip" +``` +--- diff --git a/06.JPA-Introduction-In-10-Steps/src/.DS_Store b/06.JPA-Introduction-In-10-Steps/src/.DS_Store new file mode 100644 index 0000000..c47a126 Binary files /dev/null and b/06.JPA-Introduction-In-10-Steps/src/.DS_Store differ diff --git a/06.JPA-Introduction-In-10-Steps/src/main/.DS_Store b/06.JPA-Introduction-In-10-Steps/src/main/.DS_Store new file mode 100644 index 0000000..a13d2e6 Binary files /dev/null and b/06.JPA-Introduction-In-10-Steps/src/main/.DS_Store differ diff --git a/06.JPA-Introduction-In-10-Steps/src/main/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplication.java b/06.JPA-Introduction-In-10-Steps/src/main/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplication.java new file mode 100644 index 0000000..7aff7a2 --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/src/main/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplication.java @@ -0,0 +1,12 @@ +package com.in28minutes.learning.jpa.jpain10steps; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class JpaIn10StepsApplication { + + public static void main(String[] args) { + SpringApplication.run(JpaIn10StepsApplication.class, args); + } +} diff --git a/06.JPA-Introduction-In-10-Steps/src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserDaoServiceCommandLineRunner.java b/06.JPA-Introduction-In-10-Steps/src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserDaoServiceCommandLineRunner.java new file mode 100644 index 0000000..bae155f --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserDaoServiceCommandLineRunner.java @@ -0,0 +1,28 @@ +package com.in28minutes.learning.jpa.jpain10steps; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; +import com.in28minutes.learning.jpa.jpain10steps.service.UserDAOService; + +@Component +public class UserDaoServiceCommandLineRunner implements CommandLineRunner{ + + private static final Logger log = + LoggerFactory.getLogger(UserDaoServiceCommandLineRunner.class); + + @Autowired + private UserDAOService userDaoService; + + @Override + public void run(String... arg0) throws Exception { + User user = new User("Jack", "Admin"); + //New User is created : User [id=1, name=Jack, role=Admin] + long insert = userDaoService.insert(user); + log.info("New User is created : " + user); + } +} \ No newline at end of file diff --git a/06.JPA-Introduction-In-10-Steps/src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserRepositoryCommandLineRunner.java b/06.JPA-Introduction-In-10-Steps/src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserRepositoryCommandLineRunner.java new file mode 100644 index 0000000..3cdf7e7 --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserRepositoryCommandLineRunner.java @@ -0,0 +1,37 @@ +package com.in28minutes.learning.jpa.jpain10steps; + +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; +import com.in28minutes.learning.jpa.jpain10steps.service.UserRepository; + +@Component +public class UserRepositoryCommandLineRunner implements CommandLineRunner{ + + private static final Logger log = + LoggerFactory.getLogger(UserRepositoryCommandLineRunner.class); + + @Autowired + private UserRepository userRepository; + + @Override + public void run(String... arg0) throws Exception { + User user = new User("Jill", "Admin"); + userRepository.save(user); + log.info("New User is created : " + user); + + Optional userWithIdOne = userRepository.findById(1L); + log.info("User is retrived : " + userWithIdOne); + + List users = userRepository.findAll(); + log.info("All Users : " + users); + } + +} \ No newline at end of file diff --git a/06.JPA-Introduction-In-10-Steps/src/main/java/com/in28minutes/learning/jpa/jpain10steps/entity/User.java b/06.JPA-Introduction-In-10-Steps/src/main/java/com/in28minutes/learning/jpa/jpain10steps/entity/User.java new file mode 100644 index 0000000..d5bab5c --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/src/main/java/com/in28minutes/learning/jpa/jpain10steps/entity/User.java @@ -0,0 +1,45 @@ +package com.in28minutes.learning.jpa.jpain10steps.entity; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +//Table - User +@Entity +public class User { + + @Id + @GeneratedValue + private long id; + + private String name; + + private String role; + + protected User() { + + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return String.format("User [id=%s, name=%s, role=%s]", id, name, role); + } +} diff --git a/06.JPA-Introduction-In-10-Steps/src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserDAOService.java b/06.JPA-Introduction-In-10-Steps/src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserDAOService.java new file mode 100644 index 0000000..0c6fccc --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserDAOService.java @@ -0,0 +1,29 @@ +package com.in28minutes.learning.jpa.jpain10steps.service; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.transaction.Transactional; + +import org.springframework.stereotype.Repository; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; + +@Repository +@Transactional +public class UserDAOService { + + @PersistenceContext + private EntityManager entityManager; + + public long insert(User user){ + entityManager.persist(user); + return user.getId(); + } +} + +/* + * Spring Data JPA + * + * + * + */ diff --git a/06.JPA-Introduction-In-10-Steps/src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserRepository.java b/06.JPA-Introduction-In-10-Steps/src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserRepository.java new file mode 100644 index 0000000..9fc309f --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserRepository.java @@ -0,0 +1,9 @@ +package com.in28minutes.learning.jpa.jpain10steps.service; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; + +public interface UserRepository extends JpaRepository{ + +} diff --git a/06.JPA-Introduction-In-10-Steps/src/main/resources/.DS_Store b/06.JPA-Introduction-In-10-Steps/src/main/resources/.DS_Store new file mode 100644 index 0000000..97d947d Binary files /dev/null and b/06.JPA-Introduction-In-10-Steps/src/main/resources/.DS_Store differ diff --git a/06.JPA-Introduction-In-10-Steps/src/main/resources/application.properties b/06.JPA-Introduction-In-10-Steps/src/main/resources/application.properties new file mode 100644 index 0000000..7716fe8 --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/src/main/resources/application.properties @@ -0,0 +1,3 @@ +spring.jpa.show-sql=true +spring.h2.console.enabled=true +logging.level.org.springframework=debug \ No newline at end of file diff --git a/06.JPA-Introduction-In-10-Steps/src/test/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplicationTests.java b/06.JPA-Introduction-In-10-Steps/src/test/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplicationTests.java new file mode 100644 index 0000000..770aad7 --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/src/test/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplicationTests.java @@ -0,0 +1,16 @@ +package com.in28minutes.learning.jpa.jpain10steps; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class JpaIn10StepsApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/06.JPA-Introduction-In-10-Steps/step-completed.sh b/06.JPA-Introduction-In-10-Steps/step-completed.sh new file mode 100755 index 0000000..084e06f --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/step-completed.sh @@ -0,0 +1,3 @@ +java -cp /ProgrammingExcellence/Workspaces/Rithus.com/ListDirectoryContentInGitFormat/bin test.ListDirectoryContentInGitFormat $PWD >> $1.md +zip -r $1.zip . -x "target/*" -x ".*/*" -x ".*" -x "*.md" -x "mvn*" -x "*.zip" +git add *; git commit -m "$1"; git push; diff --git a/06.JPA-Introduction-In-10-Steps/step21.md b/06.JPA-Introduction-In-10-Steps/step21.md new file mode 100644 index 0000000..71b3f64 --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/step21.md @@ -0,0 +1,766 @@ + +## Complete Code Example + + +### /example.sh + +``` +java -cp /ProgrammingExcellence/Workspaces/Rithus.com/ListDirectoryContentInGitFormat/bin test.ListDirectoryContentInGitFormat >> $1.md +git add *; git commit -m "$1"; git push; +zip -r $1.zip . -x "target/*" -x ".*/*" -x ".*" -x "*.md" -x "mvn*" +``` +--- + +### /notes.txt + +``` +Questions +- Where is the database created? +- What schema is used to create the tables? +- Where are the tables created? +- Can I see the data in the database? +- Where is Hibernate coming in from? +- How is a datasource created? + +Magic of Spring Boot and in Memory Database +- Zero project setup or infrastructure +- Zero Configuration +- Zero Maintainance +- Easy to use for Learning and Unit Tests +- Simple Configuration to switch to a real database + +# Restrictions of using in-memory database +- Data is not persisted between restarts + +Spring Boot chooses a default value for you based on whether it thinks your database is embedded (default create-drop) or not (default none). + +HibernateJpaAutoConfiguration matched: + - @ConditionalOnClass found required classes 'org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean', 'javax.persistence.EntityManager'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) + - HibernateEntityManager found class 'org.hibernate.ejb.HibernateEntityManager' (HibernateJpaAutoConfiguration.HibernateEntityManagerCondition) + +DataSourceAutoConfiguration matched: + - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) + +JpaBaseConfiguration#entityManagerFactory matched: + - @ConditionalOnMissingBean (types: org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean,javax.persistence.EntityManagerFactory; SearchStrategy: all) did not find any beans (OnBeanCondition) + +JpaBaseConfiguration#transactionManager matched: + - @ConditionalOnMissingBean (types: org.springframework.transaction.PlatformTransactionManager; SearchStrategy: all) did not find any beans (OnBeanCondition) +``` +--- + +### /pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.learning.jpa + jpa-in-10-steps + 0.0.1-SNAPSHOT + jar + + jpa-in-10-steps + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.BUILD-SNAPSHOT + + + + + UTF-8 + UTF-8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-web + + + + com.h2database + h2 + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/entity/User.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.entity; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +//Table - User +@Entity +public class User { + + @Id + @GeneratedValue + private long id; + + private String name; + + private String role; + + protected User() { + + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return String.format("User [id=%s, name=%s, role=%s]", id, name, role); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplication.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class JpaIn10StepsApplication { + + public static void main(String[] args) { + SpringApplication.run(JpaIn10StepsApplication.class, args); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserDAOService.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.service; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.transaction.Transactional; + +import org.springframework.stereotype.Repository; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; + +@Repository +@Transactional +public class UserDAOService { + + @PersistenceContext + private EntityManager entityManager; + + public long insert(User user){ + entityManager.persist(user); + return user.getId(); + } +} + +/* + * Spring Data JPA + * + * + * + */ +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserRepository.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.service; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; + +public interface UserRepository extends JpaRepository{ + +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserDaoServiceCommandLineRunner.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; +import com.in28minutes.learning.jpa.jpain10steps.service.UserDAOService; + +@Component +public class UserDaoServiceCommandLineRunner implements CommandLineRunner{ + + private static final Logger log = + LoggerFactory.getLogger(UserDaoServiceCommandLineRunner.class); + + @Autowired + private UserDAOService userDaoService; + + @Override + public void run(String... arg0) throws Exception { + User user = new User("Jack", "Admin"); + //New User is created : User [id=1, name=Jack, role=Admin] + long insert = userDaoService.insert(user); + log.info("New User is created : " + user); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserRepositoryCommandLineRunner.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; +import com.in28minutes.learning.jpa.jpain10steps.service.UserRepository; + +@Component +public class UserRepositoryCommandLineRunner implements CommandLineRunner{ + + private static final Logger log = + LoggerFactory.getLogger(UserRepositoryCommandLineRunner.class); + + @Autowired + private UserRepository userRepository; + + @Override + public void run(String... arg0) throws Exception { + User user = new User("Jill", "Admin"); + userRepository.save(user); + log.info("New User is created : " + user); + + Optional userWithIdOne = userRepository.findById(1L); + log.info("User is retrived : " + userWithIdOne); + + List users = userRepository.findAll(); + log.info("All Users : " + users); + } + +} +``` +--- + +### /src/main/resources/application.properties + +``` +spring.jpa.show-sql=true +spring.h2.console.enabled=true +logging.level.org.springframework=debug +``` +--- + +### /src/test/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplicationTests.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class JpaIn10StepsApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- + +## Complete Code Example + + +### /example.sh + +``` +java -cp /ProgrammingExcellence/Workspaces/Rithus.com/ListDirectoryContentInGitFormat/bin test.ListDirectoryContentInGitFormat >> $1.md +git add *; git commit -m "$1"; git push; +zip -r $1.zip . -x "target/*" -x ".*/*" -x ".*" -x "*.md" -x "mvn*" -x "*.zip" +``` +--- + +### /notes.txt + +``` +Questions +- Where is the database created? +- What schema is used to create the tables? +- Where are the tables created? +- Can I see the data in the database? +- Where is Hibernate coming in from? +- How is a datasource created? + +Magic of Spring Boot and in Memory Database +- Zero project setup or infrastructure +- Zero Configuration +- Zero Maintainance +- Easy to use for Learning and Unit Tests +- Simple Configuration to switch to a real database + +# Restrictions of using in-memory database +- Data is not persisted between restarts + +Spring Boot chooses a default value for you based on whether it thinks your database is embedded (default create-drop) or not (default none). + +HibernateJpaAutoConfiguration matched: + - @ConditionalOnClass found required classes 'org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean', 'javax.persistence.EntityManager'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) + - HibernateEntityManager found class 'org.hibernate.ejb.HibernateEntityManager' (HibernateJpaAutoConfiguration.HibernateEntityManagerCondition) + +DataSourceAutoConfiguration matched: + - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) + +JpaBaseConfiguration#entityManagerFactory matched: + - @ConditionalOnMissingBean (types: org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean,javax.persistence.EntityManagerFactory; SearchStrategy: all) did not find any beans (OnBeanCondition) + +JpaBaseConfiguration#transactionManager matched: + - @ConditionalOnMissingBean (types: org.springframework.transaction.PlatformTransactionManager; SearchStrategy: all) did not find any beans (OnBeanCondition) +``` +--- + +### /pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.learning.jpa + jpa-in-10-steps + 0.0.1-SNAPSHOT + jar + + jpa-in-10-steps + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.BUILD-SNAPSHOT + + + + + UTF-8 + UTF-8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-web + + + + com.h2database + h2 + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/entity/User.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.entity; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +//Table - User +@Entity +public class User { + + @Id + @GeneratedValue + private long id; + + private String name; + + private String role; + + protected User() { + + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return String.format("User [id=%s, name=%s, role=%s]", id, name, role); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplication.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class JpaIn10StepsApplication { + + public static void main(String[] args) { + SpringApplication.run(JpaIn10StepsApplication.class, args); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserDAOService.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.service; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.transaction.Transactional; + +import org.springframework.stereotype.Repository; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; + +@Repository +@Transactional +public class UserDAOService { + + @PersistenceContext + private EntityManager entityManager; + + public long insert(User user){ + entityManager.persist(user); + return user.getId(); + } +} + +/* + * Spring Data JPA + * + * + * + */ +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserRepository.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.service; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; + +public interface UserRepository extends JpaRepository{ + +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserDaoServiceCommandLineRunner.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; +import com.in28minutes.learning.jpa.jpain10steps.service.UserDAOService; + +@Component +public class UserDaoServiceCommandLineRunner implements CommandLineRunner{ + + private static final Logger log = + LoggerFactory.getLogger(UserDaoServiceCommandLineRunner.class); + + @Autowired + private UserDAOService userDaoService; + + @Override + public void run(String... arg0) throws Exception { + User user = new User("Jack", "Admin"); + //New User is created : User [id=1, name=Jack, role=Admin] + long insert = userDaoService.insert(user); + log.info("New User is created : " + user); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserRepositoryCommandLineRunner.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; +import com.in28minutes.learning.jpa.jpain10steps.service.UserRepository; + +@Component +public class UserRepositoryCommandLineRunner implements CommandLineRunner{ + + private static final Logger log = + LoggerFactory.getLogger(UserRepositoryCommandLineRunner.class); + + @Autowired + private UserRepository userRepository; + + @Override + public void run(String... arg0) throws Exception { + User user = new User("Jill", "Admin"); + userRepository.save(user); + log.info("New User is created : " + user); + + Optional userWithIdOne = userRepository.findById(1L); + log.info("User is retrived : " + userWithIdOne); + + List users = userRepository.findAll(); + log.info("All Users : " + users); + } + +} +``` +--- + +### /src/main/resources/application.properties + +``` +spring.jpa.show-sql=true +spring.h2.console.enabled=true +logging.level.org.springframework=debug +``` +--- + +### /src/test/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplicationTests.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class JpaIn10StepsApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- diff --git a/06.JPA-Introduction-In-10-Steps/step21.zip b/06.JPA-Introduction-In-10-Steps/step21.zip new file mode 100644 index 0000000..539a5ba Binary files /dev/null and b/06.JPA-Introduction-In-10-Steps/step21.zip differ diff --git a/06.JPA-Introduction-In-10-Steps/step22.md b/06.JPA-Introduction-In-10-Steps/step22.md new file mode 100644 index 0000000..afdb99a --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/step22.md @@ -0,0 +1,383 @@ + +## Complete Code Example + + +### /example.sh + +``` +java -cp /ProgrammingExcellence/Workspaces/Rithus.com/ListDirectoryContentInGitFormat/bin test.ListDirectoryContentInGitFormat >> $1.md +zip -r $1.zip . -x "target/*" -x ".*/*" -x ".*" -x "*.md" -x "mvn*" -x "*.zip" +git add *; git commit -m "$1"; git push; +``` +--- + +### /notes.txt + +``` +Questions +- Where is the database created? +- What schema is used to create the tables? +- Where are the tables created? +- Can I see the data in the database? +- Where is Hibernate coming in from? +- How is a datasource created? + +Magic of Spring Boot and in Memory Database +- Zero project setup or infrastructure +- Zero Configuration +- Zero Maintainance +- Easy to use for Learning and Unit Tests +- Simple Configuration to switch to a real database + +# Restrictions of using in-memory database +- Data is not persisted between restarts + +Spring Boot chooses a default value for you based on whether it thinks your database is embedded (default create-drop) or not (default none). + +HibernateJpaAutoConfiguration matched: + - @ConditionalOnClass found required classes 'org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean', 'javax.persistence.EntityManager'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) + - HibernateEntityManager found class 'org.hibernate.ejb.HibernateEntityManager' (HibernateJpaAutoConfiguration.HibernateEntityManagerCondition) + +DataSourceAutoConfiguration matched: + - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) + +JpaBaseConfiguration#entityManagerFactory matched: + - @ConditionalOnMissingBean (types: org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean,javax.persistence.EntityManagerFactory; SearchStrategy: all) did not find any beans (OnBeanCondition) + +JpaBaseConfiguration#transactionManager matched: + - @ConditionalOnMissingBean (types: org.springframework.transaction.PlatformTransactionManager; SearchStrategy: all) did not find any beans (OnBeanCondition) +``` +--- + +### /pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.learning.jpa + jpa-in-10-steps + 0.0.1-SNAPSHOT + jar + + jpa-in-10-steps + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.BUILD-SNAPSHOT + + + + + UTF-8 + UTF-8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-web + + + + com.h2database + h2 + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/entity/User.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.entity; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +//Table - User +@Entity +public class User { + + @Id + @GeneratedValue + private long id; + + private String name; + + private String role; + + protected User() { + + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return String.format("User [id=%s, name=%s, role=%s]", id, name, role); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplication.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class JpaIn10StepsApplication { + + public static void main(String[] args) { + SpringApplication.run(JpaIn10StepsApplication.class, args); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserDAOService.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.service; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.transaction.Transactional; + +import org.springframework.stereotype.Repository; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; + +@Repository +@Transactional +public class UserDAOService { + + @PersistenceContext + private EntityManager entityManager; + + public long insert(User user){ + entityManager.persist(user); + return user.getId(); + } +} + +/* + * Spring Data JPA + * + * + * + */ +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserRepository.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.service; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; + +public interface UserRepository extends JpaRepository{ + +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserDaoServiceCommandLineRunner.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; +import com.in28minutes.learning.jpa.jpain10steps.service.UserDAOService; + +@Component +public class UserDaoServiceCommandLineRunner implements CommandLineRunner{ + + private static final Logger log = + LoggerFactory.getLogger(UserDaoServiceCommandLineRunner.class); + + @Autowired + private UserDAOService userDaoService; + + @Override + public void run(String... arg0) throws Exception { + User user = new User("Jack", "Admin"); + //New User is created : User [id=1, name=Jack, role=Admin] + long insert = userDaoService.insert(user); + log.info("New User is created : " + user); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserRepositoryCommandLineRunner.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; +import com.in28minutes.learning.jpa.jpain10steps.service.UserRepository; + +@Component +public class UserRepositoryCommandLineRunner implements CommandLineRunner{ + + private static final Logger log = + LoggerFactory.getLogger(UserRepositoryCommandLineRunner.class); + + @Autowired + private UserRepository userRepository; + + @Override + public void run(String... arg0) throws Exception { + User user = new User("Jill", "Admin"); + userRepository.save(user); + log.info("New User is created : " + user); + + Optional userWithIdOne = userRepository.findById(1L); + log.info("User is retrived : " + userWithIdOne); + + List users = userRepository.findAll(); + log.info("All Users : " + users); + } + +} +``` +--- + +### /src/main/resources/application.properties + +``` +spring.jpa.show-sql=true +spring.h2.console.enabled=true +logging.level.org.springframework=debug +``` +--- + +### /src/test/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplicationTests.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class JpaIn10StepsApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- diff --git a/06.JPA-Introduction-In-10-Steps/step22.zip b/06.JPA-Introduction-In-10-Steps/step22.zip new file mode 100644 index 0000000..97c8f12 Binary files /dev/null and b/06.JPA-Introduction-In-10-Steps/step22.zip differ diff --git a/06.JPA-Introduction-In-10-Steps/step23.md b/06.JPA-Introduction-In-10-Steps/step23.md new file mode 100644 index 0000000..05f7cdf --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/step23.md @@ -0,0 +1,383 @@ + +## Complete Code Example + + +### /notes.txt + +``` +Questions +- Where is the database created? +- What schema is used to create the tables? +- Where are the tables created? +- Can I see the data in the database? +- Where is Hibernate coming in from? +- How is a datasource created? + +Magic of Spring Boot and in Memory Database +- Zero project setup or infrastructure +- Zero Configuration +- Zero Maintainance +- Easy to use for Learning and Unit Tests +- Simple Configuration to switch to a real database + +# Restrictions of using in-memory database +- Data is not persisted between restarts + +Spring Boot chooses a default value for you based on whether it thinks your database is embedded (default create-drop) or not (default none). + +HibernateJpaAutoConfiguration matched: + - @ConditionalOnClass found required classes 'org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean', 'javax.persistence.EntityManager'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) + - HibernateEntityManager found class 'org.hibernate.ejb.HibernateEntityManager' (HibernateJpaAutoConfiguration.HibernateEntityManagerCondition) + +DataSourceAutoConfiguration matched: + - @ConditionalOnClass found required classes 'javax.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition) + +JpaBaseConfiguration#entityManagerFactory matched: + - @ConditionalOnMissingBean (types: org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean,javax.persistence.EntityManagerFactory; SearchStrategy: all) did not find any beans (OnBeanCondition) + +JpaBaseConfiguration#transactionManager matched: + - @ConditionalOnMissingBean (types: org.springframework.transaction.PlatformTransactionManager; SearchStrategy: all) did not find any beans (OnBeanCondition) +``` +--- + +### /pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.learning.jpa + jpa-in-10-steps + 0.0.1-SNAPSHOT + jar + + jpa-in-10-steps + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.BUILD-SNAPSHOT + + + + + UTF-8 + UTF-8 + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-web + + + + com.h2database + h2 + runtime + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/entity/User.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.entity; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +//Table - User +@Entity +public class User { + + @Id + @GeneratedValue + private long id; + + private String name; + + private String role; + + protected User() { + + } + + public User(String name, String role) { + super(); + this.name = name; + this.role = role; + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getRole() { + return role; + } + + @Override + public String toString() { + return String.format("User [id=%s, name=%s, role=%s]", id, name, role); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplication.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class JpaIn10StepsApplication { + + public static void main(String[] args) { + SpringApplication.run(JpaIn10StepsApplication.class, args); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserDAOService.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.service; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.transaction.Transactional; + +import org.springframework.stereotype.Repository; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; + +@Repository +@Transactional +public class UserDAOService { + + @PersistenceContext + private EntityManager entityManager; + + public long insert(User user){ + entityManager.persist(user); + return user.getId(); + } +} + +/* + * Spring Data JPA + * + * + * + */ +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/service/UserRepository.java + +```java +package com.in28minutes.learning.jpa.jpain10steps.service; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; + +public interface UserRepository extends JpaRepository{ + +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserDaoServiceCommandLineRunner.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; +import com.in28minutes.learning.jpa.jpain10steps.service.UserDAOService; + +@Component +public class UserDaoServiceCommandLineRunner implements CommandLineRunner{ + + private static final Logger log = + LoggerFactory.getLogger(UserDaoServiceCommandLineRunner.class); + + @Autowired + private UserDAOService userDaoService; + + @Override + public void run(String... arg0) throws Exception { + User user = new User("Jack", "Admin"); + //New User is created : User [id=1, name=Jack, role=Admin] + long insert = userDaoService.insert(user); + log.info("New User is created : " + user); + } +} +``` +--- + +### /src/main/java/com/in28minutes/learning/jpa/jpain10steps/UserRepositoryCommandLineRunner.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import com.in28minutes.learning.jpa.jpain10steps.entity.User; +import com.in28minutes.learning.jpa.jpain10steps.service.UserRepository; + +@Component +public class UserRepositoryCommandLineRunner implements CommandLineRunner{ + + private static final Logger log = + LoggerFactory.getLogger(UserRepositoryCommandLineRunner.class); + + @Autowired + private UserRepository userRepository; + + @Override + public void run(String... arg0) throws Exception { + User user = new User("Jill", "Admin"); + userRepository.save(user); + log.info("New User is created : " + user); + + Optional userWithIdOne = userRepository.findById(1L); + log.info("User is retrived : " + userWithIdOne); + + List users = userRepository.findAll(); + log.info("All Users : " + users); + } + +} +``` +--- + +### /src/main/resources/application.properties + +``` +spring.jpa.show-sql=true +spring.h2.console.enabled=true +logging.level.org.springframework=debug +``` +--- + +### /src/test/java/com/in28minutes/learning/jpa/jpain10steps/JpaIn10StepsApplicationTests.java + +```java +package com.in28minutes.learning.jpa.jpain10steps; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class JpaIn10StepsApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- + +### /step-completed.sh + +``` +java -cp /ProgrammingExcellence/Workspaces/Rithus.com/ListDirectoryContentInGitFormat/bin test.ListDirectoryContentInGitFormat $PWD >> $1.md +zip -r $1.zip . -x "target/*" -x ".*/*" -x ".*" -x "*.md" -x "mvn*" -x "*.zip" +git add *; git commit -m "$1"; git push; +``` +--- diff --git a/06.JPA-Introduction-In-10-Steps/step23.zip b/06.JPA-Introduction-In-10-Steps/step23.zip new file mode 100644 index 0000000..351bc9d Binary files /dev/null and b/06.JPA-Introduction-In-10-Steps/step23.zip differ diff --git a/06.JPA-Introduction-In-10-Steps/take-step-backup.sh b/06.JPA-Introduction-In-10-Steps/take-step-backup.sh new file mode 100755 index 0000000..587e7d0 --- /dev/null +++ b/06.JPA-Introduction-In-10-Steps/take-step-backup.sh @@ -0,0 +1,2 @@ +java -cp /ProgrammingExcellence/Workspaces/Rithus.com/ListDirectoryContentInGitFormat/bin test.ListDirectoryContentInGitFormat $PWD >> $1.md +zip -r $1.zip . -x "target/*" -x ".*/*" -x ".*" -x "*.md" -x "mvn*" -x "*.zip" \ No newline at end of file diff --git a/09.Spring-Introduction-In-10-Steps/.gitignore b/09.Spring-Introduction-In-10-Steps/.gitignore new file mode 100644 index 0000000..2af7cef --- /dev/null +++ b/09.Spring-Introduction-In-10-Steps/.gitignore @@ -0,0 +1,24 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ \ No newline at end of file diff --git a/09.Spring-Introduction-In-10-Steps/.mvn/wrapper/maven-wrapper.jar b/09.Spring-Introduction-In-10-Steps/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..9cc84ea Binary files /dev/null and b/09.Spring-Introduction-In-10-Steps/.mvn/wrapper/maven-wrapper.jar differ diff --git a/09.Spring-Introduction-In-10-Steps/.mvn/wrapper/maven-wrapper.properties b/09.Spring-Introduction-In-10-Steps/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..c315043 --- /dev/null +++ b/09.Spring-Introduction-In-10-Steps/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip diff --git a/09.Spring-Introduction-In-10-Steps/code-21July2017.zip b/09.Spring-Introduction-In-10-Steps/code-21July2017.zip new file mode 100644 index 0000000..826a060 Binary files /dev/null and b/09.Spring-Introduction-In-10-Steps/code-21July2017.zip differ diff --git a/09.Spring-Introduction-In-10-Steps/mvnw b/09.Spring-Introduction-In-10-Steps/mvnw new file mode 100755 index 0000000..5bf251c --- /dev/null +++ b/09.Spring-Introduction-In-10-Steps/mvnw @@ -0,0 +1,225 @@ +#!/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 +# +# http://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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + 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" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +echo $MAVEN_PROJECTBASEDIR +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/09.Spring-Introduction-In-10-Steps/mvnw.cmd b/09.Spring-Introduction-In-10-Steps/mvnw.cmd new file mode 100644 index 0000000..019bd74 --- /dev/null +++ b/09.Spring-Introduction-In-10-Steps/mvnw.cmd @@ -0,0 +1,143 @@ +@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 http://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 Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/09.Spring-Introduction-In-10-Steps/pom.xml b/09.Spring-Introduction-In-10-Steps/pom.xml new file mode 100644 index 0000000..737356c --- /dev/null +++ b/09.Spring-Introduction-In-10-Steps/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + com.in28minutes.spring.basics + spring-in-5-steps + 0.0.1-SNAPSHOT + jar + + spring-in-5-steps + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.BUILD-SNAPSHOT + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + diff --git a/09.Spring-Introduction-In-10-Steps/readme.md b/09.Spring-Introduction-In-10-Steps/readme.md new file mode 100644 index 0000000..a9efb4f --- /dev/null +++ b/09.Spring-Introduction-In-10-Steps/readme.md @@ -0,0 +1,265 @@ +## First 10 Steps in Spring + +- Step 1 : Setting up a Spring Project using htttp://start.spring.io +- Step 2 : Understanding Tight Coupling using the Binary Search Algorithm Example +- Step 3 : Making the Binary Search Algorithm Example Loosely Coupled +- Step 4 : Using Spring to Manage Dependencies - @Component, @Autowired +- Step 5 : What is happening in the background? +- Step 6 : Dynamic auto wiring and Troubleshooting - @Primary +- Step 7 : Constructor and Setter Injection +- Step 8 : Spring Modules +- Step 9 : Spring Projects +- Step 10 : Why is Spring Popular? + +## Complete Code Example + + +### /pom.xml + +```xml + + + 4.0.0 + + com.in28minutes.spring.basics + spring-in-5-steps + 0.0.1-SNAPSHOT + jar + + spring-in-5-steps + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 2.0.0.BUILD-SNAPSHOT + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + +``` +--- + +### /src/main/java/com/in28minutes/spring/basics/springin5steps/BinarySearchImpl.java + +```java +package com.in28minutes.spring.basics.springin5steps; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class BinarySearchImpl { + + @Autowired + private SortAlgorithm sortAlgorithm; + + public int binarySearch(int[] numbers, int numberToSearchFor) { + + int[] sortedNumbers = sortAlgorithm.sort(numbers); + System.out.println(sortAlgorithm); + // Search the array + return 3; + } + +} +``` +--- + +### /src/main/java/com/in28minutes/spring/basics/springin5steps/BubbleSortAlgorithm.java + +```java +package com.in28minutes.spring.basics.springin5steps; + +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +@Component +@Primary +public class BubbleSortAlgorithm implements SortAlgorithm { + public int[] sort(int[] numbers) { + // Logic for Bubble Sort + return numbers; + } +} +``` +--- + +### /src/main/java/com/in28minutes/spring/basics/springin5steps/QuickSortAlgorithm.java + +```java +package com.in28minutes.spring.basics.springin5steps; + +import org.springframework.stereotype.Component; + +@Component +public class QuickSortAlgorithm implements SortAlgorithm { + public int[] sort(int[] numbers) { + // Logic for Quick Sort + return numbers; + } +} +``` +--- + +### /src/main/java/com/in28minutes/spring/basics/springin5steps/SortAlgorithm.java + +```java +package com.in28minutes.spring.basics.springin5steps; + +public interface SortAlgorithm { + public int[] sort(int[] numbers); +} +``` +--- + +### /src/main/java/com/in28minutes/spring/basics/springin5steps/SpringIn5StepsApplication.java + +```java +package com.in28minutes.spring.basics.springin5steps; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; + +@SpringBootApplication +public class SpringIn5StepsApplication { + + // What are the beans? + // What are the dependencies of a bean? + // Where to search for beans? => No need + + public static void main(String[] args) { + + // BinarySearchImpl binarySearch = + // new BinarySearchImpl(new QuickSortAlgorithm()); + // Application Context + ApplicationContext applicationContext = + SpringApplication.run(SpringIn5StepsApplication.class, args); + BinarySearchImpl binarySearch = + applicationContext.getBean(BinarySearchImpl.class); + int result = + binarySearch.binarySearch(new int[] { 12, 4, 6 }, 3); + System.out.println(result); + } +} +``` +--- + +### /src/main/resources/application.properties + +```properties +logging.level.org.springframework = debug +``` +--- + +### /src/main/resources/log.txt + +``` +Searching directory [/in28Minutes/git/getting-started-in-5-steps/spring-in-5-steps/target/classes/com/in28minutes/spring/basics/springin5steps] for files matching pattern [/in28Minutes/git/getting-started-in-5-steps/spring-in-5-steps/target/classes/com/in28minutes/spring/basics/springin5steps/**/*.class] +Identified candidate component class: file [/in28Minutes/git/getting-started-in-5-steps/spring-in-5-steps/target/classes/com/in28minutes/spring/basics/springin5steps/BinarySearchImpl.class] +Identified candidate component class: file [/in28Minutes/git/getting-started-in-5-steps/spring-in-5-steps/target/classes/com/in28minutes/spring/basics/springin5steps/BubbleSortAlgorithm.class] + +Creating instance of bean 'binarySearchImpl' +Creating instance of bean 'bubbleSortAlgorithm' +Finished creating instance of bean 'bubbleSortAlgorithm' + +Constuctor - Autowiring by type from bean name 'binarySearchImpl' via constructor +to bean named 'bubbleSortAlgorithm' +Setter - Autowiring by type from bean name 'binarySearchImpl' to bean named 'bubbleSortAlgorithm' +No Setter or Constructor - Autowiring by type from bean name 'binarySearchImpl' to bean named 'bubbleSortAlgorithm' + + +Finished creating instance of bean 'binarySearchImpl' +``` +--- + +### /src/test/java/com/in28minutes/spring/basics/springin5steps/SpringIn5StepsApplicationTests.java + +```java +package com.in28minutes.spring.basics.springin5steps; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringIn5StepsApplicationTests { + + @Test + public void contextLoads() { + } + +} +``` +--- diff --git a/09.Spring-Introduction-In-10-Steps/src/main/java/com/in28minutes/spring/basics/springin5steps/BinarySearchImpl.java b/09.Spring-Introduction-In-10-Steps/src/main/java/com/in28minutes/spring/basics/springin5steps/BinarySearchImpl.java new file mode 100644 index 0000000..b3e2e57 --- /dev/null +++ b/09.Spring-Introduction-In-10-Steps/src/main/java/com/in28minutes/spring/basics/springin5steps/BinarySearchImpl.java @@ -0,0 +1,20 @@ +package com.in28minutes.spring.basics.springin5steps; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class BinarySearchImpl { + + @Autowired + private SortAlgorithm sortAlgorithm; + + public int binarySearch(int[] numbers, int numberToSearchFor) { + + int[] sortedNumbers = sortAlgorithm.sort(numbers); + System.out.println(sortAlgorithm); + // Search the array + return 3; + } + +} diff --git a/09.Spring-Introduction-In-10-Steps/src/main/java/com/in28minutes/spring/basics/springin5steps/BubbleSortAlgorithm.java b/09.Spring-Introduction-In-10-Steps/src/main/java/com/in28minutes/spring/basics/springin5steps/BubbleSortAlgorithm.java new file mode 100644 index 0000000..66bb71e --- /dev/null +++ b/09.Spring-Introduction-In-10-Steps/src/main/java/com/in28minutes/spring/basics/springin5steps/BubbleSortAlgorithm.java @@ -0,0 +1,13 @@ +package com.in28minutes.spring.basics.springin5steps; + +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +@Component +@Primary +public class BubbleSortAlgorithm implements SortAlgorithm { + public int[] sort(int[] numbers) { + // Logic for Bubble Sort + return numbers; + } +} diff --git a/09.Spring-Introduction-In-10-Steps/src/main/java/com/in28minutes/spring/basics/springin5steps/QuickSortAlgorithm.java b/09.Spring-Introduction-In-10-Steps/src/main/java/com/in28minutes/spring/basics/springin5steps/QuickSortAlgorithm.java new file mode 100644 index 0000000..b495569 --- /dev/null +++ b/09.Spring-Introduction-In-10-Steps/src/main/java/com/in28minutes/spring/basics/springin5steps/QuickSortAlgorithm.java @@ -0,0 +1,11 @@ +package com.in28minutes.spring.basics.springin5steps; + +import org.springframework.stereotype.Component; + +@Component +public class QuickSortAlgorithm implements SortAlgorithm { + public int[] sort(int[] numbers) { + // Logic for Quick Sort + return numbers; + } +} diff --git a/09.Spring-Introduction-In-10-Steps/src/main/java/com/in28minutes/spring/basics/springin5steps/SortAlgorithm.java b/09.Spring-Introduction-In-10-Steps/src/main/java/com/in28minutes/spring/basics/springin5steps/SortAlgorithm.java new file mode 100644 index 0000000..cb49da6 --- /dev/null +++ b/09.Spring-Introduction-In-10-Steps/src/main/java/com/in28minutes/spring/basics/springin5steps/SortAlgorithm.java @@ -0,0 +1,5 @@ +package com.in28minutes.spring.basics.springin5steps; + +public interface SortAlgorithm { + public int[] sort(int[] numbers); +} diff --git a/09.Spring-Introduction-In-10-Steps/src/main/java/com/in28minutes/spring/basics/springin5steps/SpringIn5StepsApplication.java b/09.Spring-Introduction-In-10-Steps/src/main/java/com/in28minutes/spring/basics/springin5steps/SpringIn5StepsApplication.java new file mode 100644 index 0000000..cbcd806 --- /dev/null +++ b/09.Spring-Introduction-In-10-Steps/src/main/java/com/in28minutes/spring/basics/springin5steps/SpringIn5StepsApplication.java @@ -0,0 +1,27 @@ +package com.in28minutes.spring.basics.springin5steps; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; + +@SpringBootApplication +public class SpringIn5StepsApplication { + + // What are the beans? + // What are the dependencies of a bean? + // Where to search for beans? => No need + + public static void main(String[] args) { + + // BinarySearchImpl binarySearch = + // new BinarySearchImpl(new QuickSortAlgorithm()); + // Application Context + ApplicationContext applicationContext = + SpringApplication.run(SpringIn5StepsApplication.class, args); + BinarySearchImpl binarySearch = + applicationContext.getBean(BinarySearchImpl.class); + int result = + binarySearch.binarySearch(new int[] { 12, 4, 6 }, 3); + System.out.println(result); + } +} \ No newline at end of file diff --git a/09.Spring-Introduction-In-10-Steps/src/main/resources/application.properties b/09.Spring-Introduction-In-10-Steps/src/main/resources/application.properties new file mode 100644 index 0000000..35c0162 --- /dev/null +++ b/09.Spring-Introduction-In-10-Steps/src/main/resources/application.properties @@ -0,0 +1 @@ +logging.level.org.springframework = debug \ No newline at end of file diff --git a/09.Spring-Introduction-In-10-Steps/src/main/resources/log.txt b/09.Spring-Introduction-In-10-Steps/src/main/resources/log.txt new file mode 100644 index 0000000..bd755b2 --- /dev/null +++ b/09.Spring-Introduction-In-10-Steps/src/main/resources/log.txt @@ -0,0 +1,15 @@ +Searching directory [/in28Minutes/git/getting-started-in-5-steps/spring-in-5-steps/target/classes/com/in28minutes/spring/basics/springin5steps] for files matching pattern [/in28Minutes/git/getting-started-in-5-steps/spring-in-5-steps/target/classes/com/in28minutes/spring/basics/springin5steps/**/*.class] +Identified candidate component class: file [/in28Minutes/git/getting-started-in-5-steps/spring-in-5-steps/target/classes/com/in28minutes/spring/basics/springin5steps/BinarySearchImpl.class] +Identified candidate component class: file [/in28Minutes/git/getting-started-in-5-steps/spring-in-5-steps/target/classes/com/in28minutes/spring/basics/springin5steps/BubbleSortAlgorithm.class] + +Creating instance of bean 'binarySearchImpl' +Creating instance of bean 'bubbleSortAlgorithm' +Finished creating instance of bean 'bubbleSortAlgorithm' + +Constuctor - Autowiring by type from bean name 'binarySearchImpl' via constructor +to bean named 'bubbleSortAlgorithm' +Setter - Autowiring by type from bean name 'binarySearchImpl' to bean named 'bubbleSortAlgorithm' +No Setter or Constructor - Autowiring by type from bean name 'binarySearchImpl' to bean named 'bubbleSortAlgorithm' + + +Finished creating instance of bean 'binarySearchImpl' diff --git a/09.Spring-Introduction-In-10-Steps/src/test/java/com/in28minutes/spring/basics/springin5steps/SpringIn5StepsApplicationTests.java b/09.Spring-Introduction-In-10-Steps/src/test/java/com/in28minutes/spring/basics/springin5steps/SpringIn5StepsApplicationTests.java new file mode 100644 index 0000000..c5e11af --- /dev/null +++ b/09.Spring-Introduction-In-10-Steps/src/test/java/com/in28minutes/spring/basics/springin5steps/SpringIn5StepsApplicationTests.java @@ -0,0 +1,16 @@ +package com.in28minutes.spring.basics.springin5steps; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringIn5StepsApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/README.md b/README.md index 132b9b5..999d6eb 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ With Microservices, focus is shifting to getting started with developing web app ### Other Courses -- [Most Watched Courses on YouTube - 30,000 Subscribers](https://www.youtube.com/watch?v=bNFoN956P2A&list=PLBBog2r6uMCQhZaQ9vUT5zJWXzz-f49k1) +- [Check out all our courses with 100,000 Students](https://courses.in28minutes.com/courses) - [25 Videos and Articles for Beginners on Spring Boot](http://www.springboottutorial.com/spring-boot-tutorials-for-beginners) - Our Best Courses with 66,000 Students and 4,000 5-Star Ratings * [Java Interview Guide : 200+ Interview Questions and Answers](https://www.udemy.com/java-interview-questions-and-answers/?couponCode=JAVA_INTER_GIT) diff --git a/Step24.md b/Step24.md index c06afb0..e6f90b9 100644 --- a/Step24.md +++ b/Step24.md @@ -5,7 +5,7 @@ - MockMvc framework - @MockBean - Programming Tip - - Be an expert at Mockito - https://www.youtube.com/watch?v=d2KwvXQgQx4 + - Be an expert at Mockito - https://courses.in28minutes.com/p/mockito-for-beginner-in-5-steps ## Useful Snippets and References First Snippet