diff --git a/02.Spring-Boot-Web-Application/2-3-1-upgrade.md b/02.Spring-Boot-Web-Application/2-3-1-upgrade.md new file mode 100644 index 0000000..fe94494 --- /dev/null +++ b/02.Spring-Boot-Web-Application/2-3-1-upgrade.md @@ -0,0 +1,826 @@ + + +## Complete Code Example + + +### /pom.xml + +```xml + + + 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 + 2.3.1.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + 3.1.1 + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + com.h2database + h2 + runtime + + + + 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 + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestones + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestones + + + + +``` +--- + +### /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/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.TodoRepository; + +@Controller +public class TodoController { + + @Autowired + TodoRepository repository; + + @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", repository.findByUser(name)); + //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"); + repository.deleteById(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 = repository.findById(id).get(); + //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)); + + repository.save(todo); + //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"; + } + + todo.setUser(getLoggedInUserName(model)); + repository.save(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.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.validation.constraints.Size; + +@Entity +public class Todo { + + @Id + @GeneratedValue + 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; +import org.springframework.security.crypto.password.NoOpPasswordEncoder; + +@Configuration +public class SecurityConfiguration extends WebSecurityConfigurerAdapter{ + //Create User - in28Minutes/dummy + @Autowired + public void configureGlobalSecurity(AuthenticationManagerBuilder auth) + throws Exception { + auth.inMemoryAuthentication() + .passwordEncoder(NoOpPasswordEncoder.getInstance()) + .withUser("in28Minutes").password("dummy") + .roles("USER", "ADMIN"); + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests().antMatchers("/login", "/h2-console/**").permitAll() + .antMatchers("/", "/*todo*/**").access("hasRole('USER')").and() + .formLogin(); + + http.csrf().disable(); + http.headers().frameOptions().disable(); + } +} +``` +--- + +### /src/main/java/com/in28minutes/springboot/web/service/TodoRepository.java + +```java +package com.in28minutes.springboot.web.service; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.in28minutes.springboot.web.model.Todo; + +public interface TodoRepository extends JpaRepository{ + List findByUser(String user); + + //service.retrieveTodos(name) + + //service.deleteTodo(id); + //service.retrieveTodo(id) + //service.updateTodo(todo) + //service.addTodo(getLoggedInUserName(model), todo.getDesc(), todo.getTargetDate(),false); +} +``` +--- + +### /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/resources/application.properties + +```properties +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp +logging.level.org.springframework.web=INFO + +spring.jpa.show-sql=true +spring.h2.console.enabled=true +``` +--- + +### /src/main/resources/data.sql + +``` +insert into TODO +values(10001, 'Learn Spring Boot', false, sysdate(), 'in28Minutes'); +insert into TODO +values(10002, 'Learn Angular JS', false, sysdate(), 'in28Minutes'); +insert into TODO +values(10003, 'Learn to Dance', false, sysdate(), 'in28Minutes'); +``` +--- + +### /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"%> + + + + +Spring Boot Normal 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() { + } + +} +``` +--- diff --git a/02.Spring-Boot-Web-Application/pom.xml b/02.Spring-Boot-Web-Application/pom.xml index f92f5ca..0394353 100644 --- a/02.Spring-Boot-Web-Application/pom.xml +++ b/02.Spring-Boot-Web-Application/pom.xml @@ -14,7 +14,7 @@ org.springframework.boot spring-boot-starter-parent - 2.0.0.RELEASE + 2.3.1.RELEASE @@ -22,6 +22,7 @@ UTF-8 UTF-8 1.8 + 3.1.1 @@ -30,6 +31,12 @@ spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + org.springframework.boot spring-boot-starter-data-jpa diff --git a/05.Spring-Boot-Advanced/2-3-1-upgrade.md b/05.Spring-Boot-Advanced/2-3-1-upgrade.md new file mode 100644 index 0000000..4d01bde --- /dev/null +++ b/05.Spring-Boot-Advanced/2-3-1-upgrade.md @@ -0,0 +1,948 @@ + + +## Complete Code Example + + +### /pom.xml + +```xml + + 4.0.0 + com.in28minutes.springboot + first-springboot-project + 0.0.1-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.3.1.RELEASE + + + + 1.8 + 3.1.1 + + + + + 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.security + spring-security-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +--- + +### /src/main/java/com/in28minutes/springboot/Application.java + +```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/WelcomeController.java + +```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 + +```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/java/com/in28minutes/springboot/configuration/BasicConfiguration.java + +```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 + +```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 + +```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 + +```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 + +```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 + +```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 + +```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 + +```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 + +```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().passwordEncoder(org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance()).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 + +```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-dev.properties + +```properties +logging.level.org.springframework: TRACE +``` +--- + +### /src/main/resources/application-prod.properties + +```properties +logging.level.org.springframework: INFO +``` +--- + +### /src/main/resources/application.properties + +```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 + +```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.json.JSONException; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.web.server.LocalServerPort; +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() throws JSONException { + + 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 + +```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.security.test.context.support.WithMockUser; +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) +@WithMockUser +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/Step01.md b/05.Spring-Boot-Advanced/Step01.md index 34fea76..e494704 100644 --- a/05.Spring-Boot-Advanced/Step01.md +++ b/05.Spring-Boot-Advanced/Step01.md @@ -30,7 +30,7 @@ org.springframework.boot spring-boot-starter-parent - 1.4.0.RELEASE + 2.3.1.RELEASE diff --git a/05.Spring-Boot-Advanced/pom.xml b/05.Spring-Boot-Advanced/pom.xml index 1caae73..df0fabb 100644 --- a/05.Spring-Boot-Advanced/pom.xml +++ b/05.Spring-Boot-Advanced/pom.xml @@ -8,11 +8,12 @@ org.springframework.boot spring-boot-starter-parent - 2.0.1.RELEASE + 2.3.1.RELEASE 1.8 + 3.1.1 @@ -62,6 +63,12 @@ spring-boot-starter-test test + + + org.springframework.security + spring-security-test + test + 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 index d807253..c0232a5 100644 --- 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 @@ -15,6 +15,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -25,7 +26,8 @@ import com.in28minutes.springboot.service.SurveyService; @RunWith(SpringRunner.class) -@WebMvcTest(value = SurveyController.class, secure = false) +@WebMvcTest(value = SurveyController.class) +@WithMockUser public class SurveyControllerTest { @Autowired