Atributes for annotation ModelAtribute and PreAuthorize in Generic classes - java

In my spring project, I am trying set up generic classes for my controllers and service classes, with commons methods used by that classes. In my generic controller, each possible action the entities could receive (like insert, update, delete, select), are implemented with a pair of methods like that:
#RequestMapping(value="cadastra")
#PreAuthorize("hasPermission(#user, 'permission')")
public ModelAndView cadastra() throws InstantiationException, IllegalAccessException {
return new ModelAndView("privado/"+this.entity_name+"/cadastra", "command", this.entity.getClass().newInstance());
}
#RequestMapping(value="cadastra", method=RequestMethod.POST)
#ResponseBody
public String cadastra(#ModelAttribute("object") E object, BindingResult result) {
if(service.cadastra(object))
return "yes";
else
return "not";
}
and in my generic service class, this same action have related methods like that:
#PreAuthorize("hasPermission(#user, 'permissao')")
public boolean cadastra(E object) {
return dao.persist(object);
}
My question is which value I should use as atribute replacing permission and object above. The value for permssion follow this scheme:
<name_of_action>_<name_of_entity>
and the value for object is the name of each entity.
I try use the same structure I use inside the method (+this.entity_name+), but this cause an compilation error, because this annotations only accept constant arguments.
It was sugested to me use a generic class for my entities, but I can't figure out how to use that in my case.
Anyone can give a direction of how to accomplish what I want?
UPDATE
After some sugestions from other users from stackoverflow, I get to solve my problem with the ModelAtribute annotation. The final solution was this:
#RequestMapping(value="cadastra", method=RequestMethod.POST)
#ResponseBody
public String cadastra(#ModelAttribute("object") E object, BindingResult result) {
if(serv.cadastra(object))
return "yes";
else
return "not";
}
and I add this new method to my controller:
#ModelAttribute("object")
public E createCommandObject() throws InstantiationException, IllegalAccessException {
return (E) this.entityClass.newInstance();
}
Now I need only a solution for the PreAuthorize annotation, which uses the instruction #this.

So, I solve this issue with this approach:
1) For annotation PreAuthorize:
1.1) Adding a new method to my generic controller, where I return the name of the class:
public String getName() {
String expressao = entityClass.getName();
String nome_classe = new String();
StringTokenizer st = new StringTokenizer(expressao, ".");
while (st.hasMoreTokens()) {
nome_classe = st.nextToken();
}
return nome_classe;
}
1.2) Inside the annotation, I use the returned value by this method and concatenate the result with the constant string (using the notation described by the user #pgjecek in this topic):
#PreAuthorize("hasPermission(#user, 'cadastra_'+#this.this.name)")
and now it1s working perfectly.
2) For annotation ModelAtribute:
I keep using the form #ModelAttribute("object") E object, but add the method:
#ModelAttribute("object")
public E createCommandObject() throws InstantiationException, IllegalAccessException {
return (E) this.entityClass.newInstance();
}
which return a new instance of the desired object.

Related

Spring Boot: Optional mapping of request parameters to POJO

I'm trying to map request parameters of a controller method into a POJO object, but only if any of its fields are present. However, I can't seem to find a way to achieve this. I have the following POJO:
public class TimeWindowModel {
#NotNull
public Date from;
#NotNull
public Date to;
}
If none of the fields are specified, I'd like to get an empty Optional, otherwise I'd get an Optional with a validated instance of the POJO. Spring supports mapping request parameter into POJO objects by leaving them unannotated in the handler:
#GetMapping("/shop/{shopId}/slot")
public Slice<Slot> getSlots(#RequestAttribute("staff") Staff staff,
#PathVariable("shopId") Long shopId, #Valid TimeWindowModel timeWindow) {
// controller code
}
With this, Spring will map request parameters "from" and "to" to an instance of TimeWindowModel. However, I want to make this mapping optional. For POST requests you can use #RequestBody #Valid Optional<T>, which will give you an Optional<T> containing an instance of T, but only if a request body was provided, otherwise it will be empty. This makes #Valid work as expected.
When not annotated, Optional<T> doesn't appear to do anything. You always get an Optional<T> with an instance of the POJO. This is problematic when combined with #Valid because it will complain that "from" and "to" are not set.
The goal is to get either (a) an instance of the POJO where both "from" and "to" are not null or (b) nothing at all. If only one of them is specified, then #Valid should fail and report that the other is missing.
I came up with a solution with a custom HandlerMethodArgumentResolver, Jackson and Jackson Databind.
The annotation:
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
public #interface RequestParamBind {
}
The resolver:
public class RequestParamBindResolver implements HandlerMethodArgumentResolver {
private final ObjectMapper mapper;
public RequestParamBindResolver(ObjectMapper mapper) {
this.mapper = mapper.copy();
this.mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(RequestParamBind.class) != null;
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mav, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// take the first instance of each request parameter
Map<String, String> requestParameters = webRequest.getParameterMap()
.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue()[0]));
// perform the actual resolution
Object resolved = doResolveArgument(parameter, requestParameters);
// *sigh*
// see: https://stackoverflow.com/questions/18091936/spring-mvc-valid-validation-with-custom-handlermethodargumentresolver
if (parameter.hasParameterAnnotation(Valid.class)) {
String parameterName = Conventions.getVariableNameForParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, resolved, parameterName);
// DataBinder constructor unwraps Optional, so the target could be null
if (binder.getTarget() != null) {
binder.validate();
BindingResult bindingResult = binder.getBindingResult();
if (bindingResult.getErrorCount() > 0)
throw new MethodArgumentNotValidException(parameter, bindingResult);
}
}
return resolved;
}
private Object doResolveArgument(MethodParameter parameter, Map<String, String> requestParameters) {
Class<?> clazz = parameter.getParameterType();
if (clazz != Optional.class)
return mapper.convertValue(requestParameters, clazz);
// special case for Optional<T>
Type type = parameter.getGenericParameterType();
Class<?> optionalType = (Class<?>)((ParameterizedType)type).getActualTypeArguments()[0];
Object obj = mapper.convertValue(requestParameters, optionalType);
// convert back to a map to find if any fields were set
// TODO: how can we tell null from not set?
if (mapper.convertValue(obj, new TypeReference<Map<String, String>>() {})
.values().stream().anyMatch(Objects::nonNull))
return Optional.of(obj);
return Optional.empty();
}
}
Then, we register it:
#Configuration
public class WebConfig implements WebMvcConfigurer {
//...
#Override
public void addArgumentResolvers(
List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new RequestParamBindResolver(new ObjectMapper()));
}
}
Finally, we can use it like so:
#GetMapping("/shop/{shopId}/slot")
public Slice<Slot> getSlots(#RequestAttribute("staff") Staff staff,
#PathVariable("shopId") Long shopId,
#RequestParamBind #Valid Optional<TimeWindowModel> timeWindow) {
// controller code
}
Which works exactly as you'd expect.
I'm sure it's possible to accomplish this by using Spring's own DataBind classes in the resolver. However, Jackson Databind seemed like the most straight-forward solution. That said, it's not able to distinguish between fields that are set to null and fields that just not set. This is not really an issue for my use-case, but it's something that should be noted.
To implement logic (a) both not null or (b) both are nulls you need to implement custom validation.
Examples are here:
https://blog.clairvoyantsoft.com/spring-boot-creating-a-custom-annotation-for-validation-edafbf9a97a4
https://www.baeldung.com/spring-mvc-custom-validator
Generally, you create a new annotation, it's just a stub, and then you create a validator which implements ConstraintValidator where you provide your logic and then you put your new annotation to your POJO.

Varargs in thymeleaf #ModelAttribute?

I want to pass multiple variable parameters (including none) for a #ModelAttribute evaluation from html thymeleaf page to a method:
//provide 0 or X params
#ModelAttribute("url")
public String url(String... params) {
return "generatedurl";
}
The following thymeleaf statements should be possible:
th:href="#{${url()}"
th:href="#{${url('page')}}"
th:href="#{${url('page', 'sort')}}"
But it does not work. Why?
#ModelAttribute is used to bind common objects to the model.
You are returning a String generatedurl every time from your method url() annotated with #ModelAttribute. So in your Thymleaf view every time yo do ${url} you get generatedurl.
One workaround for your problem could be this
#ModelAttribute("url")
public void url(Model model) {
model.addAttribute("url","YOUR_URL");
model.addAttribute("sort","age");
}
As a workaround: add the enclosing class as a model parameter, and call the method on this param from thymeleaf:
#Controller
public class PageController {
#GetMapping
public String persons(Model model) {
model.addAttribute("util", this);
return "persons";
}
public String url(String... params) {
//evaluate...
return "generatedurl";
}
}
It's then possible to access the methods as normal:
th:href="#{${url()}"
th:href="#{${url('page')}}"
th:href="#{${url('page', 'sort')}}"
Only drawback is of course having to add the class to the model explicit.
Another choice would be to initialize the model with the parameter permutations that I need. But that's less flexible:
#ModelAttribute
public void init(Model model) {
model.addAttribute("plainUrl", ...);
model.addAttribute("pageUrl", ...);
model.addAttribute("pageSizeUrl", ...);
}

Validating request parameter before controller

I want to validate the request parameter which is just a string value. Most of the examples on the net talks about validating a domain object with custom validator. But I want to write a validator just for String value. How to achieve that?
#Controller
#RequestMapping("/base")
class MyController{
//value needs to be validated.
#RequestMapping("/sub")
public String someMethod(#RequestParam String value, BindingResult result){
if(result.hasErrors()){
return "error";
}
//do operation
return "view";
}
}
I want to use the Validator interface that is already available in Spring, not AOP or any IF conditions
Your controller cannot validate the param, because BindingResult shall follow a ModelAttribute, not a RequestParam.
So if you want to use the Spring MVC automatic validation, you should use a class containing your string as a ModelAttribute :
class MyController {
#RequestMapping("/sub")
public String someMethod(#ModelAttribute Params params, BindingResult result){
if(result.hasErrors()){
return "error";
}
//do operation with params.value
return "view";
}
public static class Params {
// add eventual JSR-303 annotations here
String value;
// getter and setter ommited for brievety
}
}
Of course, this assumes you put a validator into the WebBinder for example through an #InitBinder annotated method of your controller.
//value needs to be validated.
#RequestMapping("/sub")
public String someMethod(#RequestParam String value, BindingResult result) throws SomeException {
if (!ValidationUtils.isValid(value)) {
throw new SomeException("Some text");
}

Spring : Configure xml to make a controller return a view depending on a parameter

I have a spring MVC based application and I want to add a functionality in which some of my controllers will return the same view depending on the value of a parameter.
#RequestMapping("/someView")
public String returnView(Model model, HttpServletRequest request, String param){
if(param.equals("condition")){
return "commonView";
}
// do stuff
return "methodSpecificView";
}
Is there a way in which the first if condition can be configured in an xml? Since similar functionality needs to implemented in many controllers and I don't want to write boilerplate code an xml configuration can make things simpler.
Furthermore, if the first one is possible, can it be extended to eliminate the parameter param from request mapping method signature and put that in xml too?
You can use #RequestMapping:
#RequestMapping(value = {"/someView", "/anotherView", ...}, params = "name=condition")
public String returnCommonView(){
return "commonView";
}
In Spring 3.2 which is annotation based the below code snippet will give you an idea for your problem:
#RequestMapping("formSubmit.htm")
public String onformSubmit(#ModelAttribute("TestBean") TestBean testBean,BindingResult result, ModelMap model, HttpServletRequest request) {
String _result = null;
if (!result.hasErrors()) {
_result = performAction(request, dataStoreBean);//Method to perform action based on parameters recieved
}
if(testBean.getCondition()){
_result = "commonView";
}else{
_result = "methodSpecificView";
}
return _result;
}
TestBean//Class to hold all the required setters and getters
Explanation:
As the request from your view comes to this method the ModelAttribute reference will hold all the values from view if the condition is obtained from the view than you can directly obtain it from model attribute and return the corresponding view.
If your condition is obtained after applying certain logic than you can set the condition in the testBean and again get it to return the corresponding view.
You should consider implementing this via AOP - Around advice something like below.
#Around("#annotation(RequestMapping)") // modify the condition to include / exclude specific methods
public Object aroundAdvice(ProceedingJoinPoint joinpoint) throws Throwable {
Object args[] = joinpoint.getArgs();
String param = args[2]; // change the index as per convenience
if(param.equals("condition")){
return "commonView";
} else {
return joinpoint.proceed(); // this will execute the annotated method
}
}

Spring validation, how to have PropertyEditor generate specific error message

I'm using Spring for form input and validation. The form controller's command contains the model that's being edited. Some of the model's attributes are a custom type. For example, Person's social security number is a custom SSN type.
public class Person {
public String getName() {...}
public void setName(String name) {...}
public SSN getSocialSecurtyNumber() {...}
public void setSocialSecurtyNumber(SSN ssn) {...}
}
and wrapping Person in a Spring form edit command:
public class EditPersonCommand {
public Person getPerson() {...}
public void setPerson(Person person) {...}
}
Since Spring doesn't know how to convert text to a SSN, I register a customer editor with the form controller's binder:
public class EditPersonController extends SimpleFormController {
protected void initBinder(HttpServletRequest req, ServletRequestDataBinder binder) {
super.initBinder(req, binder);
binder.registerCustomEditor(SSN.class, "person.ssn", new SsnEditor());
}
}
and SsnEditor is just a custom java.beans.PropertyEditor that can convert text to a SSN object:
public class SsnEditor extends PropertyEditorSupport {
public String getAsText() {...} // converts SSN to text
public void setAsText(String str) {
// converts text to SSN
// throws IllegalArgumentException for invalid text
}
}
If setAsText encounters text that is invalid and can't be converted to a SSN, then it throws IllegalArgumentException (per PropertyEditor setAsText's specification). The issue I'm having is that the text to object conversion (via PropertyEditor.setAsText()) takes place before my Spring validator is called. When setAsText throws IllegalArgumentException, Spring simply displays the generic error message defined in errors.properties. What I want is a specific error message that depends on the exact reason why the entered SSN is invalid. PropertyEditor.setAsText() would determine the reason. I've tried embedded the error reason text in IllegalArgumentException's text, but Spring just treats it as a generic error.
Is there a solution to this? To repeat, what I want is the specific error message generated by the PropertyEditor to surface to the error message on the Spring form. The only alternative I can think of is to store the SSN as text in the command and perform validation in the validator. The text to SSN object conversion would take place in the form's onSubmit. This is less desirable as my form (and model) has many properties and I don't want to have to create and maintain a command that has each and every model attribute as a text field.
The above is just an example, my actual code isn't Person/SSN, so there's no need to reply with "why not store SSN as text..."
You're trying to do validation in a binder. That's not the binder's purpose. A binder is supposed to bind request parameters to your backing object, nothing more. A property editor converts Strings to objects and vice versa - it is not designed to do anything else.
In other words, you need to consider separation of concerns - you're trying to shoehorn functionality into an object that was never meant to do anything more than convert a string into an object and vice versa.
You might consider breaking up your SSN object into multiple, validateable fields that are easily bound (String objects, basic objects like Dates, etc). This way you can use a validator after binding to verify that the SSN is correct, or you can set an error directly. With a property editor, you throw an IllegalArgumentException, Spring converts it to a type mismatch error because that's what it is - the string doesn't match the type that is expected. That's all that it is. A validator, on the other hand, can do this. You can use the spring bind tag to bind to nested fields, as long as the SSN instance is populated - it must be initialized with new() first. For instance:
<spring:bind path="ssn.firstNestedField">...</spring:bind>
If you truly want to persist on this path, however, have your property editor keep a list of errors - if it is to throw an IllegalArgumentException, add it to the list and then throw the IllegalArgumentException (catch and rethrow if needed). Because you can construct your property editor in the same thread as the binding, it will be threadsafe if you simply override the property editor default behavior - you need to find the hook it uses to do binding, and override it - do the same property editor registration you're doing now (except in the same method, so that you can keep the reference to your editor) and then at the end of the binding, you can register errors by retrieving the list from your editor if you provide a public accessor. Once the list is retrieved you can process it and add your errors accordingly.
As said:
What I want is the specific error message generated by the PropertyEditor to surface to the error message on the Spring form
Behind the scenes, Spring MVC uses a BindingErrorProcessor strategy for processing missing field errors, and for translating a PropertyAccessException to a FieldError. So if you want to override default Spring MVC BindingErrorProcessor strategy, you must provide a BindingErrorProcessor strategy according to:
public class CustomBindingErrorProcessor implements DefaultBindingErrorProcessor {
public void processMissingFieldError(String missingField, BindException errors) {
super.processMissingFieldError(missingField, errors);
}
public void processPropertyAccessException(PropertyAccessException accessException, BindException errors) {
if(accessException.getCause() instanceof IllegalArgumentException)
errors.rejectValue(accessException.getPropertyChangeEvent().getPropertyName(), "<SOME_SPECIFIC_CODE_IF_YOU_WANT>", accessException.getCause().getMessage());
else
defaultSpringBindingErrorProcessor.processPropertyAccessException(accessException, errors);
}
}
In order to test, Let's do the following
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) {
binder.registerCustomEditor(SSN.class, new PropertyEditorSupport() {
public String getAsText() {
if(getValue() == null)
return null;
return ((SSN) getValue()).toString();
}
public void setAsText(String value) throws IllegalArgumentException {
if(StringUtils.isBlank(value))
return;
boolean somethingGoesWrong = true;
if(somethingGoesWrong)
throw new IllegalArgumentException("Something goes wrong!");
}
});
}
Now our Test class
public class PersonControllerTest {
private PersonController personController;
private MockHttpServletRequest request;
#BeforeMethod
public void setUp() {
personController = new PersonController();
personController.setCommandName("command");
personController.setCommandClass(Person.class);
personController.setBindingErrorProcessor(new CustomBindingErrorProcessor());
request = new MockHttpServletRequest();
request.setMethod("POST");
request.addParameter("ssn", "somethingGoesWrong");
}
#Test
public void done() {
ModelAndView mav = personController.handleRequest(request, new MockHttpServletResponse());
BindingResult bindingResult = (BindingResult) mav.getModel().get(BindingResult.MODEL_KEY_PREFIX + "command");
FieldError fieldError = bindingResult.getFieldError("ssn");
Assert.assertEquals(fieldError.getMessage(), "Something goes wrong!");
}
}
regards,
As a follow up to #Arthur Ronald's answer, this is how I ended up implementing this:
On the controller:
setBindingErrorProcessor(new CustomBindingErrorProcessor());
And then the binding error processor class:
public class CustomBindingErrorProcessor extends DefaultBindingErrorProcessor {
public void processPropertyAccessException(PropertyAccessException accessException,
BindingResult bindingResult) {
if(accessException.getCause() instanceof IllegalArgumentException){
String fieldName = accessException.getPropertyChangeEvent().getPropertyName();
String exceptionError = accessException.getCause().getMessage();
FieldError fieldError = new FieldError(fieldName,
"BINDING_ERROR",
fieldName + ": " + exceptionError);
bindingResult.addError(fieldError);
}else{
super.processPropertyAccessException(accessException, bindingResult);
}
}
}
So the processor method's signature takes a BindingResult instead of a BindException on this version.
This sounds similar to an issue I had with NumberFormatExceptions when the value for an integer property could not be bound if, say, a String was entered in the form. The error message on the form was a generic message for that exception.
The solution was to add my own message resource bundle to my application context and add my own error message for type mismatches on that property. Perhaps you can do something similar for IllegalArgumentExceptions on a specific field.
I believe you could just try to put this in your message source:
typeMismatch.person.ssn=Wrong SSN format

Categories

Resources