Spring MVC 3.1 : how to map JSON from a PUT request body? - ajax

I know this question has been asked a gazillion times, but I still cannot find a solution to my problem, which basically boils down to JSON deserialization from a PUT request.
I've already added HiddenHttpMethodFilter as a filter.
org.codehaus.jackson.jackson-mapper-lgpl is in the classpath.
Here is the client part:
$.ajax({
url: '/occurrence',
type: 'PUT',
contentType: 'application/json',
data: JSON.stringify({id:id,startDate:startDate, endDate:endDate, frequencyType:frequency})
})
Here is the controller part:
#Controller
#RequestMapping("/occurrence")
public class OccurrenceController {
private static final String COMMAND = "eventCommand";
#Autowired
private PersistenceCapableOccurrence occurrenceDao;
#Autowired
private PersistenceCapableFrequencyType frequencyTypeDao;
#InitBinder(COMMAND)
public void customizeConversions(final WebDataBinder binder) {
DateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm");
df.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(df, true));
EntityConverter<FrequencyType> frequencyTypeEntityConverter = new EntityConverter<FrequencyType>(frequencyTypeDao, FrequencyType.class, "findByValue", String.class);
((GenericConversionService) binder.getConversionService()).addConverter(frequencyTypeEntityConverter);
}
#RequestMapping(method = PUT, consumes = "application/json")
#ResponseBody
public Long saveOccurrence(#RequestBody Occurrence occurrence) {
return occurrenceDao.saveOrUpdate(occurrence);
}
}
Here are my two domain classes (Occurrence and FrequencyType):
public class Occurrence {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", nullable = false)
private long id;
#NotNull
#Column(name = "start_date")
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
private DateTime startDate;
#Column(name="end_date")
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
private DateTime endDate;
#ManyToOne
#JoinColumn(name = "frequency_type", nullable = false)
private FrequencyType frequencyType;
/* C-tor (1 with [start,end,freq], another with [start,freq]), getters (no setters) */
}
#Entity
#Table(name = "frequency_types")
public class FrequencyType {
public enum FrequencyTypeValues {
ONCE, DAILY, WEEKLY, MONTHLY, YEARLY;
}
private String value;
public FrequencyType() {}
public FrequencyType(FrequencyTypeValues value) {
this.value = value.name();
}
#Id
#Column(name = "value")
public String getValue() {
return value;
}
public void setValue(String value) {
//validates value against the enumerated/allowed values (ie throws exceptions if invalid value)
FrequencyTypeValues.valueOf(value.toUpperCase());
this.value = value;
}
}
All I get at the end is a 400 response.
Example :
PUT Request
{"id":"","startDate":"20/10/2012 17:32","endDate":"","frequencyType":"YEARLY"}
Response
"NetworkError: 400 Bad Request - http://localhost:9999/occurrence"
Thanks in advance for your help !
Rolf

Related

Json content for One post in Many to one rs request in spring boot

So i have two classes,
class User:
#Data
#NoArgsConstructor
#Entity
public class User {
#Id
#GeneratedValue
private int id;
private String displayName;
private String email;
private String gender;
private String Nationality;
private int age;
#OneToMany(mappedBy = "id", cascade = CascadeType.ALL)
private List<Event> createdEvents;
#OneToMany(mappedBy = "id", cascade = CascadeType.ALL)
private List<Reservation> clientReservations;
}
and class Event:
#Data
#Entity
public class Event {
#Id
#GeneratedValue
private int id;
private String eventName;
private Date eventDate;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_at", nullable = false, updatable = false)
#CreatedDate
private Date createdAt;
#ManyToOne
#JoinColumn(name="user_id")
private User user;
}
Contoller class:
#RestController
#RequestMapping("/event")
public class EventController {
#Autowired
private EventRepository eventRepository;
#PostMapping("/create")
public Event addEvent(#RequestBody Event event) {
return eventRepository.save(event);
}
}
Json:
"eventName": "theatre",
"eventDate": "2020-04-22",
"user": 3
im new to spring boot and what I've tried doesn't work.
now i want to add a single Event, and i need to pass a user id to reference the user who created the event, How can i do it ?
1.
In your Event class, add a constructor like this:
public Event(String eventName, Date eventDate, User user) {
this.createdAt = new DateTime();
this.eventName = eventName;
this.eventDate = eventDate;
this.user = user;
}
2.
Instead of passing Event as #RequestBody, consider creating a dto that handles submitted data on Post requests
public class EventDto {
private String eventName;
private String eventDateString;
private Long userId;
public String getEventName() {
return eventName;
}
public void setEventName(String eventName) {
this.eventName = eventName;
}
public String getEventDateString() {
return eventDateString;
}
public void setEventDateString(String eventDateString) {
this.eventDateString = eventDateString;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
}
Then you must replace #RequestBody Event event with #RequestBody EventDto eventDto
3.
Inject UserRepository, handle data, check that submitted user id exists and save your Event
#PostMapping("/create")
public Event addEvent(#RequestBody EventDto eventDto) throws ParseException {
var user = userRepository.findById(eventDto.getUserId());
if (user.isPresent()) {
Event event = new Event(eventDto.eventName.trim(), new SimpleDateFormat("yyyy-MM-dd").parse(eventDto.eventDateString), user.get());
eventRepository.save(event);
}
}

marshall attributes inside XML elements with JAXB

I work with Spring JPA and have the following entity:
#Entity
#Table(name = Constants.ENTITY_TABLE_PREFIX + "ENTRY")
#XmlAccessorType(XmlAccessType.NONE)
#XmlRootElement(name = "monObj_info")
public class EntryXML implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
#XmlAttribute
private long id;
#Column(name = "ip_address", nullable = true)
#XmlElement
private String ip_address;
#Column(name = "network_element_name", nullable = false)
#XmlElement
private String network_element_name;
public EntryXML() {}
public EntryXML(long id, String ip_address, String network_element_name) {
super();
this.id = id;
this.ip_address = ip_address;
this.network_element_name = network_element_name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getIp_address() {
return ip_address;
}
public void setIp_address(String ip_address) {
this.ip_address = ip_address;
}
public String getNetwork_element_name() {
return network_element_name;
}
public void setNetwork_element_name(String network_element_name) {
this.network_element_name = network_element_name;
}
}
and the endpoint:
#RestController
public class EntryXMLEndpoint {
#Autowired
private IEntryXMLService service;
#RequestMapping(value = "/restxml", produces = { "application/xml" })
public EntryXML findEntries() {
EntryXML record = service.findById(1);
return record;
}
}
Now the requested response is:
<monObj_info id="1">
<atribute name="ip_address" value="xx.xxx.xxx.x"/>
<atribute name="network_element_name" value="xxxxxx"/>
</monObj_info>
Of course what I get is :
<monObj_info id="1">
<ip_address>xx.xxx.xxx.x</ip_address>
<network_element_name>xxxxxx</network_element_name>
</monObj_info>
I read similar posts , but the problem is I cannot create a List with the required elements inside my Entity Class, since it will not map with any column in the respective table, any suggestions?
You can achieve your goal in a straight-forward but somewhat hackish way.
Since you don't want the ip_address and network_element_name properties
to be marshalled and unmarshalled directly, you need to remove their #XmlElement annotation
and add #XmlTransient.
Instead, you want some <atribute name="..." value="..." /> elements marshalled and unmarshalled.
Therefore you need to add the following things to your EntryXML class:
an attributes property holding a list of attributes.
It is annotated with #XmlElement so that it will be part of XML marshalling and unmarshalling.
It is annotated with #Transient so that it will not be part of database persistence.
a simple helper class Attribute for holding name and value.
name and value are annotated with #XmlAttribute so that they will be part of XML marshalling and unmarshalling.
a Marshal Event Callback (beforeMarshal)
for doing the conversion from ip_address and network_element_name
to the attributes list.
an Unmarshal Event Callback (afterUnmarshal)
for doing the opposite conversion.
#XmlElement(name = "atribute")
#Transient // from package javax.persistence
private List<Attribute> attributes;
// there is no need for getAttributes and setAttributes methods
private static class Attribute {
#SuppressWarnings("unused") // called by the unmarshaller
Attribute() {
}
Attribute(String name, String value) {
this.name = name;
this.value = value;
}
#XmlAttribute
private String name;
#XmlAttribute
private String value;
}
#SuppressWarnings("unused") // this method is called only by the marshaller
private boolean beforeMarshal(Marshaller marshaller) {
attributes = new ArrayList<>();
attributes.add(new Attribute("ip_address", ip_address));
attributes.add(new Attribute("network_element_name", network_element_name));
return true;
}
#SuppressWarnings("unused") // this method is called only by the unmarshaller
private void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
if (attributes != null) {
for (Attribute attribute : attributes) {
switch (attribute.name) {
case "ip_address":
ip_address = attribute.value;
break;
case "network_element_name":
network_element_name = attribute.value;
break;
}
}
}
}
Then the XML output will look like this:
<monObj_info id="1">
<atribute name="ip_address" value="xx.xxx.xxx.x"/>
<atribute name="network_element_name" value="xxxxxx"/>
</monObj_info>

How to cache only when the json is valid

I have a spring rest api application that is using HATEOAS/PagingAndSortingRepository to do most of the heavy lifting.
I have implemented caching using guava but I am having issues where when the user cancels the request midway through an api call, it caches the incomplete json and re-serves it for 60 seconds.
I am trying to use the unless="" parameter of the #Cacheable annotation. Previously, I just used unless="#result == null" but that does not handle incomplete or invalid json.
This does not seem to work either. So now I am trying to use com.google.gson.JsonParser to parse the result and invalidate if applicable.
Repository
#RepositoryRestResource(path = "products", collectionResourceRel = "products")
public interface ProductEntityRepository extends PagingAndSortingRepository<ProductEntity, String> {
JsonParser parser = new JsonParser();
#Cacheable(value = CacheConfig.STORE_CACHE)
ProductEntity findByName(String name);
}
Cache Config
public final static String PRODUCTS_CACHE = "products";
#Bean
public Cache productsCache() {
return new GuavaCache(PRODUCTS_CACHE, CacheBuilder.newBuilder()
.expireAfterWrite(60, TimeUnit.SECONDS)
.build());
}
How do I detect invalid json in the unless="" parameter?
I figured out my own issue!
When I interrupted the api request to localhost/products and re-requested, I finally saw an error about not being able to fetch a onetomany mapping. I believe the error was lazy initialization error for a collection.
I solved this issue by adding #LazyCollection(LazyCollectionOption.FALSE) to my models where the #OneToMany and #ManyToOne mappings were decalared.
For example:
#Entity(name = "product")
#Table(name = "products", schema = "${DB_NAME}", catalog = "")
public class ProductEntity {
private Integer id;
private String name;
private List shipments = new ArrayList<>();
#Id
#Column(name = "id", nullable = false)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#Basic
#Column(name = "name", nullable = false, length = 10)
public String getName() { return name; }
public void setName(String name) {
this.name = name;
}
#OneToMany(mappedBy = "shipmentID", targetEntity=ShipmentEntity.class)
#LazyCollection(LazyCollectionOption.FALSE)
public Collection<ShipmentEntity> getShipments() { return shipments; }
public void setShipments(Collection<ShipmentEntity> shipments) { this.shipments = shipments; }
}

spring boot ignore field dynamically jpa

I am using Spring Boot REST Web Services and Angular 5 as a frontend, well I have a model class for hibernating like this :
#Entity
public class Title {
private Integer id;
private String name;
private Date releaseDate;
private Time runtime;
private String storyline;
private String picture;
private String rated;
private String type;
private Double rating;
private Integer numberOfVotes;
private Timestamp inserted;
private Set<Genre> genres = new HashSet<>();
private List<TitleCelebrity> titleCelebrities;
private List<TitleMedia> titleMedia;
// Basic getters and setter
#ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "title_genre", joinColumns = { #JoinColumn(name = "title_id") }, inverseJoinColumns = { #JoinColumn(name = "genre_id") })
public Set<Genre> getGenres() {
return genres;
}
public void setGenres(Set<Genre> genres) {
this.genres = genres;
}
#OneToMany(mappedBy = "title", cascade = CascadeType.ALL)
public List<TitleCelebrity> getTitleCelebrities() {
return titleCelebrities;
}
public void setTitleCelebrities(List<TitleCelebrity> titleCelebrities) {
this.titleCelebrities = titleCelebrities;
}
#OneToMany(mappedBy = "title", cascade = CascadeType.ALL)
public List<TitleMedia> getTitleMedia() {
return titleMedia;
}
public void setTitleMedia(List<TitleMedia> titleMedia) {
this.titleMedia = titleMedia;
}
}
And here's my REST controller
#RestController
#RequestMapping("titles")
#CrossOrigin(origins = {"http://localhost:4200"})
public class TitleController {
private TitleService titleService;
#Autowired
public void setTitleService(TitleService titleService) {
this.titleService = titleService;
}
// Api to get all the movies ordered by release date
#GetMapping("movies")
public List<Title> getAllMoviesOrderByReleaseDateDesc() {
return this.titleService.findByTypeOrderByReleaseDateDesc("movie");
}
#GetMapping("movies/{id}")
public Title findById(#PathVariable Integer id) {
return this.titleService.findById(id);
}
}
What I want is when I make a request to the first method '/movies' i don't want the collection of Telemedia, but if I make a request to the second method '/movies/id' i want the collection of Telemedia.
of course, the annotation #JsonIgnore will ignore the collection whatever the request is.
It may be better to create two models in this case; one to represent the first response and another to represent the second response.
You could also set the collection to null in your second request before sending it back.
You cannot accomplish this with #JsonIgnore alone as you cannot perform conditional logic in annotations.

Getting ConstraintViolationException while saving a row with embedded key in the table with many-to-many mapping between two entities using Spring JPA

In our spring boot Restful WebService, we have two master tables with many-to-many relationship between them. But in the transaction table, we want one extra field (current_time) as part of the embedded key other than the primary keys of the two tables. Now, we’ve created a separate class for defining embedded primary key using #Embeddable. Now, while inserting one transaction row to transaction table using Spring JPA, I am manually setting the primary keys in the corresponding entity and calling the save method on corresponding repository. But It is giving me ConstraintViolationException as the current_time is going with null value even if I have manually set it. Any help would be highly appreciated.
First Entity is as follows :
#Entity
#Table(name = "project")
public class Project {
#Id
#GenericGenerator(name = "projectid", strategy = "com.sample.upload.entity.ProjectIDGenerator")
#GeneratedValue(generator = "projectid")
#Column(name = "projectid")
private String projectID;
#Column(name = "project_name")
private String projectName;
#Column(name = "project_descr")
private String projectDesc;
#Column(name = "project_input_path")
private String projectPath;
#Column(name = "project_creation_time")
private Calendar projectCreationTime;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "project_migration", joinColumns = #JoinColumn(name = "projectid", referencedColumnName = "projectid"), inverseJoinColumns = #JoinColumn(name = "migratorid", referencedColumnName = "migratorid"))
private List<Migrator> migrators;
#Column(name = "account_name")
private String accountName;
#Column(name = "account_group")
private String accountGroup;
public String getProjectID() {
return projectID;
}
public void setProjectID(String projectID) {
this.projectID = projectID;
}
public String getAccountName() {
return accountName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public String getAccountGroup() {
return accountGroup;
}
public void setAccountGroup(String accountGroup) {
this.accountGroup = accountGroup;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public String getProjectDesc() {
return projectDesc;
}
public void setProjectDesc(String projectDesc) {
this.projectDesc = projectDesc;
}
public String getProjectPath() {
return projectPath;
}
public void setProjectPath(String projectPath) {
this.projectPath = projectPath;
}
public Calendar getProjectCreationTime() {
return projectCreationTime;
}
public void setProjectCreationTime(Calendar projectCreationTime) {
this.projectCreationTime = projectCreationTime;
}
public List<Migrator> getMigrators() {
return migrators;
}
public void setMigrators(List<Migrator> migrators) {
this.migrators = migrators;
}
}
Second Entity :
#Entity
#GenericGenerator(name = "generatorName", strategy = "increment")
#Table(name = "migrator")
public class Migrator {
#Id
#GeneratedValue(generator = "generatorName")
#Column(name = "migratorid")
private String migratorId;
#Column(name = "src_tech_name")
private String srcTechName;
#Column(name = "dest_tech_name")
private String destTechName;
#Column(name = "migrator_name")
private String migratorName;
#Column(name = "migrator_type")
private String migratorType;
public String getMigratorId() {
return migratorId;
}
public void setMigratorId(String migratorId) {
this.migratorId = migratorId;
}
public String getSrcTechName() {
return srcTechName;
}
public void setSrcTechName(String srcTechName) {
this.srcTechName = srcTechName;
}
public String getDestTechName() {
return destTechName;
}
public void setDestTechName(String destTechName) {
this.destTechName = destTechName;
}
public String getMigratorName() {
return migratorName;
}
public void setMigratorName(String migratorName) {
this.migratorName = migratorName;
}
public String getMigratorType() {
return migratorType;
}
public void setMigratorType(String migratorType) {
this.migratorType = migratorType;
}
#Override
public String toString() {
return "Technology [migratorId=" + migratorId + ", srcTechName=" + srcTechName + ", destTechName="
+ destTechName + ", migratorName=" + migratorName + ", migratorType=" + migratorType + "]";
}
}
The join (transaction) table's entity :
#Entity
#Table(name = "project_migration")
public class ProjectMigration {
#EmbeddedId
private ProjectMigrationID migrationId;
#Column(name ="migration_finish_time")
private Calendar migrationFinishTime;
#Column(name ="time_in_millis_for_migration")
private long timeInMillisForMigration;
#Column(name ="migration_status")
private String migrationStatus;
#Column(name ="migrated_codebase_path")
private String migratedCodeBasePath;
The embedded Primary Key class is as follows:
#Embeddable
public class ProjectMigrationID implements Serializable {
private static final long serialVersionUID = -3623993529011381924L;
#Column(name = "projectid")
private String projectId;
#Column(name = "migratorid")
private String migratorId;
#Column(name = "migration_start_time")
private Calendar migrationStartTime;
public ProjectMigrationID() {
}
public ProjectMigrationID(String projectId, String migratorId, Calendar migrationStartTime) {
this.projectId = projectId;
this.migratorId = migratorId;
this.migrationStartTime = migrationStartTime;
}
The snippet from service Class :
for (String migratorId : data.getMigratorIds()) {
Migrator migrator = migratorRepository.findByMigratorId(migratorId);
migrators.add(migrator);
}
if (projectId != null) {
project = projectRepository.findByProjectID(projectId);
System.out.println(project==null);
project.setMigrators(migrators);
System.out.println("I am here");
if (project != null) {
//project.setMigrationStatus("In Progress");
ProjectMigrationID pmId = new ProjectMigrationID();
pmId.setProjectId(project.getProjectID());
pmId.setMigratorId(project.getMigrators().get(0).getMigratorId());
pmId.setMigrationStartTime(new GregorianCalendar());
ProjectMigration pm = new ProjectMigration();
pm.setMigrationId(pmId);
pm.setMigrationStatus("Pending");
projectMigrationRepository.save(pm);
That's because of the #JoinTable where the date is not included and it skips the insertion. If you include a column with all the primary keys needed, it will work as expected.
Only the columns mapped via #JoinTable will be included during insertion or update (defaults to true when mapped)
Either include the date time column in the Project class or use association without #JoinTable.
I'm editing via mobile. So please ignore typos if any.

Resources