Here's my entity:
@Entity
@Table( name = "tbl_license" )
public class License implements Serializable
{
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
@Column( name = "id", nullable = false, columnDefinition = "serial" )
private int id;
@Column( name = "key", nullable = false, columnDefinition = "text" )
private String key;
@Column( name = "users", columnDefinition = "text" )
private String users;
@Transient
private final SimpleDateFormat dateFormat = new SimpleDateFormat( "MM/dd/yyyy" );
@Column( name = "valid_from" )
@Temporal( TemporalType.DATE )
private Date from;
@Column( name = "valid_to" )
@Temporal( TemporalType.DATE )
private Date to;
@Column( name = "volume" )
private Integer volume;
@Column( name = "SSL", columnDefinition = "bool default false" )
private boolean SSL = false;
@ManyToOne
@JoinColumn( name = "software_id", columnDefinition = "int" )
@ForeignKey( name = "tbl_software_fkey" )
private Software software;
@ManyToOne
@JoinColumn( name = "license_type_id", columnDefinition = "int" )
@ForeignKey( name = "tbl_license_type_fkey" )
private LicenseType licenseType;
@ManyToOne
@JoinColumn( name = "license_class_id", columnDefinition = "int" )
@ForeignKey( name = "tbl_license_class_fkey" )
private LicenseClass licenseClass;
@ManyToOne
@JoinColumn( name = "license_metric_id", columnDefinition = "int" )
@ForeignKey( name = "tbl_license_metric_fkey" )
private LicenseMetric licenseMetric;
@ManyToOne
@JoinColumn( name = "location_id", columnDefinition = "int" )
@ForeignKey( name = "tbl_location_fkey" )
private Location location;
@OneToMany( fetch = FetchType.LAZY, mappedBy = "id.license"/*, cascade = { CascadeType.REMOVE }, orphanRemoval = true*/ )
private List<LicenseUserAlert> alerts;
public int getId()
{
return id;
}
public void setId( int id )
{
this.id = id;
}
public String getKey()
{
return key;
}
public void setKey( String key )
{
this.key = key;
}
public String getUsers()
{
return users;
}
public void setUsers( String users )
{
this.users = users;
}
public String getFrom()
{
if ( from == null )
{
return null;
}
return dateFormat.format( from );
}
public Date getFromDate()
{
return from;
}
public void setFrom( String from )
{
try
{
this.from = dateFormat.parse( from );
}
catch ( ParseException e )
{
this.from = null;
}
}
public String getTo()
{
if ( to == null )
{
return null;
}
return dateFormat.format( to );
}
public Date getToDate()
{
return to;
}
public void setTo( String to )
{
try
{
this.to = dateFormat.parse( to );
}
catch ( ParseException e )
{
this.to = null;
}
}
@Transient
public Long getRemaining()
{
if ( from == null || to == null )
{
return null;
}
Date now = new Date();
Date fromDate = from;
Date toDate = to;
if ( fromDate.before( now ) )
{
fromDate = now;
}
return toDate.getTime() - fromDate.getTime();
}
public int getVolume()
{
return volume != null ? volume : 0;
}
public void setVolume( int volume )
{
this.volume = volume;
}
public boolean isSSL()
{
return SSL;
}
public void setSSL( boolean SSL )
{
this.SSL = SSL;
}
public Software getSoftware()
{
return software;
}
public void setSoftware( Software software )
{
this.software = software;
}
public LicenseType getLicenseType()
{
return licenseType;
}
public void setLicenseType( LicenseType licenseType )
{
this.licenseType = licenseType;
}
public LicenseClass getLicenseClass()
{
return licenseClass;
}
public void setLicenseClass( LicenseClass licenseClass )
{
this.licenseClass = licenseClass;
}
public LicenseMetric getLicenseMetric()
{
return licenseMetric;
}
public void setLicenseMetric( LicenseMetric licenseMetric )
{
this.licenseMetric = licenseMetric;
}
public Location getLocation()
{
return location;
}
public void setLocation( Location location )
{
this.location = location;
}
public List<LicenseUserAlert> getAlerts()
{
return alerts;
}
public void setAlerts( List<LicenseUserAlert> alerts )
{
this.alerts = alerts;
}
}
And here are the controller methods:
@RequestMapping( value = "/save.html", params = "json", method = RequestMethod.POST )
public @ResponseBody Map<String, ? extends Object> saveJSON( @RequestBody License license, HttpServletRequest request, HttpServletResponse response )
{
Map<String, String> validationMessages = new HashMap<String, String>();
for ( FieldError error : getFieldErrors( license ) )
{
validationMessages.put( error.getField(), error.getDefaultMessage() );
}
Set<ConstraintViolation<License>> constraintViolations = validator.validate( license );
if ( !validationMessages.isEmpty() || !constraintViolations.isEmpty() )
{
for ( ConstraintViolation<License> constraintViolation : constraintViolations )
{
String violationMessage = constraintViolation.getMessage();
try
{
violationMessage = messageSource.getMessage( constraintViolation.getConstraintDescriptor().getAnnotation().annotationType().getSimpleName() + ".license." + constraintViolation.getPropertyPath().toString(), null, null );
}
catch ( Exception e )
{
}
validationMessages.put( constraintViolation.getPropertyPath().toString(), violationMessage );
}
return Collections.singletonMap( "validationMessages", validationMessages );
}
service.save( license );
return Collections.singletonMap( "redirect", request.getContextPath() + "/index.html" );
}
@RequestMapping( value = "/save.html", method = RequestMethod.POST )
public String save( @ModelAttribute( "license" ) @Valid License license, BindingResult result, Map<String, Object> map )
{
for ( FieldError error : getFieldErrors( license ) )
{
result.addError( error );
}
if ( result.hasErrors() )
{
map.put( "softwareList", softwareService.list() );
map.put( "licenseTypeList", licenseTypeService.list() );
map.put( "licenseClassList", licenseClassService.list() );
map.put( "licenseMetricList", licenseMetricService.list() );
map.put( "locationList", locationService.list() );
return "license.form";
}
service.save( license );
return "redirect:/index.html";
}
And for completeness, here's the form:
<c:url value="/licenses/save.html" var="formAction" />
<form:form modelAttribute="license" action="${formAction}" method="post">
<form:errors path="*" cssClass="error" element="div" />
<form:hidden path="id" />
<div>
<form:label path="software.id">Software*:</form:label>
<form:select path="software.id" items="${softwareList}" itemValue="id" itemLabel="title" />
</div>
<div>
<form:label path="key">License key*:</form:label>
<form:input path="key" />
</div>
<div>
<form:label path="users">Installied at:</form:label>
<form:input path="users" />
</div>
<div>
<form:label path="licenseType">Type*:</form:label>
<form:select id="licenseType" path="licenseType.id" items="${licenseTypeList}" itemValue="id" itemLabel="title" />
</div>
<div>
<span id="licenseType1" class="typeAttr">
<form:label path="volume">Volume*:</form:label>
<form:input type="number" path="volume" />
</span>
<span id="licenseType2" class="typeAttr">
<form:label path="location">Location*:</form:label>
<form:select path="location.id" items="${locationList}" itemValue="id" itemLabel="title" />
</span>
<span id="licenseType3" class="typeAttr">
<form:label path="from">Valid from*:</form:label>
<form:input type="date" path="from" />
<form:label path="to">to*:</form:label>
<form:input type="date" path="to" />
</span>
</div>
<div>
<form:label path="licenseClass">Class*:</form:label>
<form:select path="licenseClass.id" items="${licenseClassList}" itemValue="id" itemLabel="title" />
</div>
<div>
<form:label path="licenseMetric">Metric*:</form:label>
<form:select path="licenseMetric.id" items="${licenseMetricList}" itemValue="id" itemLabel="title" />
</div>
<input type="submit" value="Submit" />
</form:form>
First method is for ajax submitting, second one as fallback/for testing without ajax.
Without ajax is works great, but using the first method results in a 400 Bad Request:
The request sent by the client was syntactically incorrect ().
It seems it has something to do with the other entities (class, metric, location, ...)
I already tried to add for all those entities a @RequestBody
param to the saveJSON method - but that didn't worked (same result)
EDIT
And here's the post body (this is a well formed json object, so i dunno why it is syntactically incorrect)
{
"id":"0",
"software.id":"5",
"key":"12345",
"users":"a, b, c",
"licenseType.id":"1",
"volume":"2",
"from":"",
"to":"",
"licenseClass.id":"1",
"licenseMetric.id":"1"
}