In a spring mvc web application using hibernate in eclipse and tomcat server, I changed a couple of text fields to drop down lists in a jsp, so that a person's gender and race can each be selected from its own drop down menu. I was careful to change other levels of the application, including setting up joined tables for gender and race in the underlying database, and changing code in the model and repository levels. The application compiles, and the jsp loads with the correct selected values for the selected person in each dropdown list, but clicking the submit/update button causes a BindingResult.hasErrors() problem which does not help me localize the cause of the problem.
Can someone help me find the cause of the failure to process the update?
Here is the processUpdatePatientForm() method that is called in the controller class. Note that it triggers the System.out.println() which shows that BindingResult.hasErrors() and returns the jsp:
#RequestMapping(value = "/patients/{patientId}/edit", method = RequestMethod.PUT)
public String processUpdatePatientForm(#Valid Patient patient, BindingResult result, SessionStatus status) {
if (result.hasErrors()) {
System.out.println(":::::::::::::::: in PatientController.processUpdatePatientForm() result.hasErrors() ");
List<ObjectError> errors = result.getAllErrors();
for(int i=0;i<result.getErrorCount();i++){System.out.println("]]]]]]] error "+i+" is: "+errors.get(i).toString());}
return "patients/createOrUpdatePatientForm";}
else {
this.clinicService.savePatient(patient);
status.setComplete();
return "redirect:/patients?patientID=" + patient.getId();
}
}
When the jsp is returned, the following error messages are included:
//This is printed out in my jsp below the Sex drop down list:
Failed to convert property value of type java.lang.String to required type org.springframework.samples.knowledgemanager.model.Gender for property sex; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [org.springframework.samples.knowledgemanager.model.Gender] for property sex: no matching editors or conversion strategy found
//This is printed out in my jsp below the Race drop down list:
Failed to convert property value of type java.lang.String to required type org.springframework.samples.knowledgemanager.model.Race for property race; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [org.springframework.samples.knowledgemanager.model.Race] for property race: no matching editors or conversion strategy found
The following is all that is printed in the eclipse console:
Hibernate: select gender0_.id as id1_2_, gender0_.name as name2_2_ from gender gender0_ order by gender0_.name
Hibernate: select race0_.id as id1_7_, race0_.name as name2_7_ from race race0_ order by race0_.name
:::::::::::::::: in PatientController.processUpdatePatientForm() result.hasErrors()
]]]]]]] error 0 is: Field error in object 'patient' on field 'race': rejected value [Hispanic]; codes [typeMismatch.patient.race,typeMismatch.race,typeMismatch.org.springframework.samples.knowledgemanager.model.Race,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [patient.race,race]; arguments []; default message [race]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'org.springframework.samples.knowledgemanager.model.Race' for property 'race'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [org.springframework.samples.knowledgemanager.model.Race] for property 'race': no matching editors or conversion strategy found]
]]]]]]] error 1 is: Field error in object 'patient' on field 'sex': rejected value [Male]; codes [typeMismatch.patient.sex,typeMismatch.sex,typeMismatch.org.springframework.samples.knowledgemanager.model.Gender,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [patient.sex,sex]; arguments []; default message [sex]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'org.springframework.samples.knowledgemanager.model.Gender' for property 'sex'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [org.springframework.samples.knowledgemanager.model.Gender] for property 'sex': no matching editors or conversion strategy found]
Note that the values [Hispanic] and [Male] are shown in the error message as triggering the error. The problem might be that the name property of Gender and Race is being passed to Spring MVC, when the id property should be passed instead. But how do I fix this in the code?
Can someone help me get to the bottom of this? The first step would be how can I get a more useful error message which locates the location in my code where the problem is being triggered.
EDIT:
Per Sotirios's request, the following is my form in the jsp:
<form:form modelAttribute="patient" method="${method}" class="form-horizontal" id="add-patient-form">
<petclinic:inputField label="First Name" name="firstName"/>
<petclinic:inputField label="Middle Initial" name="middleInitial"/>
<petclinic:inputField label="Last Name" name="lastName"/>
<div class="control-group">
<petclinic:selectField label="Sex" name="sex" names="${genders}" size="5"/>
</div>
<petclinic:inputField label="Date of Birth" name="dateOfBirth"/>
<div class="control-group">
<petclinic:selectField label="Race" name="race" names="${races}" size="5"/>
</div>
<div class="form-actions">
<c:choose>
<c:when test="${patient['new']}">
<button type="submit">Add Patient</button>
</c:when>
<c:otherwise>
<button type="submit">Update Patient</button>
</c:otherwise>
</c:choose>
</div>
</form:form>
And the Patient.java class is:
#Entity
#Table(name = "patients")
public class Patient extends BaseEntity {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "patient", fetch=FetchType.EAGER)
private Set<Document> documents;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "patient", fetch=FetchType.EAGER)
private Set<Address> addresses;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "patient", fetch=FetchType.EAGER)
private Set<PhoneNumber> phonenumbers;
#Column(name = "first_name")
#NotEmpty
protected String firstName;
#Column(name = "middle_initial")
protected String middleInitial;
#Column(name = "last_name")
#NotEmpty
protected String lastName;
#ManyToOne
#JoinColumn(name = "sex_id")
protected Gender sex;
#Column(name = "date_of_birth")
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
#DateTimeFormat(pattern = "yyyy/MM/dd")
protected DateTime dateOfBirth;
#ManyToOne
#JoinColumn(name = "race_id")
protected Race race;
////////////// Document methods
protected void setDocumentsInternal(Set<Document> documents) {this.documents = documents;}
public Set<Document> getFaxes() {
Set<Document> faxes = new HashSet<Document>();
for (Document doc : getDocumentsInternal()) {if (doc.getType().getName().equals("ScannedFaxes")) {faxes.add(doc);}}
return faxes;
}
public Set<Document> getForms() {
Set<Document> forms = new HashSet<Document>();
for (Document doc : getDocumentsInternal()) {if (doc.getType().getName().equals("ScannedPatientForms")) {forms.add(doc);}}
return forms;
}
protected Set<Document> getDocumentsInternal() {
if (this.documents == null) {this.documents = new HashSet<Document>();}
return this.documents;
}
public List<Document> getDocuments() {
List<Document> sortedDocuments = new ArrayList<Document>(getDocumentsInternal());
PropertyComparator.sort(sortedDocuments, new MutableSortDefinition("name", true, true));
return Collections.unmodifiableList(sortedDocuments);
}
public void addDocument(Document doc) {
getDocumentsInternal().add(doc);
doc.setPatient(this);
}
public Document getDocument(String name) {return getDocument(name, false);}
/** Return the Document with the given name, or null if none found for this Patient.
* #param name to test
* #return true if document name is already in use
*/
public Document getDocument(String name, boolean ignoreNew) {
name = name.toLowerCase();
for (Document doc : getDocumentsInternal()) {
if (!ignoreNew || !doc.isNew()) {
String compName = doc.getName();
compName = compName.toLowerCase();
if (compName.equals(name)) {
return doc;
}
}
}
return null;
}
//////////// Address methods
protected void setAddressesInternal(Set<Address> addresses) {this.addresses = addresses;}
protected Set<Address> getAddressesInternal() {
if (this.addresses == null) {this.addresses = new HashSet<Address>();}
return this.addresses;
}
public List<Address> getAddresses() {
List<Address> sortedAddresses = new ArrayList<Address>(getAddressesInternal());
PropertyComparator.sort(sortedAddresses, new MutableSortDefinition("address", true, true));
return Collections.unmodifiableList(sortedAddresses);
}
public void addAddress(Address addr) {
getAddressesInternal().add(addr);
addr.setPatient(this);
}
public Address getAddress(String address) {return getAddress(address, false);}
/** Return the Address with the given name, or null if none found for this Patient.
* #param name to test
* #return true if document name is already in use
*/
public Address getAddress(String addr, boolean ignoreNew) {
addr = addr.toLowerCase();
for (Address address1 : getAddressesInternal()) {
if (!ignoreNew || !address1.isNew()) {
String compName = address1.getAddress();
compName = compName.toLowerCase();
if (compName.equals(addr)) {
return address1;
}
}
}
return null;
}
//////////// PhoneNumber methods
protected void setPhoneNumbersInternal(Set<PhoneNumber> phonenumbers) {this.phonenumbers = phonenumbers;}
protected Set<PhoneNumber> getPhoneNumbersInternal() {
if (this.phonenumbers == null) {this.phonenumbers = new HashSet<PhoneNumber>();}
return this.phonenumbers;
}
public List<PhoneNumber> getPhoneNumbers() {
List<PhoneNumber> sortedPhoneNumbers = new ArrayList<PhoneNumber>(getPhoneNumbersInternal());
PropertyComparator.sort(sortedPhoneNumbers, new MutableSortDefinition("phonenumber", true, true));
return Collections.unmodifiableList(sortedPhoneNumbers);
}
public void addPhoneNumber(PhoneNumber pn) {
getPhoneNumbersInternal().add(pn);
pn.setPatient(this);
}
public PhoneNumber getPhoneNumber(String pn) {return getPhoneNumber(pn, false);}
/** Return the PhoneNumber with the given name, or null if none found for this Patient.
* #param name to test
* #return true if phone number is already in use
*/
public PhoneNumber getPhoneNumber(String pn, boolean ignoreNew) {
pn = pn.toLowerCase();
for (PhoneNumber number : getPhoneNumbersInternal()) {
if (!ignoreNew || !number.isNew()) {
String compName = number.getPhonenumber();
compName = compName.toLowerCase();
if (compName.equals(pn)) {
return number;
}
}
}
return null;
}
public String getFirstName(){return this.firstName;}
public void setFirstName(String firstName){this.firstName = firstName;}
public String getMiddleInitial() {return this.middleInitial;}
public void setMiddleInitial(String middleinitial) {this.middleInitial = middleinitial;}
public String getLastName() {return this.lastName;}
public void setLastName(String lastName) {this.lastName = lastName;}
public Gender getSex() {return this.sex;}
public void setSex(Gender sex) {this.sex = sex;}
public void setDateOfBirth(DateTime birthDate){this.dateOfBirth = birthDate;}
public DateTime getDateOfBirth(){return this.dateOfBirth;}
public Race getRace() {return this.race;}
public void setRace(Race race) {this.race = race;}
#Override
public String toString() {
return new ToStringCreator(this)
.append("id", this.getId())
.append("new", this.isNew())
.append("lastName", this.getLastName())
.append("firstName", this.getFirstName())
.append("middleinitial", this.getMiddleInitial())
.append("dateofbirth", this.dateOfBirth)
.toString();
}
}
SECOND EDIT:
Per Alexey's comment, the following is the method in the controller class which has always had the #InitBinder annotation. It is identical to a method in the controller of a similar module which works:
#InitBinder
public void setAllowedFields(WebDataBinder dataBinder) {dataBinder.setDisallowedFields("id");}
THIRD EDIT:
PatientController.java:
#Controller
#SessionAttributes(types = Patient.class)
public class PatientController {
private final ClinicService clinicService;
#Autowired
public PatientController(ClinicService clinicService) {this.clinicService = clinicService;}
#ModelAttribute("genders")
public Collection<Gender> populateGenders() {return this.clinicService.findGenders();}
#ModelAttribute("races")
public Collection<Race> populateRaces() {return this.clinicService.findRaces();}
#InitBinder
public void setAllowedFields(WebDataBinder dataBinder) {dataBinder.setDisallowedFields("id");}
#RequestMapping(value = "/patients/new", method = RequestMethod.GET)
public String initCreationForm(Map<String, Object> model) {
Patient patient = new Patient();
model.put("patient", patient);
return "patients/createOrUpdatePatientForm";
}
#RequestMapping(value = "/patients/new", method = RequestMethod.POST)
public String processCreationForm(#Valid Patient patient, BindingResult result, SessionStatus status) {
if (result.hasErrors()) {return "patients/createOrUpdatePatientForm";}
else {
this.clinicService.savePatient(patient);
status.setComplete();
return "redirect:/patients?patientID=" + patient.getId();
}
}
#RequestMapping(value = "/patients", method = RequestMethod.GET)
public String processFindForm(#RequestParam("patientID") String patientId, Patient patient, BindingResult result, Map<String, Object> model) {
Collection<Patient> results = this.clinicService.findPatientByLastName("");
model.put("selections", results);
int patntId = Integer.parseInt(patientId);
Patient sel_patient = this.clinicService.findPatientById(patntId);//I added this
model.put("sel_patient",sel_patient);
return "patients/patientsList";
}
#RequestMapping(value = "/patients/{patientId}/edit", method = RequestMethod.GET)
public String initUpdatePatientForm(#PathVariable("patientId") int patientId, Model model) {
Patient patient = this.clinicService.findPatientById(patientId);
model.addAttribute(patient);
return "patients/createOrUpdatePatientForm";
}
#RequestMapping(value = "/patients/{patientId}/edit", method = RequestMethod.PUT)
public String processUpdatePatientForm(#Valid Patient patient, BindingResult result, SessionStatus status) {
if (result.hasErrors()) {
System.out.println(":::::::::::::::: in PatientController.processUpdatePatientForm() result.hasErrors() ");
List<ObjectError> errors = result.getAllErrors();
for(int i=0;i<result.getErrorCount();i++){System.out.println("]]]]]]] error "+i+" is: "+errors.get(i).toString());}
return "patients/createOrUpdatePatientForm";}
else {
this.clinicService.savePatient(patient);
status.setComplete();
return "redirect:/patients?patientID=" + patient.getId();
}
}
}
FOURTH EDIT:
Gender.java
#Entity
#Table(name = "gender")
public class Gender extends NamedEntity {}
NamedEntity.java:
#MappedSuperclass
public class NamedEntity extends BaseEntity {
#Column(name = "name")
private String name;
public void setName(String name) {this.name = name;}
public String getName() {return this.name;}
#Override
public String toString() {return this.getName();}
}
BaseEntity.java:
#MappedSuperclass
public class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
public void setId(Integer id) {this.id = id;}
public Integer getId() {return id;}
public boolean isNew() {return (this.id == null);}
}
You need to add a converter or a proper editor. I prefer the first one. Refer to section 6.5. on this page for the details.
Your converter would have to get the Entity with the given name from the database and return it. The code would be something like this:
class StringToGender implements Converter<String, Gender> {
#Autowired
private GenderRepository repository;
public Gender convert(String name) {
return repository.getGenderByName(name);
}
}
And in your application context xml (if you use xml):
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="org.example.StringToGender"/>
</set>
</property>
Related
I am developing an application in Vaadin where I have a view for Suppliers.
I am getting the error Internal error Please notify the administrator. Take note of any unsaved data, and click here or press ESC to continue. due to the calling of the method categoriesToString in my View.
View
public SupplierView(CrmService service) {
this.service = service;
addClassName("supplier-view");
setSizeFull();
configureGrid();
form = new SupplierForm(service.findAllCategories(null));
form.setWidth("25em");
form.addListener(SupplierForm.SaveEvent.class, this::saveSupplier);
form.addListener(SupplierForm.DeleteEvent.class, this::deleteSupplier);
form.addListener(SupplierForm.CloseEvent.class, e -> closeEditor());
FlexLayout content = new FlexLayout(grid, form);
content.setFlexGrow(2, grid);
content.setFlexGrow(1, form);
content.setFlexShrink(0, form);
content.addClassNames("content", "gap-m");
content.setSizeFull();
add(getToolbar(), content);
updateList();
closeEditor();
grid.asSingleSelect()
.addValueChangeListener(event -> editSupplier(event.getValue()));
}
private void configureGrid() {
grid.addClassNames("supplier-grid");
grid.setSizeFull();
grid.setColumns("name", "address");
grid.addColumn(supplier -> supplier.categoriesToString())
.setHeader("Categories");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
}
}
The said method belongs to the class Supplier that can be seen below. This class has a Set of Category that I am trying to go trough in order to return all categories present in it.
Supplier
#Entity
public class Supplier extends AbstractEntity {
#NotEmpty
private String name;
#NotEmpty
private String address;
#OneToMany(mappedBy = "supplier")
private Set<Category> categories = new LinkedHashSet<>();
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return this.address;
}
public void setAddress(String address) {
this.address = address;
}
public Set<Category> getCategories() {
return categories;
}
public void setCategories(Set<Category> categories) {
this.categories = categories;
}
public String categoriesToString(){
String allCategories = "";
for (Category c : this.categories)
{
allCategories += c.getDescription();
}
return allCategories;
}
}
In order to do some testing I tried to make the method just return "test" and in that scenario the word was indeed added to "Categories" column and no error was shown.
Category class
#Entity
public class Category extends AbstractEntity{
#NotBlank
private String description;
#ManyToOne
#JoinColumn(name = "supplier_id")
#JsonIgnoreProperties({"categories"})
private Supplier supplier;
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description = description;
}
Why is this happening? Am I iterating through the set in a incorrect way?
You have not specified the fetch mode for your Entity, so it is defaulting to Lazy mode. Your current logic will only work with Eager mode (that would fetch the "categories" one-to-many relationship), but this is bound to create performance issues down the road as your data grows.
I wouldn't use logic inside my entities either (your categoriesToString method), but that is a different topic altogether.
You can either specify Eager loading on your entity as:
#OneToMany(fetch = FetchType.EAGER, mappedBy = "supplier")
private Set<Category> categories = new LinkedHashSet<>();
Or perform an additional query to fetch the related categories. Or even better, create a POJO or Spring Data Projection to fetch/map the appropriate data to. You can check this link for more info on that matter.
I'm having UnexpectedRollBack exception on a simple merge method with hibernate.
I'm using JSP and controller structure.
it should be a simple update, but it returns an exception.
I have this:
User.java
public class user{
private Integer id;
String name;
String active;
}
#Id
#Column(name="ID", unique = true, nullable = false, etc...)
public Integer getId(){return this.id}
#Column(name = "NAME", nullable = true, length = 9)
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
#Column(name = "ACTIVE", nullable = true, length = 9)
public String getActive() {
return this.active;
}
public void setActive(String active) {
this.active= active;
}
Controller:
Public class UpdateUserController{
//Here's the main issue
User userToUpdate = findUserById("1");
//user properly recovered from the DB
userToUpdate.setActive("N");
//UserService is the class that contains the merging method
userService.merge(userToUpdate);
System.out.println(userToUpdate.getActive());
}
service class:
#Transactional(propagation = Propagation.REQUIRED)
public user merge(User detachedInstance) {
try {
User result = entityManager.merge(detachedInstance);
return result;
} catch (RuntimeException re) {
throw re;
}
}
It's a simple update of a String, only one cel in a row, this shouldn't return an exception but it does.
I've seen on some posts that the problem may be caused by the method try to update an element that is currently null, that may cause hibernate returns RollBackException by default.
I've checked and there is nothing null at the moment of the merge.
Thank you for the help.
RESOLVED:
The element name in data base was varchar (5)
On my code was trying to update a name with 7 char long, this caused the RollbackException.
From controller I am sending BinderPlaceOrder object like this
model.addAttribute("addNewBinderPlaceOrder", new BinderPlaceOrder());
My Thymeleaf page is,
<form class="addBinderPlaceOrderForm" role="form" action="#" th:action="#{/binderPlaceOrder/new-binderPlaceOrder}" th:object="${addNewBinderPlaceOrder}" method="post">
<div class="form-group">
<label>Book</label>
<select class="form-control" th:field="*{binderOrderItemDetails.book}">
<option th:if="${book} == null" value=" " >Select Book</option>
<option th:each="book : ${allBook}"
th:value="${book.id}"
th:text="${book.name}">
</option>
</select>
</div>
<form>
Here is the controller,
#Controller
#RequestMapping("/binderPlaceOrder")
public class BinderPlaceOrderController{
#Autowired
BinderPlaceOrderService binderPlaceOrderService;
#Autowired
BookService bookService;
#RequestMapping(value="/new-binderPlaceOrder", method = RequestMethod.GET)
public String newBinderPlaceOrder(Model model){
model.addAttribute("addNewBinderPlaceOrder", new BinderPlaceOrder());
model.addAttribute("allBook", bookService.getAllBooks();
model.addAttribute("addNewBinderOrderItemDetails", new BinderOrderItemDetails());
return "user/binderPlaceOrder/new";
}
}
During run time I get the below error
org.springframework.beans.NotReadablePropertyException: Invalid property 'binderOrderItemDetails.book' of bean class [PublisherInventory.model.user.BinderPlaceOrder]: Bean property 'binderOrderItemDetails.book' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
Below are the classes.
Please note, every class has their own Id property along with other properties. For readability those are omitted.
Here is BinderPlaceOrder Class,
#Entity
public class BinderPlaceOrder implements Comparator<BinderPlaceOrder> {
#OneToMany(mappedBy = "binderPlaceOrder", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<BinderOrderItemDetails> binderOrderItemDetails;
public List<BinderOrderItemDetails> getBinderOrderItemDetails() {
return binderOrderItemDetails;
}
public void setBinderOrderItemDetails(List<BinderOrderItemDetails> binderOrderItemDetails) {
this.binderOrderItemDetails = binderOrderItemDetails;
}
}
Here is BinderOrderItemDetails class
#Entity
#Indexed
public class BinderOrderItemDetails {
#OneToOne(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
#JoinColumn(name="bookId")
private Book book;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name="binderPlaceOrderId")
private BinderPlaceOrder binderPlaceOrder;
public Book getBook() { return book; }
public void setBook(Book book) { this.book = book; }
public BinderPlaceOrder getBinderPlaceOrder() { return binderPlaceOrder; }
public void setBinderPlaceOrder(BinderPlaceOrder binderPlaceOrder) {
this.binderPlaceOrder = binderPlaceOrder; }
}
Here is the Book Class
#Entity
#Indexed
public class Book implements Comparator<Book> {
#Column(name = "name")
#NotNull
#NotBlank
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name;}
}
Can you please tell me of what I am doing wrong or how it can be solved?
Thanks in advance.
As #JBNizet pointed out, your BinderPlaceOrder has
public List<BinderOrderItemDetails> getBinderOrderItemDetails() {
return binderOrderItemDetails;
}
which is a list, hence
th:field="*{binderOrderItemDetails.book}"
would not be correct,
but say you want to insert the first element,
th:field="${binderOrderItemDetails[0].book}"
I have a requirement wherein I want to use a Bean for both update/add. Now i have a validation as in the name should be unique.
Now during add the validation part is working correctly as it is checking for unique value by querying DB.
Now when i wanted to update the same record, it is trying to check the unique constraint in the DB and fails as the record already exists.
Role Bean
public class Role {
#NotEmpty
#Pattern(regexp = "[a-zA-Z ]*")
#UniqueValue(query = AppConstants.UNIQUE_VALIDATION_DB_QUERY)
private String roleName;
private String roleDesc;
private boolean active;
private String maskRoleName;
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getRoleDesc() {
return roleDesc;
}
public void setRoleDesc(String roleDesc) {
this.roleDesc = roleDesc;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
}
My Custom Annotation Validator
public class UniqueValueValidator implements ConstraintValidator<UniqueValue, String> {
#Autowired
private ValidationDAO validationDAO;
private String query;
public void initialize(UniqueValue uniqueValue) {
this.query = uniqueValue.query();
}
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
if (!StringUtils.isEmpty(value) && !StringUtils.isEmpty(query)) {
return validationDAO.isValidUniqueField(query, value);
}
return true;
}
}
Now when I update only the RoleDesc Field from screen the role name is validated and throws the validation error as the same role name exists in DB. Is there a way wherein I can send other variable to my custom validator from screen saying the following is update screen so only validate the field if it is changed from its previous value?
I came with a work around by annotating on a getter method where all the required fields are returned as a single map through that method and in the validationIMPL I retrieved all the required information and processed accordingly.
private String roleName;
#UniqueValue(query = AppConstants.UNIQUE_VALIDATION_DB_QUERY)
public Map<String,String> getUniqueValidator(){
Map<String,String> validatorMap=new HashMap<String,String>();
validatorMap.put("ACTION",type of action(update/new)):
validatorMap.put("VALUE",this.roleName):
return validatorMap;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
What you are probably looking for are Groups. You would modify your annotation to:
#UniqueValue(query = AppConstants.UNIQUE_VALIDATION_DB_QUERY, groups = {CreationGroup.class})
You'll also need to create a CreationGroup interface.
Then you will need to update your interceptor that calls the bean validation to use contextual information (possibly provided by another annotation wrapping the method where the validation is happening) to be something like this:
if (myMethodIsCreatingANewRecord()) {
validator.validate(address, Default.class, CreationGroup.class);
} else {
validator.validate(address, Default.class);
}
In my current spring project, the atributes from my entity classes have annotations to indicate which type of form control should be used for data input, like this:
#Column(name = "login", unique=true)
#Order(value=1)
#Input(name="login")
private String login;
#Column(name = "senha")
#Order(value=2)
#Input(type="password", name="senha")
private String senha;
#Column(name="nome")
#Order(value=3)
#Input(name="nome")
private String nome;
#Column(name="sobrenome")
#Order(value=4)
#Input(name="sobrenome")
private String sobrenome;
#Column(name="email")
#Order(value=5)
#Input(name="email")
private String email;
And I have custom tags which should read the atributes of the annotations and add to the page the correct tag, like that:
public class InputTag extends TagSupport {
/**
*
*/
private static final long serialVersionUID = 1L;
public void doTag() throws IOException, NoSuchFieldException, SecurityException {
JspWriter out = pageContext.getOut();
String name = this.getName();
String type = this.getType();
String pattern = this.getPattern();
if(type == null) {
if(pattern == "") {
out.println("<form:input path=\""+name+"\" class=\"form-control\"/>");
} else {
out.println("<form:input path=\""+name+"\" class=\"form-control valida\" pattern=\""+pattern+"\"/>");
}
} else {
if(pattern == "") {
out.println("<form:input path=\""+name+"\" type=\""+type+"\" class=\"form-control\"/>");
} else {
out.println("<form:input path=\""+name+"\" type=\""+type+"\" class=\"form-control valida\" pattern=\""+pattern+"\"/>");
}
}
}
...
}
the getter methods from the tag class have this format:
private String getName() throws NoSuchFieldException, SecurityException {
Field field = null;
Annotation annotation = field.getAnnotation(Input.class);
Input inputAnnotation = (Input) annotation;
String name = inputAnnotation.name();
return name;
}
What I need right now it's a way of store in the variable field the field I want add to he page. I know the method for do this if I was inside the entity class (something like getClass().getField("<nome>")), but how I access this information from my tag class?
The view is mapped in my controller this way:
#RequestMapping(value="cadastra")
#PreAuthorize("hasPermission(#user, 'cadastra_'+#this.this.name)")
public String cadastra(Model model) throws InstantiationException, IllegalAccessException {
model.addAttribute("command", this.entity.newInstance());
return "private/cadastrar";
}
and the jsp code in this moment it's this (just the basic structure):
<c:url value="/${entity}/cadastra" var="cadastra"/>
<form:form method="POST" action="${cadastra}" class="form" enctype="multipart/form-data">
<button type="submit" class="btn btn-lg btn-primary">cadastrar</button>
</form:form>
Anyone knows a way to accomplish this?