SPRING IN ACTION FIFTH EDITIO, chapter 2
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Taco Cloud</title>
</head>
<body>
<h1>Welcome to...</h1>
<img th:src="@{/images/TacoCloud.png}"/>
<a th:href="@{/design}" id="design">Design a taco</a>
</body>
</html>
<!-- tag::all[] -->
<!-- tag::head[] -->
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Taco Cloud</title>
<link rel="stylesheet" th:href="@{/styles.css}" />
</head>
<body>
<h1>Design your taco!</h1>
<img th:src="@{/images/TacoCloud.png}"/>
<!-- tag::formTag[] -->
<form method="POST" th:object="${design}">
<!-- end::all[] -->
<span class="validationError"
th:if="${#fields.hasErrors('ingredients')}"
th:errors="*{ingredients}">Ingredient Error</span>
<!-- tag::all[] -->
<div class="grid">
<!-- end::formTag[] -->
<!-- end::head[] -->
<div class="ingredient-group" id="wraps">
<!-- tag::designateWrap[] -->
<h3>Designate your wrap:</h3>
<div th:each="ingredient : ${wrap}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
<!-- end::designateWrap[] -->
</div>
<div class="ingredient-group" id="proteins">
<h3>Pick your protein:</h3>
<div th:each="ingredient : ${protein}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="cheeses">
<h3>Choose your cheese:</h3>
<div th:each="ingredient : ${cheese}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="veggies">
<h3>Determine your veggies:</h3>
<div th:each="ingredient : ${veggies}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group" id="sauces">
<h3>Select your sauce:</h3>
<div th:each="ingredient : ${sauce}">
<input name="ingredients" type="checkbox" th:value="${ingredient.id}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
</div>
<div>
<h3>Name your taco creation:</h3>
<input type="text" th:field="*{name}"/>
<!-- end::all[] -->
<span th:text="${#fields.hasErrors('name')}">XXX</span>
<span class="validationError"
th:if="${#fields.hasErrors('name')}"
th:errors="*{name}">Name Error</span>
<!-- tag::all[] -->
<br/>
<button>Submit your taco</button>
</div>
<!-- tag::closeFormTag[] -->
</form>
<!-- end::closeFormTag[] -->
</body>
</html>
<!-- end::all[] -->
<!-- tag::allButValidation[] -->
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Taco Cloud</title>
<link rel="stylesheet" th:href="@{/styles.css}" />
</head>
<body>
<form method="POST" th:action="@{/orders}" th:object="${order}">
<h1>Order your taco creations!</h1>
<img th:src="@{/images/TacoCloud.png}"/>
<a th:href="@{/design}" id="another">Design another taco</a><br/>
<div th:if="${#fields.hasErrors()}">
<span class="validationError">
Please correct the problems below and resubmit.
</span>
</div>
<h3>Deliver my taco masterpieces to...</h3>
<label for="name">Name: </label>
<input type="text" th:field="*{name}"/>
<!-- end::allButValidation[] -->
<span class="validationError"
th:if="${#fields.hasErrors('name')}"
th:errors="*{name}">Name Error</span>
<!-- tag::allButValidation[] -->
<br/>
<label for="street">Street address: </label>
<input type="text" th:field="*{street}"/>
<!-- end::allButValidation[] -->
<span class="validationError"
th:if="${#fields.hasErrors('street')}"
th:errors="*{street}">Street Error</span>
<!-- tag::allButValidation[] -->
<br/>
<label for="city">City: </label>
<input type="text" th:field="*{city}"/>
<!-- end::allButValidation[] -->
<span class="validationError"
th:if="${#fields.hasErrors('city')}"
th:errors="*{city}">City Error</span>
<!-- tag::allButValidation[] -->
<br/>
<label for="state">State: </label>
<input type="text" th:field="*{state}"/>
<!-- end::allButValidation[] -->
<span class="validationError"
th:if="${#fields.hasErrors('state')}"
th:errors="*{state}">State Error</span>
<!-- tag::allButValidation[] -->
<br/>
<label for="zip">Zip code: </label>
<input type="text" th:field="*{zip}"/>
<!-- end::allButValidation[] -->
<span class="validationError"
th:if="${#fields.hasErrors('zip')}"
th:errors="*{zip}">Zip Error</span>
<!-- tag::allButValidation[] -->
<br/>
<h3>Here's how I'll pay...</h3>
<!-- tag::validatedField[] -->
<label for="ccNumber">Credit Card #: </label>
<input type="text" th:field="*{ccNumber}"/>
<!-- end::allButValidation[] -->
<span class="validationError"
th:if="${#fields.hasErrors('ccNumber')}"
th:errors="*{ccNumber}">CC Num Error</span>
<!-- tag::allButValidation[] -->
<!-- end::validatedField[] -->
<br/>
<label for="ccExpiration">Expiration: </label>
<input type="text" th:field="*{ccExpiration}"/>
<!-- end::allButValidation[] -->
<span class="validationError"
th:if="${#fields.hasErrors('ccExpiration')}"
th:errors="*{ccExpiration}">CC Num Error</span>
<!-- tag::allButValidation[] -->
<br/>
<label for="ccCVV">CVV: </label>
<input type="text" th:field="*{ccCVV}"/>
<!-- end::allButValidation[] -->
<span class="validationError"
th:if="${#fields.hasErrors('ccCVV')}"
th:errors="*{ccCVV}">CC Num Error</span>
<!-- tag::allButValidation[] -->
<br/>
<input type="submit" value="Submit order"/>
</form>
</body>
</html>
<!-- end::allButValidation[] -->
Clase main() inicial
package tacos;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // <1>
public class TacoCloudApplication {
public static void main(String[] args) {
SpringApplication.run(TacoCloudApplication.class, args); // <2>
}
}
clase Ingredientes
package tacos;
import lombok.Data;
import lombok.RequiredArgsConstructor;
@Data
@RequiredArgsConstructor
public class Ingredient {
private final String id;
private final String name;
private final Type type;
public static enum Type {
WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
}
}
class Order
//tag::all[]
//tag::allButValidation[]
package tacos;
import javax.validation.constraints.Digits;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.CreditCardNumber;
import lombok.Data;
@Data
public class Order {
//end::allButValidation[]
@NotBlank(message="Name is required")
//tag::allButValidation[]
private String name;
//end::allButValidation[]
@NotBlank(message="Street is required")
//tag::allButValidation[]
private String street;
//end::allButValidation[]
@NotBlank(message="City is required")
//tag::allButValidation[]
private String city;
//end::allButValidation[]
@NotBlank(message="State is required")
//tag::allButValidation[]
private String state;
//end::allButValidation[]
@NotBlank(message="Zip code is required")
//tag::allButValidation[]
private String zip;
//end::allButValidation[]
@CreditCardNumber(message="Not a valid credit card number")
//tag::allButValidation[]
private String ccNumber;
//end::allButValidation[]
@Pattern(regexp="^(0[1-9]|1[0-2])([\\/])([1-9][0-9])$",
message="Must be formatted MM/YY")
//tag::allButValidation[]
private String ccExpiration;
//end::allButValidation[]
@Digits(integer=3, fraction=0, message="Invalid CVV")
//tag::allButValidation[]
private String ccCVV;
}
//end::allButValidation[]
//end::all[]
class Taco
// tag::all[]
// tag::allButValidation[]
package tacos;
import java.util.List;
// end::allButValidation[]
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
// tag::allButValidation[]
import lombok.Data;
@Data
public class Taco {
// end::allButValidation[]
@NotNull
@Size(min=5, message="Name must be at least 5 characters long")
// tag::allButValidation[]
private String name;
// end::allButValidation[]
@Size(min=1, message="You must choose at least 1 ingredient")
// tag::allButValidation[]
private List<String> ingredients;
}
//end::allButValidation[]
//tag::end[]
SUBCARPETA WEB
CONTROLER
// tag::head[]
package tacos.web;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.slf4j.Slf4j;
import tacos.Ingredient;
import tacos.Ingredient.Type;
import tacos.Taco;
@Slf4j
@Controller
@RequestMapping("/design")
public class DesignTacoController {
//end::head[]
@ModelAttribute
public void addIngredientsToModel(Model model) {
List<Ingredient> ingredients = Arrays.asList(
new Ingredient("FLTO", "Flour Tortilla", Type.WRAP),
new Ingredient("COTO", "Corn Tortilla", Type.WRAP),
new Ingredient("GRBF", "Ground Beef", Type.PROTEIN),
new Ingredient("CARN", "Carnitas", Type.PROTEIN),
new Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES),
new Ingredient("LETC", "Lettuce", Type.VEGGIES),
new Ingredient("CHED", "Cheddar", Type.CHEESE),
new Ingredient("JACK", "Monterrey Jack", Type.CHEESE),
new Ingredient("SLSA", "Salsa", Type.SAUCE),
new Ingredient("SRCR", "Sour Cream", Type.SAUCE)
);
Type[] types = Ingredient.Type.values();
for (Type type : types) {
model.addAttribute(type.toString().toLowerCase(),
filterByType(ingredients, type));
}
}
//tag::showDesignForm[]
@GetMapping
public String showDesignForm(Model model) {
model.addAttribute("design", new Taco());
return "design";
}
//end::showDesignForm[]
/*
//tag::processDesign[]
@PostMapping
public String processDesign(Design design) {
// Save the taco design...
// We'll do this in chapter 3
log.info("Processing design: " + design);
return "redirect:/orders/current";
}
//end::processDesign[]
*/
//tag::processDesignValidated[]
@PostMapping
public String processDesign(@Valid @ModelAttribute("design") Taco design, Errors errors, Model model) {
if (errors.hasErrors()) {
return "design";
}
// Save the taco design...
// We'll do this in chapter 3
log.info("Processing design: " + design);
return "redirect:/orders/current";
}
//end::processDesignValidated[]
//tag::filterByType[]
private List<Ingredient> filterByType(
List<Ingredient> ingredients, Type type) {
return ingredients
.stream()
.filter(x -> x.getType().equals(type))
.collect(Collectors.toList());
}
//end::filterByType[]
// tag::foot[]
}
// end::foot[]
ORDER CONTROLLER
// tag::baseClass[]
package tacos.web;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
//end::baseClass[]
import org.springframework.web.bind.annotation.PostMapping;
//tag::baseClass[]
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.slf4j.Slf4j;
import tacos.Order;
@Slf4j
@Controller
@RequestMapping("/orders")
public class OrderController {
//end::baseClass[]
//tag::orderForm[]
@GetMapping("/current")
public String orderForm(Model model) {
model.addAttribute("order", new Order());
return "orderForm";
}
//end::orderForm[]
/*
//tag::handlePost[]
@PostMapping
public String processOrder(Order order) {
log.info("Order submitted: " + order);
return "redirect:/";
}
//end::handlePost[]
*/
//tag::handlePostWithValidation[]
@PostMapping
public String processOrder(@Valid Order order, Errors errors) {
if (errors.hasErrors()) {
return "orderForm";
}
log.info("Order submitted: " + order);
return "redirect:/";
}
//end::handlePostWithValidation[]
//tag::baseClass[]
}
//end::baseClass[]
WebConfig
package tacos.web;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
}
}
TEST
package tacos;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class DesignAndOrderTacosBrowserTest {
private static HtmlUnitDriver browser;
@LocalServerPort
private int port;
@Autowired
TestRestTemplate rest;
@BeforeClass
public static void setup() {
browser = new HtmlUnitDriver();
browser.manage().timeouts()
.implicitlyWait(10, TimeUnit.SECONDS);
}
@AfterClass
public static void closeBrowser() {
browser.quit();
}
@Test
public void testDesignATacoPage_HappyPath() throws Exception {
browser.get(homePageUrl());
clickDesignATaco();
assertDesignPageElements();
buildAndSubmitATaco("Basic Taco", "FLTO", "GRBF", "CHED", "TMTO", "SLSA");
clickBuildAnotherTaco();
buildAndSubmitATaco("Another Taco", "COTO", "CARN", "JACK", "LETC", "SRCR");
fillInAndSubmitOrderForm();
assertEquals(homePageUrl(), browser.getCurrentUrl());
}
@Test
public void testDesignATacoPage_EmptyOrderInfo() throws Exception {
browser.get(homePageUrl());
clickDesignATaco();
assertDesignPageElements();
buildAndSubmitATaco("Basic Taco", "FLTO", "GRBF", "CHED", "TMTO", "SLSA");
submitEmptyOrderForm();
fillInAndSubmitOrderForm();
assertEquals(homePageUrl(), browser.getCurrentUrl());
}
@Test
public void testDesignATacoPage_InvalidOrderInfo() throws Exception {
browser.get(homePageUrl());
clickDesignATaco();
assertDesignPageElements();
buildAndSubmitATaco("Basic Taco", "FLTO", "GRBF", "CHED", "TMTO", "SLSA");
submitInvalidOrderForm();
fillInAndSubmitOrderForm();
assertEquals(homePageUrl(), browser.getCurrentUrl());
}
//
// Browser test action methods
//
private void buildAndSubmitATaco(String name, String... ingredients) {
assertDesignPageElements();
for (String ingredient : ingredients) {
browser.findElementByCssSelector("input[value='" + ingredient + "']").click();
}
browser.findElementByCssSelector("input#name").sendKeys(name);
browser.findElementByCssSelector("form").submit();
}
private void assertDesignPageElements() {
assertEquals(designPageUrl(), browser.getCurrentUrl());
List<WebElement> ingredientGroups = browser.findElementsByClassName("ingredient-group");
assertEquals(5, ingredientGroups.size());
WebElement wrapGroup = browser.findElementByCssSelector("div.ingredient-group#wraps");
List<WebElement> wraps = wrapGroup.findElements(By.tagName("div"));
assertEquals(2, wraps.size());
assertIngredient(wrapGroup, 0, "FLTO", "Flour Tortilla");
assertIngredient(wrapGroup, 1, "COTO", "Corn Tortilla");
WebElement proteinGroup = browser.findElementByCssSelector("div.ingredient-group#proteins");
List<WebElement> proteins = proteinGroup.findElements(By.tagName("div"));
assertEquals(2, proteins.size());
assertIngredient(proteinGroup, 0, "GRBF", "Ground Beef");
assertIngredient(proteinGroup, 1, "CARN", "Carnitas");
WebElement cheeseGroup = browser.findElementByCssSelector("div.ingredient-group#cheeses");
List<WebElement> cheeses = proteinGroup.findElements(By.tagName("div"));
assertEquals(2, cheeses.size());
assertIngredient(cheeseGroup, 0, "CHED", "Cheddar");
assertIngredient(cheeseGroup, 1, "JACK", "Monterrey Jack");
WebElement veggieGroup = browser.findElementByCssSelector("div.ingredient-group#veggies");
List<WebElement> veggies = proteinGroup.findElements(By.tagName("div"));
assertEquals(2, veggies.size());
assertIngredient(veggieGroup, 0, "TMTO", "Diced Tomatoes");
assertIngredient(veggieGroup, 1, "LETC", "Lettuce");
WebElement sauceGroup = browser.findElementByCssSelector("div.ingredient-group#sauces");
List<WebElement> sauces = proteinGroup.findElements(By.tagName("div"));
assertEquals(2, sauces.size());
assertIngredient(sauceGroup, 0, "SLSA", "Salsa");
assertIngredient(sauceGroup, 1, "SRCR", "Sour Cream");
}
private void fillInAndSubmitOrderForm() {
assertTrue(browser.getCurrentUrl().startsWith(orderDetailsPageUrl()));
fillField("input#name", "Ima Hungry");
fillField("input#street", "1234 Culinary Blvd.");
fillField("input#city", "Foodsville");
fillField("input#state", "CO");
fillField("input#zip", "81019");
fillField("input#ccNumber", "4111111111111111");
fillField("input#ccExpiration", "10/19");
fillField("input#ccCVV", "123");
browser.findElementByCssSelector("form").submit();
}
private void submitEmptyOrderForm() {
assertEquals(currentOrderDetailsPageUrl(), browser.getCurrentUrl());
browser.findElementByCssSelector("form").submit();
assertEquals(orderDetailsPageUrl(), browser.getCurrentUrl());
List<String> validationErrors = getValidationErrorTexts();
assertEquals(9, validationErrors.size());
assertTrue(validationErrors.contains("Please correct the problems below and resubmit."));
assertTrue(validationErrors.contains("Name is required"));
assertTrue(validationErrors.contains("Street is required"));
assertTrue(validationErrors.contains("City is required"));
assertTrue(validationErrors.contains("State is required"));
assertTrue(validationErrors.contains("Zip code is required"));
assertTrue(validationErrors.contains("Not a valid credit card number"));
assertTrue(validationErrors.contains("Must be formatted MM/YY"));
assertTrue(validationErrors.contains("Invalid CVV"));
}
private List<String> getValidationErrorTexts() {
List<WebElement> validationErrorElements = browser.findElementsByClassName("validationError");
List<String> validationErrors = validationErrorElements.stream()
.map(el -> el.getText())
.collect(Collectors.toList());
return validationErrors;
}
private void submitInvalidOrderForm() {
assertTrue(browser.getCurrentUrl().startsWith(orderDetailsPageUrl()));
fillField("input#name", "I");
fillField("input#street", "1");
fillField("input#city", "F");
fillField("input#state", "C");
fillField("input#zip", "8");
fillField("input#ccNumber", "1234432112344322");
fillField("input#ccExpiration", "14/91");
fillField("input#ccCVV", "1234");
browser.findElementByCssSelector("form").submit();
assertEquals(orderDetailsPageUrl(), browser.getCurrentUrl());
List<String> validationErrors = getValidationErrorTexts();
assertEquals(4, validationErrors.size());
assertTrue(validationErrors.contains("Please correct the problems below and resubmit."));
assertTrue(validationErrors.contains("Not a valid credit card number"));
assertTrue(validationErrors.contains("Must be formatted MM/YY"));
assertTrue(validationErrors.contains("Invalid CVV"));
}
private void fillField(String fieldName, String value) {
WebElement field = browser.findElementByCssSelector(fieldName);
field.clear();
field.sendKeys(value);
}
private void assertIngredient(WebElement ingredientGroup,
int ingredientIdx, String id, String name) {
List<WebElement> proteins = ingredientGroup.findElements(By.tagName("div"));
WebElement ingredient = proteins.get(ingredientIdx);
assertEquals(id,
ingredient.findElement(By.tagName("input")).getAttribute("value"));
assertEquals(name,
ingredient.findElement(By.tagName("span")).getText());
}
private void clickDesignATaco() {
assertEquals(homePageUrl(), browser.getCurrentUrl());
browser.findElementByCssSelector("a[id='design']").click();
}
private void clickBuildAnotherTaco() {
assertTrue(browser.getCurrentUrl().startsWith(orderDetailsPageUrl()));
browser.findElementByCssSelector("a[id='another']").click();
}
//
// URL helper methods
//
private String designPageUrl() {
return homePageUrl() + "design";
}
private String homePageUrl() {
return "http://localhost:" + port + "/";
}
private String orderDetailsPageUrl() {
return homePageUrl() + "orders";
}
private String currentOrderDetailsPageUrl() {
return homePageUrl() + "orders/current";
}
}
DesignTacoControllerTest
// tag::testShowDesignForm[]
package tacos;
import static org.mockito.Mockito.verify;
import static
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static
org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
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 tacos.Ingredient.Type;
import tacos.web.DesignTacoController;
//tag::testProcessForm[]
@RunWith(SpringRunner.class)
@WebMvcTest(DesignTacoController.class)
public class DesignTacoControllerTest {
//end::testProcessForm[]
@Autowired
private MockMvc mockMvc;
private List<Ingredient> ingredients;
//end::testShowDesignForm[]
/*
//tag::testProcessForm[]
...
//end::testProcessForm[]
*/
//tag::testProcessForm[]
private Taco design;
//end::testProcessForm[]
//tag::testShowDesignForm[]
@Before
public void setup() {
ingredients = Arrays.asList(
new Ingredient("FLTO", "Flour Tortilla", Type.WRAP),
new Ingredient("COTO", "Corn Tortilla", Type.WRAP),
new Ingredient("GRBF", "Ground Beef", Type.PROTEIN),
new Ingredient("CARN", "Carnitas", Type.PROTEIN),
new Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES),
new Ingredient("LETC", "Lettuce", Type.VEGGIES),
new Ingredient("CHED", "Cheddar", Type.CHEESE),
new Ingredient("JACK", "Monterrey Jack", Type.CHEESE),
new Ingredient("SLSA", "Salsa", Type.SAUCE),
new Ingredient("SRCR", "Sour Cream", Type.SAUCE)
);
//end::testShowDesignForm[]
design = new Taco();
design.setName("Test Taco");
design.setIngredients(Arrays.asList("FLTO", "GRBF", "CHED"));
//tag::testShowDesignForm[]
}
@Test
public void testShowDesignForm() throws Exception {
mockMvc.perform(get("/design"))
.andExpect(status().isOk())
.andExpect(view().name("design"))
.andExpect(model().attribute("wrap", ingredients.subList(0, 2)))
.andExpect(model().attribute("protein", ingredients.subList(2, 4)))
.andExpect(model().attribute("veggies", ingredients.subList(4, 6)))
.andExpect(model().attribute("cheese", ingredients.subList(6, 8)))
.andExpect(model().attribute("sauce", ingredients.subList(8, 10)));
}
//end::testShowDesignForm[]
/*
//tag::testProcessForm[]
...
//end::testProcessForm[]
*/
//tag::testProcessForm[]
@Test
public void processDesign() throws Exception {
mockMvc.perform(post("/design")
.content("name=Test+Taco&ingredients=FLTO,GRBF,CHED")
.contentType(MediaType.APPLICATION_FORM_URLENCODED))
.andExpect(status().is3xxRedirection())
.andExpect(header().stringValues("Location", "/orders/current"));
}
//tag::testShowDesignForm[]
}
//end::testShowDesignForm[]
//end::testProcessForm[]
HomeControllerTest
package tacos;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
@RunWith(SpringRunner.class)
@WebMvcTest // <1>
public class HomeControllerTest {
@Autowired
private MockMvc mockMvc; // <2>
@Test
public void testHomePage() throws Exception {
mockMvc.perform(get("/")) // <3>
.andExpect(status().isOk()) // <4>
.andExpect(view().name("home")) // <5>
.andExpect(content().string( // <6>
containsString("Welcome to...")));
}
}
HomePageBrowserTest
package tacos;
import java.util.concurrent.TimeUnit;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
public class HomePageBrowserTest {
@LocalServerPort
private int port;
private static HtmlUnitDriver browser;
@BeforeClass
public static void setup() {
browser = new HtmlUnitDriver();
browser.manage().timeouts()
.implicitlyWait(10, TimeUnit.SECONDS);
}
@AfterClass
public static void teardown() {
browser.quit();
}
@Test
public void testHomePage() {
String homePage = "http://localhost:" + port;
browser.get(homePage);
String titleText = browser.getTitle();
Assert.assertEquals("Taco Cloud", titleText);
String h1Text = browser.findElementByTagName("h1").getText();
Assert.assertEquals("Welcome to...", h1Text);
String imgSrc = browser.findElementByTagName("img")
.getAttribute("src");
Assert.assertEquals(homePage + "/images/TacoCloud.png", imgSrc);
}
}
TacoCloudApplicationTests
package tacos;
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) // <1>
@SpringBootTest // <2>
public class TacoCloudApplicationTests {
@Test // <3>
public void contextLoads() {
}
}
POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>sia</groupId>
<artifactId>taco-cloud</artifactId>
<version>0.0.2-SNAPSHOT</version>
<packaging>jar</packaging>
<name>taco-cloud</name>
<description>Taco Cloud Example</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>
UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>
UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- tag::thymeleaf[] -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- end::thymeleaf[] -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- tag::devTools[] -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<!-- end::devTools[] -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>htmlunit-driver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!-- <scope>compileOnly</scope> -->
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
WRAPPER
maven-wrapper.properties
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip
.gitignore
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/
mvnw
#!/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
#
# Look for the Apple JDKs first to preserve the existing behaviour, and then look
# for the new JDKs provided by Oracle.
#
if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then
#
# Apple JDKs
#
export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
fi
if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
#
# Apple JDKs
#
export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
fi
if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
#
# Oracle JDKs
#
export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
fi
if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
#
# Apple JDKs
#
export JAVA_HOME=`/usr/libexec/java_home`
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
# 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"`
fi
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
local basedir=$(pwd)
local wdir=$(pwd)
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
wdir=$(cd "$wdir/.."; pwd)
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
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} "$@"
mvnw.cmd
@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
set MAVEN_CMD_LINE_ARGS=%*
@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="".\.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_CMD_LINE_ARGS%
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%
LOMBOOK
https://projectlombok.org/features/Data
@Data
All together now: A shortcut for @ToString
, @EqualsAndHashCode
, @Getter
on all fields, @Setter
on all non-final fields, and @RequiredArgsConstructor
!
Overview
@Data
is a convenient shortcut annotation that bundles the features of @ToString
, @EqualsAndHashCode
, @Getter
/ @Setter
and @RequiredArgsConstructor
together: In other words, @Data
generates all the boilerplate that is normally associated with simple POJOs (Plain Old Java Objects) and beans: getters for all fields, setters for all non-final fields, and appropriate toString
, equals
and hashCode
implementations that involve the fields of the class, and a constructor that initializes all final fields, as well as all non-final fields with no initializer that have been marked with @NonNull
, in order to ensure the field is never null.
@Data
is like having implicit @Getter
, @Setter
, @ToString
, @EqualsAndHashCode
and @RequiredArgsConstructor
annotations on the class (except that no constructor will be generated if any explicitly written constructors already exist). However, the parameters of these annotations (such as callSuper
, includeFieldNames
and exclude
) cannot be set with @Data
. If you need to set non-default values for any of these parameters, just add those annotations explicitly; @Data
is smart enough to defer to those annotations.
All generated getters and setters will be public
. To override the access level, annotate the field or class with an explicit @Setter
and/or @Getter
annotation. You can also use this annotation (by combining it with AccessLevel.NONE
) to suppress generating a getter and/or setter altogether.
All fields marked as transient
will not be considered for hashCode
and equals
. All static fields will be skipped entirely (not considered for any of the generated methods, and no setter/getter will be made for them).
If the class already contains a method with the same name and parameter count as any method that would normally be generated, that method is not generated, and no warning or error is emitted. For example, if you already have a method with signature equals(AnyType param)
, no equals
method will be generated, even though technically it might be an entirely different method due to having different parameter types. The same rule applies to the constructor (any explicit constructor will prevent @Data
from generating one), as well as toString
, equals
, and all getters and setters. You can mark any constructor or method with @lombok.experimental.Tolerate
to hide them from lombok.
@Data
can handle generics parameters for fields just fine. In order to reduce the boilerplate when constructing objects for classes with generics, you can use the staticConstructor
parameter to generate a private constructor, as well as a static method that returns a new instance. This way, javac will infer the variable name. Thus, by declaring like so: @Data(staticConstructor="of") class Foo<T> { private T x;}
you can create new instances of Foo
by writing: Foo.of(5);
instead of having to write: new Foo<Integer>(5);
.
@Data
With Lombok
import lombok.AccessLevel;
import lombok.Setter;
import lombok.Data;
import lombok.ToString;
@Data public class DataExample {
private final String name;
@Setter(AccessLevel.PACKAGE) private int age;
private double score;
private String[] tags;
@ToString(includeFieldNames=true)
@Data(staticConstructor="of")
public static class Exercise<T> {
private final String name;
private final T value;
}
}
Vanilla Java
In information technology, vanilla (pronounced vah-NIHL-uh ) is an adjective meaning plain or basic. The unfeatured version of a product is sometimes referred to as the vanilla version. The term is based on the fact that vanilla is the most popular or at least the most commonly served flavor of ice cream.
WTF use this stupid term, when the people can say only basic !!!! WTF
import java.util.Arrays;
public class DataExample {
private final String name;
private int age;
private double score;
private String[] tags;
public DataExample(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public void setScore(double score) {
this.score = score;
}
public double getScore() {
return this.score;
}
public String[] getTags() {
return this.tags;
}
public void setTags(String[] tags) {
this.tags = tags;
}
@Override public String toString() {
return "DataExample(" + this.getName() + ", " + this.getAge() + ", " + this.getScore() + ", " + Arrays.deepToString(this.getTags()) + ")";
}
protected boolean canEqual(Object other) {
return other instanceof DataExample;
}
@Override public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof DataExample)) return false;
DataExample other = (DataExample) o;
if (!other.canEqual((Object)this)) return false;
if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
if (this.getAge() != other.getAge()) return false;
if (Double.compare(this.getScore(), other.getScore()) != 0) return false;
if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false;
return true;
}
@Override public int hashCode() {
final int PRIME = 59;
int result = 1;
final long temp1 = Double.doubleToLongBits(this.getScore());
result = (result*PRIME) + (this.getName() == null ? 43 : this.getName().hashCode());
result = (result*PRIME) + this.getAge();
result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32));
result = (result*PRIME) + Arrays.deepHashCode(this.getTags());
return result;
}
public static class Exercise<T> {
private final String name;
private final T value;
private Exercise(String name, T value) {
this.name = name;
this.value = value;
}
public static <T> Exercise<T> of(String name, T value) {
return new Exercise<T>(name, value);
}
public String getName() {
return this.name;
}
public T getValue() {
return this.value;
}
@Override public String toString() {
return "Exercise(name=" + this.getName() + ", value=" + this.getValue() + ")";
}
protected boolean canEqual(Object other) {
return other instanceof Exercise;
}
@Override public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Exercise)) return false;
Exercise<?> other = (Exercise<?>) o;
if (!other.canEqual((Object)this)) return false;
if (this.getName() == null ? other.getValue() != null : !this.getName().equals(other.getName())) return false;
if (this.getValue() == null ? other.getValue() != null : !this.getValue().equals(other.getValue())) return false;
return true;
}
@Override public int hashCode() {
final int PRIME = 59;
int result = 1;
result = (result*PRIME) + (this.getName() == null ? 43 : this.getName().hashCode());
result = (result*PRIME) + (this.getValue() == null ? 43 : this.getValue().hashCode());
return result;
}
}
}
+++++++++++++++++++++++
Overview
@SneakyThrows
can be used to sneakily throw checked exceptions without actually declaring this in your method's throws
clause. This somewhat contentious ability should be used carefully, of course. The code generated by lombok will not ignore, wrap, replace, or otherwise modify the thrown checked exception; it simply fakes out the compiler. On the JVM (class file) level, all exceptions, checked or not, can be thrown regardless of the throws
clause of your methods, which is why this works.
Common use cases for when you want to opt out of the checked exception mechanism center around 2 situations:
- A needlessly strict interface, such as
Runnable
- whatever exception propagates out of your run()
method, checked or not, it will be passed to the Thread
's unhandled exception handler. Catching a checked exception and wrapping it in some sort of RuntimeException
is only obscuring the real cause of the issue. - An 'impossible' exception. For example,
new String(someByteArray, "UTF-8");
declares that it can throw an UnsupportedEncodingException
but according to the JVM specification, UTF-8 must always be available. An UnsupportedEncodingException
here is about as likely as a ClassNotFoundError
when you use a String object, and you don't catch those either!
Being constrained by needlessly strict interfaces is particularly common when using lambda syntax (arg -> action
); however, lambdas cannot be annotated, which means it is not so easy to use @SneakyThrows
in combination with lambdas.
Be aware that it is impossible to catch sneakily thrown checked types directly, as javac will not let you write a catch block for an exception type that no method call in the try body declares as thrown. This problem is not relevant in either of the use cases listed above, so let this serve as a warning that you should not use the @SneakyThrows
mechanism without some deliberation!
You can pass any number of exceptions to the @SneakyThrows
annotation. If you pass no exceptions, you may throw any exception sneakily.
With Lombok
import lombok.SneakyThrows;
public class SneakyThrowsExample implements Runnable {
@SneakyThrows(UnsupportedEncodingException.class)
public String utf8ToString(byte[] bytes) {
return new String(bytes, "UTF-8");
}
@SneakyThrows
public void run() {
throw new Throwable();
}
}
Vanilla Java
import lombok.Lombok;
public class SneakyThrowsExample implements Runnable {
public String utf8ToString(byte[] bytes) {
try {
return new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw Lombok.sneakyThrow(e);
}
}
public void run() {
try {
throw new Throwable();
} catch (Throwable t) {
throw Lombok.sneakyThrow(t);
}
}
}
SALIDA
Microsoft Windows [Versión 10.0.18363.1379]
(c) 2019 Microsoft Corporation. Todos los derechos reservados.
C:\Users\rober\Desktop\DELROJA_2020 ORDEN TOTAL\spring-in-action-5-samples-master\spring-in-action-5-samples-master\ch02\tacos>mvn clean
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building taco-cloud 0.0.2-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:3.0.0:clean (default-clean) @ taco-cloud ---
[INFO] Deleting C:\Users\rober\Desktop\DELROJA_2020 ORDEN TOTAL\spring-in-action-5-samples-master\spring-in-action-5-samples-master\ch02\tacos\target
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.678s
[INFO] Finished at: Mon Feb 15 19:22:23 CST 2021
[INFO] Final Memory: 7M/245M
[INFO] ------------------------------------------------------------------------
C:\Users\rober\Desktop\DELROJA_2020 ORDEN TOTAL\spring-in-action-5-samples-master\spring-in-action-5-samples-master\ch02\tacos>mvn spring-boot:run
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building taco-cloud 0.0.2-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> spring-boot-maven-plugin:2.0.4.RELEASE:run (default-cli) @ taco-cloud >>>
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ taco-cloud ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 5 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.7.0:compile (default-compile) @ taco-cloud ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 7 source files to C:\Users\rober\Desktop\DELROJA_2020 ORDEN TOTAL\spring-in-action-5-samples-master\spring-in-action-5-samples-master\ch02\tacos\target\classes
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ taco-cloud ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\Users\rober\Desktop\DELROJA_2020 ORDEN TOTAL\spring-in-action-5-samples-master\spring-in-action-5-samples-master\ch02\tacos\src\test\resou
rces
[INFO]
[INFO] --- maven-compiler-plugin:3.7.0:testCompile (default-testCompile) @ taco-cloud ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 5 source files to C:\Users\rober\Desktop\DELROJA_2020 ORDEN TOTAL\spring-in-action-5-samples-master\spring-in-action-5-samples-master\ch02\tacos\target\test-classes
[INFO]
[INFO] <<< spring-boot-maven-plugin:2.0.4.RELEASE:run (default-cli) @ taco-cloud <<<
[INFO]
[INFO] --- spring-boot-maven-plugin:2.0.4.RELEASE:run (default-cli) @ taco-cloud ---
[INFO] Attaching agents: []
19:22:52.329 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Included patterns for restart : []
19:22:52.333 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Excluded patterns for restart : [/spring-boot-actuator/target/classes/, /spring-boot-devtools/ta
rget/classes/, /spring-boot/target/classes/, /spring-boot-starter-[\w-]+/, /spring-boot-autoconfigure/target/classes/, /spring-boot-starter/target/classes/]
19:22:52.334 [main] DEBUG org.springframework.boot.devtools.restart.ChangeableUrls - Matching URLs for reloading : [file:/C:/Users/rober/Desktop/DELROJA_2020%20ORDEN%20TOTAL/spring-in-
action-5-samples-master/spring-in-action-5-samples-master/ch02/tacos/target/classes/]
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.4.RELEASE)
2021-02-15 19:22:52.788 INFO 16128 --- [ restartedMain] tacos.TacoCloudApplication : Starting TacoCloudApplication on DESKTOP-DLK8P3R with PID 16128 (started by rober i
n C:\Users\rober\Desktop\DELROJA_2020 ORDEN TOTAL\spring-in-action-5-samples-master\spring-in-action-5-samples-master\ch02\tacos)
2021-02-15 19:22:52.790 INFO 16128 --- [ restartedMain] tacos.TacoCloudApplication : No active profile set, falling back to default profiles: default
2021-02-15 19:22:52.859 INFO 16128 --- [ restartedMain] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebS
erverApplicationContext@696e3a6d: startup date [Mon Feb 15 19:22:52 CST 2021]; root of context hierarchy
2021-02-15 19:22:55.471 INFO 16128 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-02-15 19:22:55.513 INFO 16128 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-02-15 19:22:55.514 INFO 16128 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.32
2021-02-15 19:22:55.528 INFO 16128 --- [ost-startStop-1] o.a.catalina.core.AprLifecycleListener : The APR based Apache Tomcat Native library which allows optimal performance in prod
uction environments was not found on the java.library.path: [C:\Program Files\Java\jdk1.8.0_271\jre\bin;C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\Program Files (x86)\Co
mmon Files\Oracle\Java\javapath;C:\oracle\app\oracle\product\11.2.0\server\bin;C:\Program Files (x86)\Intel\iCLS Client\;C:\Program Files\Intel\iCLS Client\;C:\Windows\system32;C:\Wind
ows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files\Intel\Intel(R) Manageme
nt Engine Components\DAL;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files (x86)\
NVIDIA Corporation\PhysX\Common;C:\maven\bin;C:\Program Files\Java\jdk1.8.0_271\\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\
;C:\Program Files\Java\jdk1.8.0_271\;C:\Users\rober\Desktop\openshift\;C:\Ruby24-x64\bin;C:\Program Files\Git\cmd;C:\WINDOWS\System32\OpenSSH\;C:\springCLI\bin;C:\Users\rober\AppData\L
ocal\Programs\Python\Python35\;C:\Users\rober\AppData\Local\Programs\Python\Python35\Scripts\;C:\Program Files\Intel\WiFi\bin\;C:\Program Files\Common Files\Intel\WirelessCommon\;C:\Us
ers\rober\Desktop\JAVAAFONDO\Java_a_fondo\hsqldb-2.5.0\hsqldb-2.5.0\hsqldb\bin;C:\Program Files (x86)\Common Files\Oracle\Java\javapath;C:\oracle\app\oracle\product\11.2.0\server\bin;C
:\Program Files (x86)\Intel\iCLS Client\;C:\Program Files\Intel\iCLS Client\;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Prog
ram Files (x86)\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files (x86)\Intel\Intel(R) Management Engine
Components\IPT;C:\Program Files\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\maven\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\W
INDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\Users\rober\Desktop\openshift\;C:\Ruby24-x64\bin;C:\Program Files\Git\cmd;C:\WINDOWS\System32\OpenSSH\;C:\springCLI\
bin;C:\Users\rober\AppData\Local\Programs\Python\Python35\;C:\Users\rober\AppData\Local\Programs\Python\Pyt;C:\Program Files\Java\jdk1.8.0_271\;;.]
2021-02-15 19:22:55.667 INFO 16128 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-02-15 19:22:55.667 INFO 16128 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 2812 ms
2021-02-15 19:22:56.437 INFO 16128 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*]
2021-02-15 19:22:56.437 INFO 16128 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'webMvcMetricsFilter' to: [/*]
2021-02-15 19:22:56.437 INFO 16128 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2021-02-15 19:22:56.437 INFO 16128 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2021-02-15 19:22:56.437 INFO 16128 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*]
2021-02-15 19:22:56.437 INFO 16128 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpTraceFilter' to: [/*]
2021-02-15 19:22:56.437 INFO 16128 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Servlet dispatcherServlet mapped to [/]
2021-02-15 19:22:56.589 INFO 16128 --- [ restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.w
eb.servlet.resource.ResourceHttpRequestHandler]
2021-02-15 19:22:56.760 INFO 16128 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.Annotat
ionConfigServletWebServerApplicationContext@696e3a6d: startup date [Mon Feb 15 19:22:52 CST 2021]; root of context hierarchy
2021-02-15 19:22:56.848 INFO 16128 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/design],methods=[GET]}" onto public java.lang.String tacos.web.DesignTac
oController.showDesignForm(org.springframework.ui.Model)
2021-02-15 19:22:56.848 INFO 16128 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/design],methods=[POST]}" onto public java.lang.String tacos.web.DesignTa
coController.processDesign(tacos.Taco,org.springframework.validation.Errors,org.springframework.ui.Model)
2021-02-15 19:22:56.856 INFO 16128 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/orders/current],methods=[GET]}" onto public java.lang.String tacos.web.O
rderController.orderForm(org.springframework.ui.Model)
2021-02-15 19:22:56.858 INFO 16128 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/orders],methods=[POST]}" onto public java.lang.String tacos.web.OrderCon
troller.processOrder(tacos.Order,org.springframework.validation.Errors)
2021-02-15 19:22:56.858 INFO 16128 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.M
ap<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2021-02-15 19:22:56.858 INFO 16128 --- [ restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servle
t.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2021-02-15 19:22:56.868 INFO 16128 --- [ restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Root mapping to handler of type [class org.springframework.web.servlet.mvc.Paramete
rizableViewController]
2021-02-15 19:22:56.888 INFO 16128 --- [ restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.s
ervlet.resource.ResourceHttpRequestHandler]
2021-02-15 19:22:56.888 INFO 16128 --- [ restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.r
esource.ResourceHttpRequestHandler]
2021-02-15 19:22:57.367 INFO 16128 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
2021-02-15 19:22:57.390 INFO 16128 --- [ restartedMain] o.s.b.a.e.web.EndpointLinksResolver : Exposing 2 endpoint(s) beneath base path '/actuator'
2021-02-15 19:22:57.404 INFO 16128 --- [ restartedMain] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/health],methods=[GET],produces=[application/vnd.spring-boot.act
uator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(jav
ax.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>)
2021-02-15 19:22:57.405 INFO 16128 --- [ restartedMain] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator/info],methods=[GET],produces=[application/vnd.spring-boot.actua
tor.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(javax
.servlet.http.HttpServletRequest,java.util.Map<java.lang.String, java.lang.String>)
2021-02-15 19:22:57.406 INFO 16128 --- [ restartedMain] s.b.a.e.w.s.WebMvcEndpointHandlerMapping : Mapped "{[/actuator],methods=[GET],produces=[application/vnd.spring-boot.actuator.v
2+json || application/json]}" onto protected java.util.Map<java.lang.String, java.util.Map<java.lang.String, org.springframework.boot.actuate.endpoint.web.Link>> org.springframework.bo
ot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping.links(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2021-02-15 19:22:57.491 INFO 16128 --- [ restartedMain] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
2021-02-15 19:22:57.542 INFO 16128 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-02-15 19:22:57.547 INFO 16128 --- [ restartedMain] tacos.TacoCloudApplication : Started TacoCloudApplication in 5.191 seconds (JVM running for 5.846)
2021-02-15 19:23:08.929 INFO 16128 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2021-02-15 19:23:08.930 INFO 16128 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2021-02-15 19:23:08.965 INFO 16128 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 35 ms
2021-02-20 00:35:20.261 INFO 16128 --- [nio-8080-exec-1] tacos.web.DesignTacoController : Processing design: Taco(name=Roberto Perez Martinez, ingredients=[COTO, CARN, CHED,
TMTO, SLSA])