WIP WIP WIP Implement InventoryMutateFormArgumentResolver for InventoryMutateForm

This commit is contained in:
Simon Bruder 2024-01-09 01:02:59 +01:00
parent 20ea9e5869
commit 75cb944d12
Signed by: simon
GPG key ID: 8D3C82F9F309F8EC
4 changed files with 112 additions and 48 deletions

View file

@ -3,16 +3,24 @@
// SPDX-FileCopyrightText: 2023 swt23w23 // SPDX-FileCopyrightText: 2023 swt23w23
package catering; package catering;
import java.util.List;
import org.salespointframework.EnableSalespoint; import org.salespointframework.EnableSalespoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig; import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig;
import org.springframework.security.web.SecurityFilterChain; 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.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import catering.inventory.InventoryMutateFormArgumentResolver;
import catering.inventory.InventoryMutateFormModelAttributeMethodProcessor;
/** /**
* The main application class. * The main application class.
*/ */
@ -46,10 +54,18 @@ public class Application {
@Configuration @Configuration
static class WebConfiguration implements WebMvcConfigurer { static class WebConfiguration implements WebMvcConfigurer {
@Autowired
WebApplicationContext applicationContext;
@Override @Override
public void addViewControllers(ViewControllerRegistry registry) { public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login"); registry.addViewController("/login").setViewName("login");
registry.addViewController("/").setViewName("index"); registry.addViewController("/").setViewName("index");
} }
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new InventoryMutateFormArgumentResolver(applicationContext));
}
} }
} }

View file

@ -10,15 +10,15 @@ import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.validation.Errors; import org.springframework.validation.Errors;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping; 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.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import catering.catalog.CateringCatalog; import catering.catalog.CateringCatalog;
import catering.catalog.Consumable;
import catering.catalog.Rentable;
import jakarta.validation.Valid; import jakarta.validation.Valid;
/* /*
@ -89,9 +89,6 @@ class InventoryController {
/** /**
* Returns the edit page for an inventory product. * 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 model an instance of {@link Model}
* @param form an already filled or empty {@link InventoryMutateForm} * @param form an already filled or empty {@link InventoryMutateForm}
* @return the inventory-mutate template with the {@link InventoryMutateForm} * @return the inventory-mutate template with the {@link InventoryMutateForm}
@ -104,47 +101,9 @@ class InventoryController {
return "inventory-mutate"; 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. * 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 form a user-filled {@link RentableMutateForm}
* @param errors an {@link Errors} object including validation errors * @param errors an {@link Errors} object including validation errors
* @param product the {@link Product} to edit * @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 * @return a redirect on success, otherwise the filled form with all errors
* highlighted * highlighted
*/ */
String edit(InventoryMutateForm form, Errors result, Product product, Model model) { @PostMapping(path = "/inventory/edit/{pid}")
if (result.hasErrors()) { String edit(InventoryMutateForm form, @PathVariable("pid") Product product, Model model) {
return edit(model, form); //String edit(@ModelAttribute InventoryMutateForm form, Errors result, @PathVariable("pid") Product product, Model model) {
} //if (result.hasErrors()) {
// return edit(model, form);
//}
form.modifyProduct(product); form.modifyProduct(product);
product = cateringCatalog.save(product); product = cateringCatalog.save(product);

View file

@ -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, dont 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<String, Object> bindingResultModel = bindingResult.getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
System.out.println(attribute);
return attribute;
}
private Optional<Class<? extends Product>> 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<Class<? extends Product>> 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<? extends Product>) c);
}
private String getPathVariable(NativeWebRequest webRequest, String name) {
return ((Map<String, String>) webRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
NativeWebRequest.SCOPE_REQUEST)).get(name);
}
}

View file

@ -36,7 +36,7 @@ SPDX-FileCopyrightText: 2023 swt23w23
<td th:text="${item.product.wholesalePrice.multiply(item.quantity.getAmount())}"></td> <td th:text="${item.product.wholesalePrice.multiply(item.quantity.getAmount())}"></td>
<td> <td>
<div class="d-inline-flex flex-wrap gap-1 flex-column flex-xxl-row"> <div class="d-inline-flex flex-wrap gap-1 flex-column flex-xxl-row">
<a th:href="@{/inventory/edit/{id}(id=${item.product.id},type=${item.product.getClass().getSimpleName()})}"><button class="btn btn-warning">Bearbeiten</button></a> <a th:href="@{/inventory/edit/{id}(id=${item.product.id})}"><button class="btn btn-warning">Bearbeiten</button></a>
<a th:href="@{/inventory/delete/{id}(id=${item.product.id})}"><button class="btn btn-danger">Entfernen</button></a> <a th:href="@{/inventory/delete/{id}(id=${item.product.id})}"><button class="btn btn-danger">Entfernen</button></a>
</div> </div>
</td> </td>