diff --git a/spring-boot-mvc-2/.gitignore b/spring-boot-mvc-2/.gitignore new file mode 100644 index 0000000000..82eca336e3 --- /dev/null +++ b/spring-boot-mvc-2/.gitignore @@ -0,0 +1,25 @@ +/target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ \ No newline at end of file diff --git a/spring-boot-mvc-2/README.md b/spring-boot-mvc-2/README.md new file mode 100644 index 0000000000..a405298cbe --- /dev/null +++ b/spring-boot-mvc-2/README.md @@ -0,0 +1,3 @@ +### Relevant Articles: + +- [Functional Controllers in Spring MVC]() \ No newline at end of file diff --git a/spring-boot-mvc-2/pom.xml b/spring-boot-mvc-2/pom.xml new file mode 100644 index 0000000000..18121325a5 --- /dev/null +++ b/spring-boot-mvc-2/pom.xml @@ -0,0 +1,68 @@ + + + 4.0.0 + spring-boot-mvc-2 + spring-boot-mvc-2 + jar + Module For Spring Boot MVC Web Fn + + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.BUILD-SNAPSHOT + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + com.baeldung.springbootmvc.SpringBootMvcFnApplication + JAR + + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + \ No newline at end of file diff --git a/spring-boot-mvc-2/src/main/java/com/baeldung/springbootmvc/SpringBootMvcFnApplication.java b/spring-boot-mvc-2/src/main/java/com/baeldung/springbootmvc/SpringBootMvcFnApplication.java new file mode 100644 index 0000000000..2a85550bd7 --- /dev/null +++ b/spring-boot-mvc-2/src/main/java/com/baeldung/springbootmvc/SpringBootMvcFnApplication.java @@ -0,0 +1,74 @@ +package com.baeldung.springbootmvc; + +import static org.springframework.web.servlet.function.RouterFunctions.route; +import static org.springframework.web.servlet.function.ServerResponse.notFound; +import static org.springframework.web.servlet.function.ServerResponse.status; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpStatus; +import org.springframework.web.servlet.function.RequestPredicates; +import org.springframework.web.servlet.function.RouterFunction; +import org.springframework.web.servlet.function.ServerResponse; + +import com.baeldung.springbootmvc.ctrl.ProductController; +import com.baeldung.springbootmvc.svc.ProductService; + +@SpringBootApplication +public class SpringBootMvcFnApplication { + + private static final Logger LOG = LoggerFactory.getLogger(SpringBootMvcFnApplication.class); + + public static void main(String[] args) { + SpringApplication.run(SpringBootMvcFnApplication.class, args); + } + + @Bean + RouterFunction productListing(ProductController pc, ProductService ps) { + return pc.productListing(ps); + } + + @Bean + RouterFunction allApplicationRoutes(ProductController pc, ProductService ps) { + return route().add(pc.remainingProductRoutes(ps)) + .before(req -> { + LOG.info("Found a route which matches " + req.uri() + .getPath()); + return req; + }) + .after((req, res) -> { + if (res.statusCode() == HttpStatus.OK) { + LOG.info("Finished processing request " + req.uri() + .getPath()); + } else { + LOG.info("There was an error while processing request" + req.uri()); + } + return res; + }) + .onError(Throwable.class, (e, res) -> { + LOG.error("Fatal exception has occurred", e); + return status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + }) + .build() + .and(route(RequestPredicates.all(), req -> notFound().build())); + } + + public static class Error { + private String errorMessage; + + public Error(String message) { + this.errorMessage = message; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + } +} diff --git a/spring-boot-mvc-2/src/main/java/com/baeldung/springbootmvc/ctrl/ProductController.java b/spring-boot-mvc-2/src/main/java/com/baeldung/springbootmvc/ctrl/ProductController.java new file mode 100644 index 0000000000..6a77e72cea --- /dev/null +++ b/spring-boot-mvc-2/src/main/java/com/baeldung/springbootmvc/ctrl/ProductController.java @@ -0,0 +1,59 @@ +package com.baeldung.springbootmvc.ctrl; + +import static org.springframework.web.servlet.function.RouterFunctions.route; +import static org.springframework.web.servlet.function.ServerResponse.ok; +import static org.springframework.web.servlet.function.ServerResponse.status; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.function.EntityResponse; +import org.springframework.web.servlet.function.RequestPredicates; +import org.springframework.web.servlet.function.RouterFunction; +import org.springframework.web.servlet.function.ServerRequest; +import org.springframework.web.servlet.function.ServerResponse; + +import com.baeldung.springbootmvc.SpringBootMvcFnApplication.Error; +import com.baeldung.springbootmvc.model.Product; +import com.baeldung.springbootmvc.svc.ProductService; + +@Component +public class ProductController { + + public RouterFunction productListing(ProductService ps) { + return route().GET("/product", req -> ok().body(ps.findAll())) + .build(); + } + + public RouterFunction productSearch(ProductService ps) { + return route().nest(RequestPredicates.path("/product"), builder -> { + builder.GET("/name/{name}", req -> ok().body(ps.findByName(req.pathVariable("name")))) + .GET("/id/{id}", req -> ok().body(ps.findById(Integer.parseInt(req.pathVariable("id"))))); + }) + .onError(ProductService.ItemNotFoundException.class, (e, req) -> EntityResponse.fromObject(new Error(e.getMessage())) + .status(HttpStatus.NOT_FOUND) + .build()) + .build(); + } + + public RouterFunction adminFunctions(ProductService ps) { + return route().POST("/product", req -> ok().body(ps.save(req.body(Product.class)))) + .filter((req, next) -> authenticate(req) ? next.handle(req) : status(HttpStatus.UNAUTHORIZED).build()) + .onError(IllegalArgumentException.class, (e, req) -> EntityResponse.fromObject(new Error(e.getMessage())) + .status(HttpStatus.BAD_REQUEST) + .build()) + .build(); + } + + public RouterFunction remainingProductRoutes(ProductService ps) { + return route().add(productSearch(ps)) + .add(adminFunctions(ps)) + .build(); + } + + private boolean authenticate(ServerRequest req) { + return Boolean.TRUE; + } + +} diff --git a/spring-boot-mvc-2/src/main/java/com/baeldung/springbootmvc/model/Product.java b/spring-boot-mvc-2/src/main/java/com/baeldung/springbootmvc/model/Product.java new file mode 100644 index 0000000000..1213b3c11e --- /dev/null +++ b/spring-boot-mvc-2/src/main/java/com/baeldung/springbootmvc/model/Product.java @@ -0,0 +1,72 @@ +package com.baeldung.springbootmvc.model; + +public class Product { + private String name; + private double price; + private int id; + + public Product(String name, double price, int id) { + super(); + this.name = name; + this.price = price; + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public double getPrice() { + return price; + } + + public void setPrice(double price) { + this.price = price; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + long temp; + temp = Double.doubleToLongBits(price); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Product other = (Product) obj; + if (id != other.id) + return false; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (Double.doubleToLongBits(price) != Double.doubleToLongBits(other.price)) + return false; + return true; + } + +} diff --git a/spring-boot-mvc-2/src/main/java/com/baeldung/springbootmvc/svc/ProductService.java b/spring-boot-mvc-2/src/main/java/com/baeldung/springbootmvc/svc/ProductService.java new file mode 100644 index 0000000000..e2d281d54f --- /dev/null +++ b/spring-boot-mvc-2/src/main/java/com/baeldung/springbootmvc/svc/ProductService.java @@ -0,0 +1,63 @@ +package com.baeldung.springbootmvc.svc; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import com.baeldung.springbootmvc.model.Product; + +@Service +public class ProductService { + + private final Set products = new HashSet<>(); + + { + products.add(new Product("Book", 23.90, 1)); + products.add(new Product("Pen", 44.34, 2)); + } + + public Product findById(int id) { + return products.stream() + .filter(obj -> obj.getId() == id) + .findFirst() + .orElseThrow(() -> new ItemNotFoundException("Product not found")); + } + + public Product findByName(String name) { + return products.stream() + .filter(obj -> obj.getName() + .equalsIgnoreCase(name)) + .findFirst() + .orElseThrow(() -> new ItemNotFoundException("Product not found")); + } + + public Set findAll() { + return products; + } + + public Product save(Product product) { + if (StringUtils.isEmpty(product.getName()) || product.getPrice() == 0.0) { + throw new IllegalArgumentException(); + } + int newId = products.stream() + .mapToInt(Product::getId) + .max() + .getAsInt() + 1; + product.setId(newId); + products.add(product); + return product; + } + + public static class ItemNotFoundException extends RuntimeException { + /** + * + */ + private static final long serialVersionUID = 1L; + + public ItemNotFoundException(String msg) { + super(msg); + } + } +} diff --git a/spring-boot-mvc-2/src/main/resources/application.properties b/spring-boot-mvc-2/src/main/resources/application.properties new file mode 100644 index 0000000000..709574239b --- /dev/null +++ b/spring-boot-mvc-2/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.main.allow-bean-definition-overriding=true \ No newline at end of file diff --git a/spring-boot-mvc-2/src/main/resources/logback.xml b/spring-boot-mvc-2/src/main/resources/logback.xml new file mode 100644 index 0000000000..7d900d8ea8 --- /dev/null +++ b/spring-boot-mvc-2/src/main/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file