Spring List Binding - forms

Thanks in advance for any help.
I have the following object association in my model:
public class Contract {
private Integer id;
private String name;
//getters/setters...
}
public class User {
....
private List<Contract> contracts;
....
}
Controller:
#RequestMapping(....)
public String getUser(#PathVariable Integer userId, Model model) {
....
model.addAttribute(userDao.findUser(userId));
model.addAttribute("contractsList", contractDao.findAllContracts());
....
}
#RequestMapping(....)
public String processUser(#ModelAttribute User user, Model model) {
....
//Create a copy of the user to update...
User userToUpdate = userDao.findUser(user.getId);
....
userToUpdate.setContracts(user.getContracts());
//set other properties...
userDao.updateUser(userToUpdate);
return "someSuccessView";
}
#InitBinder
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
binder.registerCustomEditor(Contract.class, new UserContractsPropertyEditor());
}
My PropertyEditor:
public class UserContractsPropertyEditor extends PropertyEditorSupport {
#Inject ContractDao contractDao;
#Override
public void setAsText(String text) throws IllegalArgumentException {
System.out.println("matching value: " + text);
if (text != "") {
Integer contractId = new Integer(text);
super.setValue(contractDao.findContract(contractId));
}
}
}
My JSP form:
<form:form commandName="user">
<%-- Other fields... --%>
<form:checkboxes items="${contractsList}"
path="contracts"
itemValue="id"
itemLabel="name" />
</form:form>
The form renders correctly. That is, the checkbox list of Contracts is generated and the correct ones are "checked." The problem is when I submit I get:
java.lang.IllegalArgumentException: 'items' must not be null
at org.springframework.util.Assert.notNull(Assert.java:112)
at org.springframework.web.servlet.tags.form.AbstractMultiCheckedElementTag.setItems(AbstractMultiCheckedElementTag.java:83)
at org.apache.jsp.WEB_002dINF.jsp._005fn.forms.user_jsp._jspx_meth_form_005fcheckboxes_005f0(user_jsp.java:1192)
....
The custom property editor seems to be doing its job and there are no null/empty strings being passed.
If the form and controller makes the conversion when viewing the form, why is it having trouble when processing the form? What am I missing here?

You need to ensure that a call to getContract() returns a List instance:
public List<Contract> getContracts() {
if (contracts == null) contracts = new ArrayList<Contract>();
return contracts;
}

Thanks for your response. I guess a fresh set of eyes first thing in the morning does the trick again.
Apparently, my custom property editor had no clue what to do with the id value I was passing in since it couldn't access my DAO/service. So, I had to change the constructor:
public class UserContractsPropertyEditor extends PropertyEditorSupport {
private ContractDao contractDao;
public UserContractsPropertyEditor(ContractDao contractDao) {
this.contractDao = contractDao;
}
#Override
public void setAsText(String text) throws IllegalArgumentException {
Integer contractId = new Integer(text);
Contract contract = contractDao.findContract(contractId);
super.setValue(contract);
}
}
Then, modified the initBinder in my controller:
#Inject ContractDao contractDao;
....
#InitBinder
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
binder.registerCustomEditor(Contract.class, new UserContractsPropertyEditor(this.contractDao));
}
Maybe this will help someone else.

Related

How to Create Lookup by Account Type using DimensionDynamicAccountController?

I have a problem.
I have in my new table two new fields
1) Name -> AccountNum, EDT--> DimensionDynamicAccount
2) Name -> AccountType, EDT--> LedgerJournalACType
class declaration
:
public class FormRun extends ObjectRun
{
DimensionDynamicAccountController dimAccountController;
}
init (for the form):
public void init()
{
super();
dimAccountController = DimensionDynamicAccountController::construct(
MyTable_ds,
fieldstr(MyTable, LedgerDimension),
fieldstr(MyTable, AccountType));
}
4. Override the following methods on the Segmented Entry control instance in the form design.
public void jumpRef()
{
dimAccountController.jumpRef();
}
public void loadAutoCompleteData(LoadAutoCompleteDataEventArgs _e)
{
super(_e);
dimAccountController.loadAutoCompleteData(_e);
}
public void segmentValueChanged(SegmentValueChangedEventArgs _e)
{
super(_e);
dimAccountController.segmentValueChanged(_e);
}
public void loadSegments()
{
super();
dimAccountController.parmControl(this);
dimAccountController.loadSegments();
}
public boolean validate()
{
boolean isValid;
isValid = super();
isValid = dimAccountController.validate() && isValid;
return isValid;
}
5. Override the following methods on the data source field that backs the Segmented Entry control.
public Common resolveReference(FormReferenceControl _formReferenceControl)
{
return dimAccountController.resolveReference();
}
Now my problem is Lookup only works for AccountType=="Ledger" not for customer, Vendor etc...
If I have a AccountType == Vendor or similant but different to Ledger I see this
I would want to have same the same thing that's in the LedgerJournalTrans Form
There is a solution,
thanks all,
enjoy
This might be too obvious, but I think you're missing the lookup() method.
See:
\Forms\LedgerJournalTransDaily\Designs\Design\[Tab:Tab]\[TabPage:OverViewTab]\[Grid:overviewGrid]\SegmentedEntry:LedgerJournalTrans_AccountNum\Methods\lookup
public void lookup()
{
if (!ledgerJournalEngine.accountNumLookup(ledgerJournalTrans_AccountNum,
ledgerJournalTrans,
ledgerJournalTrans.OffsetAccountType,
ledgerJournalTrans.parmOffsetAccount(),
ledgerJournalTrans_Asset))
{
super();
}
}

JPA2.0 property access in spring rest data -- some getters not being called

I am still somewhat of a novice with Spring Boot and Spring Data Rest and hope someone out there with experience in Accessing by Property. Since I cannot change a database which stores types for Letters in an unnormalized fashion (delimited string in a varchar), I thought that I could leverage some logic in properties to overcome this. However I notice that when using property access, some of my getters are never called.
My Model code:
package ...
import ...
#Entity
#Table(name="letters", catalog="clovisdb")
#Access(AccessType.PROPERTY)
public class Letter {
public enum PhoneticType {
VOWEL, SHORT, LONG, COMMON;
public static boolean contains(String s) { ... }
}
public enum PositionType {
ALL, INITIAL, MEDIAL, FINAL;
public static boolean contains(String s) { ... }
}
public enum CaseType {
ALL, LOWER, UPPER;
public static boolean contains(String s) { ... }
}
private int id;
private String name;
private String translit;
private String present;
private List<PhoneticType> phoneticTypes;
private CaseType caseType;
private PositionType positionType;
#Id
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getTranslit() { return translit; }
public void setTranslit(String translit) { this.translit = translit; }
public String getPresent() { return present; }
public void setPresent(String present) { this.present = present; }
public String getTypes() {
StringBuilder sb = new StringBuilder(); //
if (phoneticTypes!=null) for (PhoneticType type : phoneticTypes) sb.append(" ").append(type.name());
if (caseType!=null) sb.append(" ").append(caseType.name());
if (positionType!=null) sb.append(" ").append(positionType.name());
return sb.substring( sb.length()>0?1:0 );
}
public void setTypes(String types) {
List<PhoneticType> phoneticTypes = new ArrayList<PhoneticType>();
CaseType caseType = null;
PositionType positionType = null;
for (String val : Arrays.asList(types.split(" "))) {
String canonicalVal = val.toUpperCase();
if (PhoneticType.contains(canonicalVal)) phoneticTypes.add(PhoneticType.valueOf(canonicalVal));
else if (CaseType.contains(canonicalVal)) caseType = CaseType.valueOf(canonicalVal);
else if (PositionType.contains(canonicalVal)) positionType = PositionType.valueOf(canonicalVal);
}
this.phoneticTypes = phoneticTypes;
this.caseType = (caseType==null)? CaseType.ALL : caseType;
this.positionType = (positionType==null)? PositionType.ALL : positionType;
}
#Override
public String toString() { .... }
}
My Repository/DAO code:
package ...
import ...
#RepositoryRestResource
public interface LetterRepository extends CrudRepository<Letter, Integer> {
List<Letter> findByTypesLike(#Param("types") String types);
}
Hitting this URI: http://mytestserver.com:8080/greekLetters/6
and setting breakpoints on all the getters and setters, I can see that the properties are called in this order:
setId
setName
setPresent
setTranslit
setTypes
(getId not called)
getName
getTranslit
getPresent
(getTypes not called !!)
The json returned for the URI above reflects all the getters called, and there are no errors
{
"name" : "alpha",
"translit" : "`A/",
"present" : "Ἄ",
"_links" : {
"self" : {
"href" : "http://mytestserver.com:8080/letters/6"
}
}
}
But why is my getTypes() not being called and my JSON object missing the “types” attribute? I note that the setter is called, which makes it even stranger to me.
Any help would be appreciated!
Thanks in advance
That's probably because you don't have a field types, so getTypes() isn't a proper getter. Try adding this to your entity
#Transient
private String types;
I don't know how the inner works, but it's possible that the class is first scanned for its fields, and then a getter is called for each field. And since you don't have types field, the getter isn't called. Setter getting called could be a feature but I wouldn't be surprised if it is a bug, because findByTypesLike should translate to find Letters whose types field is like <parameter>, and types is not a field.
Another thing you can try, is to annotate that getter with #JsonInclude. Jackson 2 annotations are supported in Spring versions 3.2+ (also backported to 3.1.2).

Spring MVC usage of form:radiobuttons to bind data (whole object not only one value)

I have a problem with sending data to backend by POST from my JSP using construction:
<form:radiobuttons path="pvClCategory" items="${categoryList}" itemLabel="clcaCategory"/>
After sumbit my Jboss (version 5) displays:
HTTP Status 400 - description: The request sent by the client was syntactically incorrect ().
I don't know how to see sent request. When I am using construction below everything works fine but I do not want to bind only one value from object, but whole object:
<form:radiobuttons path="pvClCategory.clcaCategory" items="${categoryList}" itemValue="clcaCategory" itemLabel="clcaCategory"/>
So, the first form construction (with binding whole object) cause me a problem. Form is initialised properly it means that radio options are correctly displayed and correct value is selected on jsp site. But problem appears when I want to submit the form, i receive error (HTTP Status 400 request sent by client was syntactically incorrect). Any idea why? What I am doing wrong?
My code:
I have one entity class Violation with field named pvClCategory type of ClCategory
#Entity
#Table(name="PV_VIOLATIONS")
public class Violation implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="VIOL_ID")
private Long id;
#ManyToOne
#JoinColumn(name="VIOL_CLCA_ID")
private ClCategory pvClCategory;
/* others fields and getters/setters */
}
My ClCategory entity class which is needed to bind it to Violation.pvClCategory field:
#Entity
#Table(name="PV_CL_CATEGORIES")
#Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
public class ClCategory implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="CLCA_ID")
private long clcaId;
#Column(name="CLCA_CATEGORY")
private String clcaCategory;
#OneToMany(mappedBy="pvClCategory")
private List<Violation> pvViolations;
public ClCategory() {
}
public long getClcaId() {
return this.clcaId;
}
public void setClcaId(long clcaId) {
this.clcaId = clcaId;
}
public String getClcaCategory() {
return this.clcaCategory;
}
public void setClcaCategory(String clcaCategory) {
this.clcaCategory = clcaCategory;
}
public List<Violation> getPvViolations() {
return this.pvViolations;
}
public void setPvViolations(List<Violation> pvViolations) {
this.pvViolations = pvViolations;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (clcaId ^ (clcaId >>> 32));
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ClCategory other = (ClCategory) obj;
if (clcaId != other.clcaId)
return false;
return true;
}
}
This Violation class is used in my form in commandName property as follows (file name: v.jsp):
<form:form action="../update" method="post" commandName="violation">
<form:radiobuttons path="pvClCategory" items="${categoryList}" itemLabel="clcaCategory"/>
<!-- other fields -->
<button type="submit" value="Apply Changes" class="button-default" >Apply</button>
</form:form>
My controller:
#Controller
#RequestMapping("/viol")
public class ViolationController {
#RequestMapping("edit/{violationId}")
public String edit(#PathVariable("violationId") long id, Map<String, Object> map) {
Violation violation = violationService.getViolation(id);
map.put("violation",violation);
map.put("categoryList", adminService.listCategories());
return "v";
}
}
As I understand in the construction form:radiobuttons path="" items="" the path is property for data binding so whole object from delivered list in items should be binded to it. I put in items List of my categories which are objects of type ClCategory. After submit error appears.
When i use form:radiobuttons path="pvClCategory.clcaCategory" items="${categoryList}" itemValue="clcaCategory" itemLabel="clcaCategory" to bind only String value from object in items (in both cases is used the same object list of type ClCategory) then the form is correctly submited but I do not want to bind only one value of objecte but whole object. Could you help me what am I doing wrong?

POJO information lost during RPC call (GWT)

I am having issues with RPC calls and GWT. Essentially, I have a Person class (common code between client and server) that is created in the client side web code, sent to the server code via an RPC call, and then saved to a DB (OrientDB). I have verified that the following work:
RPC call - I am able to send info to the server and retrieve info from the server
save to DB - have verified that a Person object is saved to the DB
Where I am having issues is the transfer of the POJO from the client to the server. I have verified that the POJO's properties are intact right before it is sent to the server, however, the object passed to the server contains null values for all properties. Essentially, the class is transferred but the information is not. It then saves to the DB, but obviously without any relevant information contained within it.
I will copy what I feel is relevant below, please let me know what else I can provide to make this problem easier to identify. Note these are still in a testing state, so mind the comments :)
Any idea why my POJO's information is being lost in translation?
Person object, followed by the abstract class it inherits from:
public class Person extends org.matesweb.shared.AbsPerson implements Serializable
{
#Id
private String id; // DON'T CREATE GETTER/SETTER FOR IT TO PREVENT THE CHANGING BY THE USER APPLICATION,
// UNLESS IT'S NEEDED
//sets new user details
public void setPerson(String fIrstName, String mIdInit, String lAstName, String email, String password)
{
firstName = fIrstName;
middleInitial = mIdInit;
lastName = lAstName;
}
/*getter and setter methods - required for every
* field due to restrictions imposed by OrientDB*/
public Object getId()
{
String tmp;
tmp = id.toString();
return tmp;
}
//end class
}
public class AbsPerson implements Serializable
{
String firstName;
String middleInitial;
String lastName;
// public sys.Login login;
public org.matesweb.shared.Group[] groups;
private org.matesweb.shared.Purchase[] purchases;
/*this method adds a new purchase to the purchases variable*/
/* public void addPurchase(float price, String description)
{
people.Purchase newPurchase = new people.Purchase(login, price, description);
}
*/
/*adds a person to a group by comparing the passed in group ID and PWD*/
public void addGroup(String groupID, String groupPWD)
{
//compare group ID with group PWD to add a user to the group
}
/*getter and setter methods - required for every
* field due to restrictions imposed by OrientDB*/
public String getFirstName()
{
return firstName;
}
public void setFirstName(String name)
{
firstName = name;
}
public String getMiddleInitial()
{
return middleInitial;
}
public void setMiddleInitial(String midInit)
{
middleInitial = midInit;
}
public String getLastName()
{
return lastName;
}
public void setLastName(String ln)
{
lastName = ln;
}
/*
public sys.Login getLogin()
{
return login;
}
public void setLogin(sys.Login log)
{
login = log;
}
*/
public org.matesweb.shared.Group[] getGroups()
{
return groups;
}
public void setGroups(org.matesweb.shared.Group[] gro)
{
groups = gro;
}
public org.matesweb.shared.Purchase[] getPurchases()
{
return purchases;
}
public void setPurchases(org.matesweb.shared.Purchase[] purch)
{
purchases = purch;
}
}
Service
package org.matesweb.client;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
import org.matesweb.shared.Person;
#RemoteServiceRelativePath("peopleService")
public interface PeopleService extends RemoteService {
//test services
String stringTest(String outgoingString);
Person getPerson(String persId);
//production services
String savePerson(Person p);
}
ServiceAsync
import com.google.gwt.user.client.rpc.AsyncCallback;
import org.matesweb.shared.Person;
public interface PeopleServiceAsync
{
//tests
void stringTest(String outgoingString, AsyncCallback<String> incomingString);
void getPerson(String persId, AsyncCallback<Person> retPerson);
//production services
void savePerson(Person p , AsyncCallback<String> st);
}
ServiceImpl call for this particular method:
//production calls
#Override
public String savePerson(Person p) {
String st = ioObj.saveObj(p);
if(st.equals("Success")){
return "Your information has been saved successfully!";
} else{
return "Something has gone wrong on our end... Sorry! Error:<br /> " + st;
}
}
and finally, the call itself
private static void savePerson(Person p)
{
// Initialize the service proxy.
if (peopleSvc == null) {
peopleSvc = GWT.create(PeopleService.class);
}
//resets status
st="";
// Set up the callback object.
AsyncCallback<String> callback = new AsyncCallback<String>() {
#Override
public void onFailure(Throwable caught) {
st = caught.getMessage();
Label stLabel= new Label(st);
personTable.setWidget(3,1,stLabel);
}
#Override
public void onSuccess(String result) {
st = result;
HTML stLabel= new HTML(st);
joinPanel.add(stLabel);
}
};
// Make the call to the people service.
peopleSvc.savePerson(p, callback);
}
I was able to fix this issue by implementing GWT's IsSerializable interface. I also removed the Serializable interface from the Person class and let it inherit IsSerializable from the abstract class it inherits from.

GWT ValueListBox, Renderer and ProvidesKey

How to implement a GWT ValueListBox inside an Editor with a specific list of objects, my code:
...
#UiField(provided = true)
#Path("address.countryCode")
ValueListBox<Country> countries = new ValueListBox<Country>(
new Renderer<Country>() {
#Override
public String render(Country object) {
return object.getCountryName();
}
#Override
public void render(Country object, Appendable appendable)
throws IOException {
render(object);
}
},
new ProvidesKey<Country>() {
#Override
public Object getKey(Country item) {
return item.getCountryCode();
}
});
...
The Country class
public class Country {
private String countryName;
private String countryCode;
}
But, during the GWT compilation I'm getting this error:
Type mismatch: cannot convert from String to Country
The problem is that you are trying to edit the address.countryCode (looking at the path annotation) with editor for Country.
To make this work, you should change the path to address.country and do the assignment of the address.countryCode after editorDriver.flash(). Something like:
Address address = editorDriver.flush();
address.setCountryCode(address.getCountry().getCountryCode());
To support this, the Address class should have the Country object as property.
You may have assumed that the ValueListBox will work like classical select where the key is assigned to the property. Here the whole object is assigned. So in your case Country object can not be assigned to address.countryCode and vice-versa.
Btw. you can correct the renderer (like the code below) and take care of null objects as arguments in the Renderer and Key Provider.
new Renderer<Country>() {
...
#Override
public void render(Country object, Appendable appendable)
throws IOException {
appendable.append(render(object));
}
...
}