diff --git a/src/main/java/catering/Application.java b/src/main/java/catering/Application.java index 0d7afbc..b37a2f6 100644 --- a/src/main/java/catering/Application.java +++ b/src/main/java/catering/Application.java @@ -3,16 +3,24 @@ // SPDX-FileCopyrightText: 2023 swt23w23 package catering; +import java.util.List; + import org.salespointframework.EnableSalespoint; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import catering.inventory.InventoryMutateFormArgumentResolver; +import catering.inventory.InventoryMutateFormModelAttributeMethodProcessor; + /** * The main application class. */ @@ -46,10 +54,18 @@ public class Application { @Configuration static class WebConfiguration implements WebMvcConfigurer { + @Autowired + WebApplicationContext applicationContext; + @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/login").setViewName("login"); registry.addViewController("/").setViewName("index"); } + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(new InventoryMutateFormArgumentResolver(applicationContext)); + } } } diff --git a/src/main/java/catering/inventory/InventoryController.java b/src/main/java/catering/inventory/InventoryController.java index b3df29c..e7d8feb 100644 --- a/src/main/java/catering/inventory/InventoryController.java +++ b/src/main/java/catering/inventory/InventoryController.java @@ -10,15 +10,15 @@ import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.util.Assert; import org.springframework.validation.Errors; +import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import catering.catalog.CateringCatalog; -import catering.catalog.Consumable; -import catering.catalog.Rentable; import jakarta.validation.Valid; /* @@ -89,9 +89,6 @@ class InventoryController { /** * Returns the edit page for an inventory product. * - * This method is not mapped, - * but is used by other methods to generalise the display of the edit page. - * * @param model an instance of {@link Model} * @param form an already filled or empty {@link InventoryMutateForm} * @return the inventory-mutate template with the {@link InventoryMutateForm} @@ -104,47 +101,9 @@ class InventoryController { return "inventory-mutate"; } - /** - * Edits a consumable. - * - * @param form a user-filled {@link ConsumableMutateForm} - * @param errors an {@link Errors} object including validation errors - * @param pid the {@link Product} to edit - * @param model an instance of {@link Model} - * @return a redirect on success, otherwise the filled form with all errors - * highlighted - */ - @PostMapping(path = "/inventory/edit/{pid}", params = "type=Consumable") - String editConsumable(@Valid @ModelAttribute("form") ConsumableMutateForm form, Errors result, - @PathVariable Consumable pid, Model model) { - return edit(form, result, pid, model); - } - - /** - * Edits a rentable. - * - * @param form a user-filled {@link RentableMutateForm} - * @param errors an {@link Errors} object including validation errors - * @param pid the {@link Product} to edit - * @param model an instance of {@link Model} - * @return a redirect on success, otherwise the filled form with all errors - * highlighted - */ - @PostMapping(path = "/inventory/edit/{pid}", params = "type=Rentable") - String editRentable(@Valid @ModelAttribute("form") RentableMutateForm form, Errors result, - @PathVariable Rentable pid, Model model) { - return edit(form, result, pid, model); - } - /** * Edits an inventory product. * - * This method is not mapped, - * but is used by - * {@link #editConsumable(ConsumableMutateForm, Errors, Consumable, Model)} - * and {@link #editRentable(RentableMutateForm, Errors, Rentable, Model)} - * to generalise the editing of products. - * * @param form a user-filled {@link RentableMutateForm} * @param errors an {@link Errors} object including validation errors * @param product the {@link Product} to edit @@ -152,10 +111,12 @@ class InventoryController { * @return a redirect on success, otherwise the filled form with all errors * highlighted */ - String edit(InventoryMutateForm form, Errors result, Product product, Model model) { - if (result.hasErrors()) { - return edit(model, form); - } + @PostMapping(path = "/inventory/edit/{pid}") + String edit(InventoryMutateForm form, @PathVariable("pid") Product product, Model model) { + //String edit(@ModelAttribute InventoryMutateForm form, Errors result, @PathVariable("pid") Product product, Model model) { + //if (result.hasErrors()) { + // return edit(model, form); + //} form.modifyProduct(product); product = cateringCatalog.save(product); diff --git a/src/main/java/catering/inventory/InventoryMutateFormArgumentResolver.java b/src/main/java/catering/inventory/InventoryMutateFormArgumentResolver.java new file mode 100644 index 0000000..a1cdfb9 --- /dev/null +++ b/src/main/java/catering/inventory/InventoryMutateFormArgumentResolver.java @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +// SPDX-FileCopyrightText: 2024 swt23w23 +package catering.inventory; + +import java.util.Map; +import java.util.Optional; + +import org.salespointframework.catalog.Product; +import org.salespointframework.catalog.Product.ProductIdentifier; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.MethodParameter; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.annotation.ModelAttributeMethodProcessor; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.servlet.HandlerMapping; + +import catering.catalog.CateringCatalog; +import catering.catalog.Consumable; + +public class InventoryMutateFormArgumentResolver implements HandlerMethodArgumentResolver { + @Autowired + ModelAttributeMethodProcessor modelAttributeMethodProcessor; + + private WebApplicationContext applicationContext; + + public InventoryMutateFormArgumentResolver(WebApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return InventoryMutateForm.class == parameter.getParameterType(); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + CateringCatalog cateringCatalog = applicationContext.getBean(CateringCatalog.class); + InventoryMutateForm form = resolveByPid(webRequest, cateringCatalog) + .or(() -> resolveByExplicitParameter(webRequest)) + .map(InventoryMutateForm::forProductType) + .orElse(null); + + // trying to reimplement things from spring, don’t understand it + Object attribute; + var binder = binderFactory.createBinder(webRequest, attribute, "form", form.getClass()); + Object attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); + BindingResult bindingResult = binder.getBindingResult(); + Map bindingResultModel = bindingResult.getModel(); + mavContainer.removeAttributes(bindingResultModel); + mavContainer.addAllAttributes(bindingResultModel); + System.out.println(attribute); + return attribute; + } + + private Optional> resolveByPid(NativeWebRequest webRequest, + CateringCatalog cateringCatalog) { + webRequest.getParameterMap().forEach((k, v) -> System.out.println(k + " → " + v)); + return Optional + .ofNullable(getPathVariable(webRequest, "pid")) + .map(ProductIdentifier::of) + .flatMap(cateringCatalog::findById) + .map(p -> p.getClass()); + } + + private Optional> resolveByExplicitParameter(NativeWebRequest webRequest) { + return Optional.ofNullable(webRequest.getParameter("type")) + .flatMap(type -> { + try { + return Optional.of(Class.forName(Consumable.class.getPackageName() + "." + type)); + } catch (ClassNotFoundException e) { + return Optional.empty(); + } + }) + .filter(Product.class::isAssignableFrom) + .map(c -> (Class) c); + } + + private String getPathVariable(NativeWebRequest webRequest, String name) { + return ((Map) webRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, + NativeWebRequest.SCOPE_REQUEST)).get(name); + } +} diff --git a/src/main/resources/templates/inventory.html b/src/main/resources/templates/inventory.html index 737baf3..2e80b54 100644 --- a/src/main/resources/templates/inventory.html +++ b/src/main/resources/templates/inventory.html @@ -36,7 +36,7 @@ SPDX-FileCopyrightText: 2023 swt23w23