spring binding of list of list of objetcs - spring

i have the object of this structure
public class Cleanse {
private Contacts contact;
private List<Contacts> dnb360;
private String operation;
public String getOperation() {
return operation;
}
public void setOperation(String operation) {
this.operation = operation;
}
public Contacts getContact() {
return contact;
}
public void setContact(Contacts contact) {
this.contact = contact;
}
public List<Contacts> getDnb360() {
return dnb360;
}
public void setDnb360(List<Contacts> dnb360) {
this.dnb360 = dnb360;
}
In jsp i am getting the list of cleanse objects
can anyone tell me how to bind this list and get the submitted value in contoller

Binding in the jsp is not that difficult. If you're using core jstl tags (which you should), you only have to iterate over the lists entries and do with them what ever you want:
<c:forEach items="${dnb360}" var="s">
<b>${s}</b>
</c:forEach>
Since you're talking about 'submitting', I guess you#re trying to set up a form here, which makes it even easier (use spring's form tag here) <%# taglib uri="http://www.springframework.org/tags/form" prefix="form" %>:
<form:checkboxes items="${dnb360}" path="dnb360" />
For retrieving such a bound value and convert back to your object, I'd suggest using the #InitBinder annotation. You annotate a method with that and define how to bind a String value back to your object when retrieving this String through a method which has #ModelAttribute("dnb360") dnb360,...
#InitBinder
public void initBinder(WebDataBinder binder) {
//custom editor to bind Institution by name
binder.registerCustomEditor(Contacts.class, new PropertyEditorSupport() {
#Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(xxx); //most likely obtained through database call
}
});
}
If you need more help, please feel free to ask.

Related

Spring form validation with conversion

In a form I take a comma separated list of integers and convert them to an ArrayList<Integer>. This works fine. My form-backing object is:
public class FormDto {
#NotNull
private ArrayList<Integer> nList;
// getters and setters
}
In the form I have:
<input type="text" class="form-control" id="nList" th:field="*{nList}" required="required"/>
Apparently spring automatically converts the string into an ArrayList. Just in case I also created a Converter:
public class CSVStringToListConverter implements Converter<String, ArrayList<Integer>>{
#Override
public ArrayList<Integer> convert(String arg0) {
ArrayList<Integer> list = new ArrayList<Integer>();
for (String s : arg0.split(",")) {
list.add(Integer.parseInt(s));
}
return list;
}
}
which I add in
#Configuration
#EnableWebMvc
public class MvcConfig extends WebMvcConfigurerAdapter implements ApplicationContextAware {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new CSVStringToListConverter());
}
}
Is this all I have to do in order to make converters work? As I wrote, the conversion also works without this converter, so I am unsure whether my converter is actually used or whether it still uses the built-in converter.
My main question however is how to validate the input String (not the ArrayList)? For form fields without conversion I just do that in the controller with:
#PostMapping("/submitForm")
public String formSubmit(#ModelAttribute #Valid SortingExplicitDto sortingExplicitDto, BindingResult result, Model model) {
if(result.hasErrors()) {
model.addAttribute("formDto", new formDto());
return "submitForm";
}
}
Is it possible to validate the input String in the same way, or do I have to do that with Javascript or in the converter?
addition
After a few more tests, I noticed that it does use my custom converter, which does answer my first question. Still I am wondering how to deal with the validation. If I enter some random string which are not integers separated by comma, I receive an exception:
Failed to convert property value of type [java.lang.String] to required type [java.util.ArrayList] for property nList; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [#javax.validation.constraints.NotNull java.util.ArrayList<java.lang.Integer>] for value ,2,3; nested exception is java.lang.NumberFormatException: For input string:
Also I can do all my validation inside the CSVStringToListConverter and throw an exception if validation fails. I believe I should then catch the exception in the controller and somehow display a nice human readable message in the form. In the html, I display errors (including the one above) with:
<span id="errornList" class="alert alert-danger" th:if="${#fields.hasErrors('nList')}" th:errors="*{nList}">NList error</span>
How do I catch the exception in the controller and put a nice error message there?
Your converter can be this way:
#Component
public class CSVStringToListConverter implements Converter<String, List<Integer>> {
#Autowired
private Errors errors;
#Override
public List<Integer> convert(String arg0) {
List<Integer> list = new ArrayList<Integer>();
for (String s : arg0.split(",")) {
try {
list.add(Integer.parseInt(s));
} catch (NumberFormatException e) {
list = Collections.emptyList();
errors.rejectValue("fieldName", "fieldName.invalid", "Invalid integer in list");
}
}
return list;
}}
Register the Converter in a Configuration class
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new CSVStringToListConverter());
}
}
I only Injected the "Errors" class in order to display "nice human readable message in the form" as you wish to have.
Use "org.springframework.validation.BindException" implementation of Errors class, since Errors is an interface, preferably in a configuration class and then the injection should work.

How to validate Spring MVC #PathVariable values?

For a simple RESTful JSON api implemented in Spring MVC, can I use Bean Validation (JSR-303) to validate the path variables passed into the handler method?
For example:
#RequestMapping(value = "/number/{customerNumber}")
#ResponseBody
public ResponseObject searchByNumber(#PathVariable("customerNumber") String customerNumber) {
...
}
Here, I need to validate the customerNumber variables's length using Bean validation. Is this possible with Spring MVC v3.x.x? If not, what's the best approach for this type of validations?
Thanks.
Spring does not support #javax.validation.Valid on #PathVariable annotated parameters in handler methods. There was an Improvement request, but it is still unresolved.
Your best bet is to just do your custom validation in the handler method body or consider using org.springframework.validation.annotation.Validated as suggested in other answers.
You can use like this:
use org.springframework.validation.annotation.Validated to valid RequestParam or PathVariable.
*
* Variant of JSR-303's {#link javax.validation.Valid}, supporting the
* specification of validation groups. Designed for convenient use with
* Spring's JSR-303 support but not JSR-303 specific.
*
step.1 init ValidationConfig
#Configuration
public class ValidationConfig {
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
return processor;
}
}
step.2 Add #Validated to your controller handler class, Like:
#RequestMapping(value = "poo/foo")
#Validated
public class FooController {
...
}
step.3 Add validators to your handler method:
#RequestMapping(value = "{id}", method = RequestMethod.DELETE)
public ResponseEntity<Foo> delete(
#PathVariable("id") #Size(min = 1) #CustomerValidator int id) throws RestException {
// do something
return new ResponseEntity(HttpStatus.OK);
}
final step. Add exception resolver to your context:
#Component
public class BindExceptionResolver implements HandlerExceptionResolver {
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if (ex.getClass().equals(BindException.class)) {
BindException exception = (BindException) ex;
List<FieldError> fieldErrors = exception.getFieldErrors();
return new ModelAndView(new MappingJackson2JsonView(), buildErrorModel(request, response, fieldErrors));
}
}
}
The solution is simple:
#GetMapping(value = {"/", "/{hash:[a-fA-F0-9]{40}}"})
public String request(#PathVariable(value = "hash", required = false) String historyHash)
{
// Accepted requests: either "/" or "/{40 character long hash}"
}
And yes, PathVariables are ment to be validated, like any user input.
Instead of using #PathVariable, you can take advantage of Spring MVC ability to map path variables into a bean:
#RestController
#RequestMapping("/user")
public class UserController {
#GetMapping("/{id}")
public void get(#Valid GetDto dto) {
// dto.getId() is the path variable
}
}
And the bean contains the actual validation rules:
#Data
public class GetDto {
#Min(1) #Max(99)
private long id;
}
Make sure that your path variables ({id}) correspond to the bean fields (id);
#PathVariable is not meant to be validated in order to send back a readable message to the user. As principle a pathVariable should never be invalid. If a pathVariable is invalid the reason can be:
a bug generated a bad url (an href in jsp for example). No #Valid is
needed and no message is needed, just fix the code;
"the user" is manipulating the url.
Again, no #Valid is needed, no meaningful message to the user should
be given.
In both cases just leave an exception bubble up until it is catched by
the usual Spring ExceptionHandlers in order to generate a nice
error page or a meaningful json response indicating the error. In
order to get this result you can do some validation using custom editors.
Create a CustomerNumber class, possibly as immutable (implementing a CharSequence is not needed but allows you to use it basically as if it were a String)
public class CustomerNumber implements CharSequence {
private String customerNumber;
public CustomerNumber(String customerNumber) {
this.customerNumber = customerNumber;
}
#Override
public String toString() {
return customerNumber == null ? null : customerNumber.toString();
}
#Override
public int length() {
return customerNumber.length();
}
#Override
public char charAt(int index) {
return customerNumber.charAt(index);
}
#Override
public CharSequence subSequence(int start, int end) {
return customerNumber.subSequence(start, end);
}
#Override
public boolean equals(Object obj) {
return customerNumber.equals(obj);
}
#Override
public int hashCode() {
return customerNumber.hashCode();
}
}
Create an editor implementing your validation logic (in this case no whitespaces and fixed length, just as an example)
public class CustomerNumberEditor extends PropertyEditorSupport {
#Override
public void setAsText(String text) throws IllegalArgumentException {
if (StringUtils.hasText(text) && !StringUtils.containsWhitespace(text) && text.length() == YOUR_LENGTH) {
setValue(new CustomerNumber(text));
} else {
throw new IllegalArgumentException();
// you could also subclass and throw IllegalArgumentException
// in order to manage a more detailed error message
}
}
#Override
public String getAsText() {
return ((CustomerNumber) this.getValue()).toString();
}
}
Register the editor in the Controller
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(CustomerNumber.class, new CustomerNumberEditor());
// ... other editors
}
Change the signature of your controller method accepting CustomerNumber instead of String (whatever your ResponseObject is ...)
#RequestMapping(value = "/number/{customerNumber}")
#ResponseBody
public ResponseObject searchByNumber(#PathVariable("customerNumber") CustomerNumber customerNumber) {
...
}
You can create the answer you want by using the fields in the ConstraintViolationException with the following method;
#ExceptionHandler(ConstraintViolationException.class)
protected ResponseEntity<Object> handlePathVariableError(final ConstraintViolationException exception) {
log.error(exception.getMessage(), exception);
final List<SisSubError> subErrors = new ArrayList<>();
exception.getConstraintViolations().forEach(constraintViolation -> subErrors.add(generateSubError(constraintViolation)));
final SisError error = generateErrorWithSubErrors(VALIDATION_ERROR, HttpStatus.BAD_REQUEST, subErrors);
return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}
You need to added an #Validated annotation to Controller class and any validation annotation before path variable field
Path variable may not be linked with any bean in your system. What do you want to annotate with JSR-303 annotations?
To validate path variable you should use this approach Problem validating #PathVariable url on spring 3 mvc
Actually there is a very simple solution to this. Add or override the same controller method with its request mapping not having the placeholder for the path variable and throw ResponseStatusException from it. Code given below
#RequestMapping(value = "/number")
#ResponseBody
public ResponseObject searchByNumber() {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,"customer number missing")
}

unable to set radio button in struts2

I am not able to get the value of radio button selected in a page
I have a JSP page as
<body>
<s:form action="/YYY" id="frmPersonalPage" name="frmPersonalPage" >
<s:radio name ="radio" list="skillMasterData"></s:radio>
</s:form>
</body>
This renders properly . In my struts.xml I have
<action name="YYY" class="com.tdi.atom.actions.CCC" method="showEditSkillMasterPage">
<result name ="success">/jsp/modules/skillmap/createskillmaster.jsp</result>
</action>
In my action class I have this
public class CCC extends BaseActionSupport {
private ArrayList skillMasterData;
public String radio;
private ArrayList l1;
private ArrayList l2;
private ArrayList l3;
public ArrayList getSkillMasterData() {
return skillMasterData;
}
public void setSkillMasterData(ArrayList skillMasterData) {
this.skillMasterData = skillMasterData;
}
public String showEditSkillMasterPage()
{ log.info("at showEditSkillMasterPage");
System.out.println("radio buttoneee : "+getRadio());//this is null
setEditType("EDIT");
return SUCCESS;
}
public String showListSkillMasterPage()
{
SkillMasterDB pddb =new SkillMasterDB();
JdbcHelper helper;
l1 =new ArrayList();
l2=new ArrayList();
l3=new ArrayList();
l1.add("asda");
l1.add("rqwrq");
l2.add("!####");
l2.add("9087907");
l3.add("./,/");
l3.add("[][][]");
skdto.add(l1);
skdto.add(l2);
skdto.add(l3);
setSkillMasterData(skdto);
return SUCCESS;
}
public String getRadio() {
return radio;
}
public void setRadio(String radio) {
this.radio = radio;
}
}
In BaseActionSupport class I have
public class BaseActionSupport extends ActionSupport implements SessionAware {
private Map userSession;
public UserDTO user;
public UserDTO getUser() {
return mgr.getUser();
}
public boolean isAdmin() {
return mgr.isUserADMIN();
}
public void setSession(Map session) {
userSession = session; mgr = new SecurityManager(userSession);
}
}
I just can't figure out why such simple code is not working. Else where very similar code works fine.
There are some strange things in your code.
Assuming CCC and YYY are obfuscated names created only for posting them here (otherwise, you should use CamelCase with first letter capitalized for class names, like MyAction),
you should respect JavaBeans conventions, then userSession should become session, because the accessors methods (getters and setters) should always respect the name of the private variable);
skdto variable is not initialized nor defined in your showListSkillMasterPage method;
I'm not sure if using ArrayList<ArrayList<String>> for radio button is good. You could try with a simple ArrayList<String>, like:
public String showListSkillMasterPage() {
SkillMasterDB pddb =new SkillMasterDB();
JdbcHelper helper;
List<String> skdto = new ArrayList<String>();
skdto.add("asda");
skdto.add("rqwrq");
skdto.add("!####");
skdto.add("9087907");
skdto.add("./,/");
skdto.add("[][][]");
setSkillMasterData(skdto);
return SUCCESS;
}
Finally, you can test with Firebug's Net module what is going out of your page, check the radio parameter value now and after the edit to see what is going wrong now and how it will be going (hopefully) right later...

Spring 3 Custom Editor field replacement

Having my ValueObject
UserVO {
long id;
String username;
}
I created custom editor for parsing this object from string id#username
public class UserVOEditor extends PropertyEditorSupport {
#Override
public void setAsText(String text) throws IllegalArgumentException {
Preconditions.checkArgument(text != null,"Null argument supplied when parsing UserVO");
String[] txtArray = text.split("\\#");
Preconditions.checkArgument(txtArray.length == 2, "Error parsing UserVO. Expected: id#username");
long parsedId = Long.valueOf(txtArray[0]);
String username = txtArray[1];
UserVO uvo = new UserVO();
uvo.setUsername(username);
uvo.setId(parsedId);
this.setValue(uvo);
}
#Override
public String getAsText() {
UserVO uvo = (UserVO) getValue();
return uvo.getId()+'#'+uvo.getUsername();
}
in my controller i register
#InitBinder
public void initBinder(ServletRequestDataBinder binder) {
binder.registerCustomEditor(UserVO.class, new UserVOEditor());
}
having in my model object ModelVO
ModelVO {
Set<UserVO> users = new HashSet<UserVO>();
}
after custom editor is invoked all you can see after form submission is
ModelVO {
Set<String> users (linkedHashSet)
}
so when trying to iterate
for(UserVO uvo : myModel.getUser()){ .. }
Im having classCastException .. cannot cast 1234#username (String) to UserVO ..
HOW THIS MAGIC IS POSSIBLE ?
It is not magic, it is because of Generics will be only proved at compile time. So you can put every thing in a Set at runtime, no one will check if you put the correct type in the Set.
What you can try, to make spring a bit more clever, is to put the ModelVO in your command object.
<form:form action="whatEver" method="GET" modelAttribute="modelVO">
#RequestMapping(method = RequestMethod.GET)
public ModelAndView whatEver(#Valid ModelVO modelVO){
...
}

Spring: escaping input when binding to command

How do you handle the case where you want user input from a form to be htmlEscape'd when
you are binding to a command object?
I want this to sanitize input data automatically in order to avoid running through all fields in command object.
thanks.
If you are using a FormController you can register a new property editor by overriding the initBinder(HttpServletReques, ServletRequestDataBinder) method. This property editor can escape the html, javascript and sql injection.
If you are using a property editor the values from the request object will be processed by the editor before assigning to the command object.
When we register a editor we have to specify the type of the item whose values has to be processed by the editor.
Sorry, now I don't the syntax of the method. But I'm sure this is how we have achieved this.
EDITED
I think the following syntax can work
In your controller override the following method as shown
#Override
protected void initBinder(HttpServletRequest request,
ServletRequestDataBinder binder) throws Exception {
super.initBinder(request, binder);
binder.registerCustomEditor(String.class,
new StringEscapeEditor(true, true, false));
}
Then create the following property editor
public class StringEscapeEditor extends PropertyEditorSupport {
private boolean escapeHTML;
private boolean escapeJavaScript;
private boolean escapeSQL;
public StringEscapeEditor() {
super();
}
public StringEscapeEditor(boolean escapeHTML, boolean escapeJavaScript,
boolean escapeSQL) {
super();
this.escapeHTML = escapeHTML;
this.escapeJavaScript = escapeJavaScript;
this.escapeSQL = escapeSQL;
}
public void setAsText(String text) {
if (text == null) {
setValue(null);
} else {
String value = text;
if (escapeHTML) {
value = StringEscapeUtils.escapeHtml(value);
}
if (escapeJavaScript) {
value = StringEscapeUtils.escapeJavaScript(value);
}
if (escapeSQL) {
value = StringEscapeUtils.escapeSql(value);
}
setValue(value);
}
}
public String getAsText() {
Object value = getValue();
return (value != null ? value.toString() : "");
}
}
Hopes this helps you
You can use #Valid and #SafeHtml from hibernate validator. See details at https://stackoverflow.com/a/40644276/548473

Resources