Passing an object to a controller with Ajax - which contains an Enum - ajax

I'm trying to pass an object Consumer to my Controller. A Consumer has a one-to-many relationship with Policy. This is what I tried:
function changeConsumer(){
var newConsumer = {
"id" : /*[[${consumer.id}]]*/,
"name" : document.getElementById('0').value,
"endpoint" : document.getElementById('1').value,
"policies" : /*[[${policies}]]*/
}
$.ajax({
type: "PUT",
url: "/rest/consumer/update",
data: JSON.stringify(newConsumer),
contentType: "application/json; charset=utf-8",
success : function(response) {
...
});
}
If I pass the Consumer like this, with an empty array of Policies - it works:
function changeConsumer(){
var newConsumer = {
"id" : /*[[${consumer.id}]]*/,
"name" : document.getElementById('0').value,
"endpoint" : document.getElementById('1').value,
"policies" : []
}
$.ajax({
type: "PUT",
url: "/rest/consumer/update",
data: JSON.stringify(newConsumer),
contentType: "application/json; charset=utf-8",
success : function(response) {
...
});
}
This is the stacktrace I'm receiving, which leads me to think there a problem with the Enum "Effect" in the Policy object:
2017-11-18 16:28:22.255 WARN 7884 --- [nio-8080-exec-6] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Can not deserialize instance of com.policyMgmt.policy.Effect out of START_OBJECT token
at [Source: java.io.PushbackInputStream#56bd85f4; line: 1, column: 93] (through reference chain: com.policyMgmt.consumer.Consumer["policies"]->java.util.ArrayList[0]->com.policyMgmt.policy.Policy["effect"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of com.policyMgmt.policy.Effect out of START_OBJECT token
at [Source: java.io.PushbackInputStream#56bd85f4; line: 1, column: 93] (through reference chain: com.policyMgmt.consumer.Consumer["policies"]->java.util.ArrayList[0]->com.policyMgmt.policy.Policy["effect"])
2017-11-18 16:28:22.255 WARN 7884 --- [nio-8080-exec-6] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved exception caused by Handler execution: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Can not deserialize instance of com.policyMgmt.policy.Effect out of START_OBJECT token
at [Source: java.io.PushbackInputStream#56bd85f4; line: 1, column: 93] (through reference chain: com.policyMgmt.consumer.Consumer["policies"]->java.util.ArrayList[0]->com.policyMgmt.policy.Policy["effect"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of com.policyMgmt.policy.Effekt out of START_OBJECT token
at [Source: java.io.PushbackInputStream#56bd85f4; line: 1, column: 93] (through reference chain: com.policyMgmt.consumer.Consumer["policies"]->java.util.ArrayList[0]->com.policyMgmt.policy.Policy["effect"])
The Policy object which looks like this:
#Entity
public class Policy {
#Id
#GeneratedValue
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
#Column (name = "consumer_id")
private Integer consumer;
#Enumerated(EnumType.STRING)
#Column(name="effect")
private Effect effect;
public Policy() {}
....
}
Maybe it helps, this is what a Consumer looks like:
#Entity
public class Consumer {
#Id
#GeneratedValue
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
#Column(name = "endpoint")
private String endpoint;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name ="consumer_id")
private List<Policy> policies;
public Consumer() {
}
}
Rest API
#Controller
#RequestMapping(value = "/rest/consumer")
public class ConsumerController {
...
#PutMapping(value = "/update")
#ResponseBody
public List<Consumer> updateConsumer(#RequestBody Consumer consumer) {}
...
}
Does anyone have experience passing enums within Ajax to a Controller?

I can't say for certain why #RequestBody is having difficulty deserializing JSON like { "$type": "Effekt", "$name": "deny" } into an instance of the Effekt enum, but I think you should try writing a custom JsonDeserializer for your enum.
Here's another Stackoverflow question that is similar, but not identical to the issue you're facing. Take a look at the second answer -- the one that includes a code example of a JsonDeserializer.
With a bit of trail-and-error, you can probably come up with something that successfully processes JSON like you're already getting into instances of Effekt.
The other option would be to change the JSON representation of Effekt in that gets sent to the server. Perhaps if it looked like this...
"effekt": "deny"
it would work directly with #RequestBody. You could try hard-coding it in the JavaScript to test.
If you want to go this route, you will probably need to implement a custom JsonSerializer -- the reverse of the earlier option.

Related

Spring WebClient Post body not getting passed

I am trying to use WebClient to Post a loan object to another microservice which saves this object in a DB. So theoretically the body (JSON loan object) should just be passed on to the API of the DB service. Somehow, I can't figure out how to accomplish this.
This is the API that accepts the JSON loan object:
Mapping: localhost:8081/loans
#PostMapping
public <T extends Loan> void addLoan(#Valid #NonNull #RequestBody T loan) {
loanService.createLoan(loan);
}
It then calls the loanService which should pass on the loan object to the DB-service API
public <T extends Loan> T createLoan(T loan) {
ParameterizedTypeReference<T> typeReference = new ParameterizedTypeReference<T>(){};
T a = client.post().uri("/loans").body(BodyInserters.fromValue(loan)).retrieve().bodyToMono(typeReference).block();
return a;
}
This is the API of that DB service:
Mapping: localhost:8080/api/v1/loans
#PostMapping
#ResponseBody
public <T extends Loan> T createLoan(#RequestBody T loan) {
return loanService.createLoan(loan);
}
And here is its service:
public <T extends Loan> T createLoan(T Loan) {
return (T) loanRepository.save(Loan);
}
If I just pass a loan object directly to the DB service API, everything works fine. But if I pass it to the other API, I get the following error:
"status": 500,
"error": "Internal Server Error",
"trace": "org.springframework.web.reactive.function.client.WebClientResponseException$InternalServerError: 500 Internal Server Error from POST http://localhost:8080/api/v1/loans/\n\tat org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:201)\n\tSuppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: \nError has been observed at the following site(s):\n\t|_ checkpoint ⇢ 500 from POST http://localhost:8080/api/v1/loans/ [DefaultWebClient]\nStack trace:\n\t\tat org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:201)\n\t\tat org.springframework.web.reactive.function.client.DefaultClientResponse.lambda$createException$1(DefaultClientResponse.java:216)\n\t\tat reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:106)\n\t\tat reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)\n\t\tat reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:99)\n\t\tat reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:127)\n\t\tat reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)\n\t\tat reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:295)\n\t\tat reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:337)\n\t\tat reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1784)\n\t\tat reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:159)\n\t\tat reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142)\n\t\tat reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:259)\n\t\tat reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142)\n\t\tat reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:383)\n\t\tat reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:396)\n\t\tat reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:452)\n\t\tat reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:664)\n\t\tat reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:94)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)\n\t\tat io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)\n\t\tat io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)\n\t\tat io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)\n\t\tat io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296)\n\t\tat io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)\n\t\tat io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)\n\t\tat io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)\n\t\tat io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:795)\n\t\tat io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:480)\n\t\tat io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:378)\n\t\tat io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)\n\t\tat io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)\n\t\tat io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)\n\t\tat java.base/java.lang.Thread.run(Thread.java:832)\n\tSuppressed: java.lang.Exception: #block terminated with an error\n\t\tat reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:99)\n\t\tat reactor.core.publisher.Mono.block(Mono.java:1679)\n\t\tat de.rwth.swc.lab.ws2021.daifu.businesslogic.services.LoanService.createLoan(LoanService.java:39)\n\t\tat de.rwth.swc.lab.ws2021.daifu.businesslogic.api.LoanController.addLoan(LoanController.java:28)\n\t\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\t\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)\n\t\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\t\tat java.base/java.lang.reflect.Method.invoke(Method.java:564)\n\t\tat org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)\n\t\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)\n\t\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)\n\t\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:893)\n\t\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:807)\n\t\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\n\t\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1061)\n\t\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:961)\n\t\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\n\t\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\n\t\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:652)\n\t\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\n\t\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:733)\n\t\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\n\t\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\t\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\n\t\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\t\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\t\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\n\t\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\t\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\t\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\t\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\n\t\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\t\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\t\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\t\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\n\t\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\n\t\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\n\t\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\n\t\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\n\t\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\n\t\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)\n\t\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)\n\t\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\n\t\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\n\t\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\n\t\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)\n\t\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\n\t\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)\n\t\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)\n\t\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\n\t\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)\n\t\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)\n\t\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\n\t\tat java.base/java.lang.Thread.run(Thread.java:832)\n",
"message": "500 Internal Server Error from POST http://localhost:8080/api/v1/loans/",
"path": "/loans/"
This is the server-side error:
Servlet.service() for servlet [dispatcherServlet] in context with path [/api/v1] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value : de.rwth.swc.lab.ws2021.daifu.dataservice.data.models.loans.PrivateLoan.customer; nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value : de.rwth.swc.lab.ws2021.daifu.dataservice.data.models.loans.PrivateLoan.customer] with root cause org.hibernate.PropertyValueException: not-null property references a null or transient value : de.rwth.swc.lab.ws2021.daifu.dataservice.data.models.loans.PrivateLoan.customer
And finally, this is the POST-body:
{
"amount": 10000.00,
"balance": -2000.00,
"customer": {"id": 1},
"interest": 0.06,
"status": "TIMELY",
"reason": "Some reaseon",
"type": "privateLoan"
}
The error says the "not-null property references a null or transient value" but the exact same request works for a direct POST-request to the 2nd API which doesn't make sense to me.
Here is the loan class:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#Getter
#Setter
#NoArgsConstructor
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = CarLoan.class, name = "carLoan"),
#JsonSubTypes.Type(value = ConstructionLoan.class, name = "constructionLoan"),
#JsonSubTypes.Type(value = Mortgage.class, name = "mortgage"),
#JsonSubTypes.Type(value = PrivateLoan.class, name = "privateLoan"),
#JsonSubTypes.Type(value = PropertyLoan.class, name = "propertyLoan")
})
#ApiModel(
discriminator = "type",
subTypes = {CarLoan.class, ConstructionLoan.class, Mortgage.class, PrivateLoan.class, PropertyLoan.class}
)
public abstract class Loan {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
#Column(name = "id")
#ApiModelProperty(required = false, hidden = true)
protected Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "customer_id", nullable = false)
#JsonBackReference(value = "customer-loans")
protected Customer customer;
#OneToMany(mappedBy = "loan", cascade = CascadeType.ALL)
#JsonManagedReference(value = "loan-loanRates")
private Set<LoanRate> loanRates;
#NonNull
protected Double amount;
#NonNull
protected Double interest;
#NonNull
protected Double balance;
#NonNull
protected LoanStatus status;
public enum LoanStatus {
TIMELY("timely"),
GRACE_PERIOD("grace period"),
DEFAULT("default"),
DEFICIT("deficit"),
IRRECOVERABLE_DEBT("irrecoverable debt"),
CLOSED("closed");
#Getter
private String stringRepresentation;
private LoanStatus(String s) {
this.stringRepresentation = s;
}
}
public <T extends Loan> boolean isOfSameInstance(T otherLoan) {
return (this.getClass().equals(otherLoan.getClass()));
}
}
Let me know if I should post anything else.
Thanks in advance.
The problem is due to the models being used in the projects. As your are reusing the model classes of the one webservice which offers the CRUD api for the backend, you are also reusing the jackson's #JsonManagedReference and #JsonBackReference. This leads to null values for the models being defined as the back reference, such as the customer in you loan class. Jackson does not serialize such tagged objects to JSON in order to not run into a stackoverflow due to infinite recursion. Thus, when you serialize a loan model in your service and send the request to the other service, jackson nulls the back reference, e.g. customer in the loan model and the 2nd webservice therefore receives an invalid loan model, since a loan model is required to have a customer not to be null.
I suggest to either remove the jackson annotations from the models in the service you develop, which would required copy pasted model classes (on the one side the classes using the required jackson annotations in the web service, and on the other side the classes not using these in the other web service). However, this solution has the typical disadvantages of duplicated code. The more elegant but more complicated solution will be to implement a custom jackson serializer and deserializer by specializing jackson's StdSerializer<Loan> and StdDeserializer<Loan>. These custom serializers and deserializers should override its serialize(T value, JsonGenerator gen, SerializerProvider provider) respectively its deserialize(JsonParser, DeserializationContext) method such that the #JsonManagedReference and #JsonBackReference, as well as, if being used, #JsonIgnore annotations in the model are being ignored.
It might be sufficient to just implement a custom serializer. However, I guess that you will also run into problems when receiving a response from the other web service when not using a custom deserializer.
This error may happen if the customer object in the loan you are trying to save is null or not yet added to the database (even though it is set in loan). You should check before saving the Loan in the DB if customer is null or not. If not, and if it is a customer not yet in the database, you should consider adding it first or specify CascadeType.PERSIST in the relation type annotation. In any case, it would be better if you post the entire model that both services are using.

How to do not send #IdClass object in Spring JSON queries

I'm setting a server to get a CRUD api from a postgresql Database using JPA. Everytime I want to expose an object from the DB it duplicate the idObject.
When I get an object from the database using springframework and send it after that, it duplicate the idObject like this:
{
"siteId": 3,
"contractId": "1",
"name": "sitenumber1",
"siteIdObject": {
"siteId": 3,
"contractId": "1"
}
}
SiteId and contractId are repeating...
but I want something like that:
{
"siteId": 3,
"contractId": "1",
"name": "sitenumber1"
}
I want to avoid using DTO because I think there is a better way but I don't find it. Since I'm using springFramework for just one or two month I'm maybe forgeting something...
there is the code:
Site code:
#Entity
#IdClass(SiteId.class)
#Table(name = "site", schema="public")
public class Site {
#Id
#Column(name="siteid")
private Integer siteId;
#Id
#Column(name="clientid")
private Integer contractId;
private String name;
#JsonIgnore
#OneToMany(cascade=CascadeType.ALL, mappedBy = "site")
public Set<Device> devices;
//setter, getter, hash, equals, tostring, constructor empty one and full one
SiteId code:
public class SiteId implements Serializable {
private Integer siteId;
private Integer contractId;
// setter, getter, constructor empty and full, hash and equals
Thanks to help :)
Bessaix Daniel
If you are using Spring you might also be using Jackson so if you annotate your SiteIdclass with #JsonIgnoreType it shouldn't be serialized at all when the Site object is serialized.
I am however unsure if this will break your application logic now that the id object is not serialized anymore.

Sending POST request with Postman with #DBref

I want to send a POST request with Postman that creates a Purchase object and save it in the database.
My class Purchase:
#Document(collection = "purchases")
public class Purchase {
#Id
private String id;
#DBRef
private User buyer;
#DBRef
private List<File> FilesToPurchase;
private Long timestamp;
public Purchase() { }
public Purchase(User buyer, List<File> filesToPurchase) {
this.buyer = buyer;
FilesToPurchase = filesToPurchase;
}
// Getters and setters not posted here.
I want to insert in the database a new purchase done by an already existing User "buyer" who wants to purchase a list of already exsting Files "FilesToPurchase".
I have in my controller a POST function that receives a Purchase object using the annotation #RequestBody, but so far I've got NullPointerExceptions because of the empty Purchase object received.
I don't know how to handle #DBRef annotation. In Postman I try sending a JSON like this:
{
"buyer": {
"$ref":"users",
"$id" : "ObjectId('5bb5d6634e5a7b2bea75d4a2')"
},
"FilesToPurchase": [
{ "$ref":"files",
"$id" : "ObjectId('5bb5d6634e5a7b2bea75d4a5')"
}
]
}
Rename field "FilesToPurchase" and setter to "filesToPurchase" to match java conventions and try this
{ "buyer": { "id" : "5bb5d6634e5a7b2bea75d4a2" }, "filesToPurchase": [ { "id" : "5bb5d6634e5a7b2bea75d4a5" } ] }
By marking controller parameter with #RequestBody you ask Spring to deserialize input json to java object(Jackson ObjectMapper is used by default). It will not automaticly populate the whole #Dbref field, you should do it yourself by querying mongo if you want, however the only field you need in referred object to save object that reffers it is 'id'.

How to create a new entity with association

Let's say i have a form on the frontend with usual fields and dropdowns.
In those dropdowns user is able to select an option, and each option is linked to an entity in Spring data JPA;
Dropdowns contain some label and a link to corresponding entity as a value.
This value is then passed in a POST-request to a PagingAndSorting repository of an entity which we wish to create.
Let's say it's a user with username and he must be associated with one of the offices (Also an entity):
#Data
#Builder
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Table(name="users")
public class User{
#Id
#Coluemn(name="USER_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
#Column(name="USER_NAME", nullable=false)
private String userName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="OFFICE_ID", **nullable=false**)
private Office office;
}
My first guess would be:
Sending POST-request to http://localhost:8080/api/users/
contentType:'application/json'
{"userName":"Anton","office":"http://localhost:8080/api/offices/1"}
But it throws an exception
{
"cause": {
"cause": null,
"message": "Cannot construct instance of `test.domain.Office` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/offices/1')\n at [Source: (org.apache.catalina.connector.CoyoteInputStream); line: 1, column: 160] (through reference chain: test.domain.User[\"office\"])"
},
"message": "JSON parse error: Cannot construct instance of `test.domain.Office` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/offices/1'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `test.domain.Office` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/office/1')\n at [Source: (org.apache.catalina.connector.CoyoteInputStream); line: 1, column: 160] (through reference chain: test.domain.User[\"office\"])"
}
What am i doing wrong?
You are sending a URL resource as a string in place of a JSON object and expecting some magic to happen between Spring and jackson to look up the value. Naturally this is not what is happening and Jackson is attempting to bind the string value of the URL to the Office field. This fails of course because it does not know how to create an Office object from a string.
A possible solution is to make a distinction between your Entity Objects (those which represent your database tables) and DTO's (Data Transfer Objects) which in this cause represent your contract with your client. When doing this you could receive a User Object like so:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class User{
private Long userId;
private String userName;
private Long officeId;
}
Now you can simply send an office id instead of a URL and in your code use a Spring data repository to lookup the office object. After that you can then construct your Entity User object like the one you have shown above and persist it.
Turns out it was because i used Lombok, which generated it's own constructor.
To make it work i just needed to set #AllArgsConstructor like this:
#AllArgsConstructor(suppressConstructorProperties = true)
Now it works as i expected:
Json to send to http://localhost:8080/api/users:
{
"userName":"Anton",
"office":"http://localhost:8080/api/offices/1"
}
Which returns:
{
"userName":"Anton",
"_links": {
"self": {
"href": "http://localhost:8080/api/users/28"
},
"user": {
"href": "http://localhost:8080/api/users/28"
},
"office": {
"href": "http://localhost:8080/api/users/28/office"
}
}
}

Spring MVC instantiate inner class form bean property

I have post data like:
cond[startData]:2015-09-22
cond[endDate]:2015-09-23
cond[orderId]:
And my ajax is:
cond={
startDate: "2015-09-22",
endDate: "2015-09-23",
orderId: ""
}
queryData = {cond:cond};
$.ajax({
url: "orderDetail",
type: "post",
dataType:"json",
data: queryData,
success: function(data){
}
});
And my spring form bean is
public class OrderFormBean {
private Cond cond;
private Result result;
// query condition
public class Cond {
private String startDate;
private String endDate;
private String orderId;
}
// query result
public class Result {
private String orderId;
private String orderDate;
private String operator;
...
}
...
}
So, it will cause error:
org.springframework.beans.NullValueInNestedPathException: Invalid property 'cond' of bean class [*.OrderFormBean]: Could not instantiate property type [*.OrderFormBean$Cond] to auto-grow nested property path: java.lang.InstantiationException: *.OrderFormBean$Cond
Edit
I found that even if I post data this way:
queryData = {
"cond.startDate": startDate,
"cond.endDate": endDate,
"cond.balanceType": balanceType,
"cond.orderId": orderId
};
Spring still produces the error Could not instantiate property type [*.OrderFormBean$Cond].
If I move inner class Cond outside, the Spring MVC works.
So It's the inner class reason, that Spring can't instantiate it.
According to this
How to create a Spring Bean of a Inner class?, there is a xml configure. But I don't want to configure it, as OrderFormBean is not configured.
Your error is caused by the field result being null.
Try the following:
cond={
startDate: "2015-09-22",
endDate: "2015-09-23",
orderId: ""
};
result={
/*whatever you are going to write here*/
};
queryData = {
cond: cond,
result: result
};
reference: http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/NullValueInNestedPathException.html
EDIT:
Also, your inner classes cannot be instantiated because non-static inner classes don't have a default (no-arguments) constructor.
Solutions are: either mark your inner classes as static or move them outside your "containing" class (to their own separate .java files)
For more details you can read:
http://thecodersbreakfast.net/index.php?post/2011/09/26/Inner-classes-and-the-myth-of-the-default-constructor

Resources