struts2-rest-plugin: sending json data to PUT/POST - java

I'm stuck trying to send JSON data to by Struts2 REST server using the struts2-rest-plugin.
It works with XML, but I can't seem to figure out the right JSON format to send it in.
Anybody has any experience with this?
Thanks,
Shaun
Update:
Sorry I wasn't clear. The problem is that Struts2 doesn't seem to be mapping the JSON data I send in to my model in the controller.
Here's the code:
Controller:
public class ClientfeatureController extends ControllerParent implements ModelDriven<Object> {
private ClientFeatureService clientFeatureService;
private ClientFeature clientFeature = new ClientFeature();
private List<ClientFeature> clientFeatureList;
//Client ID
private String id;
public ClientfeatureController() {
super(ClientfeatureController.class);
}
#Override
public Object getModel() {
return (clientFeatureList != null ? clientFeatureList : clientFeature);
}
/**
* #return clientFeatureList through Struts2 model-driven design
*/
public HttpHeaders show() {
//logic to return all client features here. this works fine..
//todo: add ETag and lastModified information for client caching purposes
return new DefaultHttpHeaders("show").disableCaching();
}
// PUT request
public String update() {
logger.info("client id: " + clientFeature.getClientId());
logger.info("clientFeature updated: " + clientFeature.getFeature().getDescription());
return "update";
}
public HttpHeaders create() {
logger.info("client id: " + clientFeature.getClientId());
logger.info("feature description: " + clientFeature.getFeature().getDescription());
return new DefaultHttpHeaders("create");
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public void setClientFeatureService(ClientFeatureService clientFeatureService) {
this.clientFeatureService = clientFeatureService;
}
public List<ClientFeature> getClientFeatureList() {
return clientFeatureList;
}
public void setClientFeatureList(List<ClientFeature> clientFeatureList) {
this.clientFeatureList = clientFeatureList;
}
public ClientFeature getClientFeature() {
return clientFeature;
}
public void setClientFeature(ClientFeature clientFeature) {
this.clientFeature = clientFeature;
}
}
This is the URL I'm making the request to:
..http://localhost:8080/coreserviceswrapper/clientfeature.json
-Method: POST or PUT (tried both, POST maps to create() and PUT maps to update())
-Header: Content-Type: application/json
Payload:
{"clientFeature":{
"feature": {
"id": 2,
"enabled": true,
"description": "description1",
"type": "type1"
},
"countries": ["SG"],
"clientId": 10}
}
And the output in the Struts2 logs when I make the request:
1356436 [http-bio-8080-exec-5] WARN net.sf.json.JSONObject - Tried to assign property clientFeature:java.lang.Object to bean of class com.foo.bar.entity.ClientFeature
1359043 [http-bio-8080-exec-5] INFO com.foo.bar.rest.ClientfeatureController - client id: null
Let me also add that XML requests work just fine:
URL: ..http://localhost:8080/coreserviceswrapper/clientfeature.xml
Method: POST/PUT
Content-Type: text/xml
Payload:
<com.foo.bar.entity.ClientFeature>
<clientId>100</clientId>
<feature>
<description>test</description>
</feature>
</com.foo.bar.entity.ClientFeature>
Output:
1738685 [http-bio-8080-exec-7] INFO com.foo.bar.rest.ClientfeatureController - client id: 100
1738685 [http-bio-8080-exec-7] INFO com.foo.bar.rest.ClientfeatureController - feature description: test
1738717 [http-bio-8080-exec-7] INFO org.apache.struts2.rest.RestActionInvocation - Executed action [/clientfeature!create!xml!200] took 1466 ms (execution: 1436 ms, result: 30 ms)

I also encounter same issue, my environment is:
Structs 2.3.16.3, Jquery 1.11, Struts-rest-plugin
symptom: post json data, rest controller not parse json data to model.
solution:
since the controller is modeldriven, browser client just post Json string is OK. but seems you have to force jquery to change conenttype of ajax call.
_self.update= function(model, callback) {
$.ajax({
beforeSend: function(xhrObj){
xhrObj.setRequestHeader("Content-Type","application/json");
xhrObj.setRequestHeader("Accept","application/json");
},
type: 'PUT',
url: this.svrUrl+"/"+ model.id + this.extension,
data: JSON.stringify(model), // '{"name":"' + model.name + '"}',
//contentType: this.contentType,
//dataType: this.dataType,
processData: false,
success: callback,
error: function(req, status, ex) {},
timeout:60000
});
};
the model data format is :
var model = {"id":"2",
"name":"name2",
"author":"author2",
"key":"key2"
}
when you put or post data whit "Content-Type"="application/json", the plugin will handle it with Jsonhandler automatically.

I got such a problem. Strange but got solved by changing the name 'clientFeature' to 'model'

Related

Nested json type won't serialize in jersey

This bounty has ended. Answers to this question are eligible for a +100 reputation bounty. Bounty grace period ends in 22 hours.
James Wierzba wants to draw more attention to this question.
I'm running a dropwizard/jersey java restful web app.
I have an endpoint that is defined like this in api.yaml:
swagger: '2.0'
info:
version: 0.0.1
basePath: /
schemes:
- https
- http
consumes:
- application/json
- application/x-protobuf
produces:
- application/json
- application/x-protobuf
paths:
/v1/event:
post:
summary: receive a event
operationId: receiveEvent ## this value names the generated Java method
parameters:
- name: event
in: body
schema:
$ref: "#/definitions/Event"
responses:
200:
description: success
schema:
type: object
$ref: '#/definitions/EventResponse'
definitions:
Stream:
properties:
vendor:
type: "string"
Event:
properties:
eventCity:
type: "string"
streams:
type: "array"
items:
$ref: "#/definitions/Stream"
EventResponse:
required:
- statusCode
properties:
statusCode:
type: "integer"
Endpoint is defined like so
#POST
#Consumes({ "application/json", "application/x-protobuf" })
#Produces({ "application/json", "application/x-protobuf" })
#Path("/event")
void receiveEvent(
#Suspended AsyncResponse response,
#Valid Event.EventModel event
);
When issuing json POST request, I cannot get the streams field to get serialized/deserialized property.
This is the payload
{
"eventCity": "San Diego",
"streams": [
{
"vendor": "CBS"
}
]
}
I test like this with curl
curl -X POST -H "Content-Type: application/json" -d '{"eventCity": "San Diego", "streams": [{"vendor": "CBS"}]}' https://localhost:8990/v1/event
In the server request handler:
#Override
public void receiveEvent(AsyncResponse response, Event.EventModel event) {
System.out.println(event.getEventCity());
System.out.println(event.getStreamCount()); // <-- this returns 0? why is the inner 'streams' list not getting serialized? it should have one element
}
And the output:
San Diego
0
Another observation, is that when I issue the same post, but with a protobuf payload, it works. The streams list is populated.
The protobuf was generated like so
// create proto
Stream.StreamModel stream = Stream.StreamModel.newBuilder()
.setVendor("CBS")
.build();
Event.EventModel event = Event.EventModel.newBuilder()
.setEventCity("San Diego")
.addStream(stream)
.build();
// write to file
FileOutputStream fos = new FileOutputStream("/Users/jameswierzba/temp/proto.out");
stream.writeTo(fos);
The output in the endpoint is as expected:
San Diego
1
This is the full generated code for the Event class: https://gist.github.com/wierzba3/84face6c21c4fb6ce554f90707ba6ef9
This is the full generated doe for the Stream class: https://gist.github.com/wierzba3/32664312df87c64049b281daab928f94
You can't use the same class as the payload for protobuf and json.
If you inspect the Event generated for protobuf processing you will notice that the the stream is stored as stream_ and has a setStream that returns a Builder as follows. A JSON deserialiser can't work with it:
/**
* <code>repeated .com.apple.amp.social.linearmasterplaylist.refresh.model.StreamModel stream = 2;</code>
*/
public Builder setStream(
int index, com.apple.amp.social.linearmasterplaylist.refresh.model.Stream.StreamModel value) {
if (streamBuilder_ == null) {
if (value == null) {
throw new NullPointerException();
}
ensureStreamIsMutable();
stream_.set(index, value);
onChanged();
} else {
streamBuilder_.setMessage(index, value);
}
return this;
}
A JSON deserialiser (probably jackson-databind) needs a definition of Event like this generated with openapi-generator-cli. In this case the stream and its setter look like this: (note that the #JsonProperty("streams") is redundant as the property is named streams)
#JsonProperty("streams")
#Valid
private List<Stream> streams = null;
and
public void setStreams(List<Stream> streams) {
this.streams = streams;
}
I have included the other model definitions here to allow you to try them with you controller to show that JSON is correctly consumed.
It is possible to write code to inspect the incoming media type and fork the processing. Eg the following generated by openapi-generator. I would recommend a separate controller method for each media type and some sort of mapping so that a single service layer call will suffice.
#RequestMapping(
method = RequestMethod.POST,
value = "/v1/event",
produces = { "application/json", "application/x-protobuf" },
consumes = { "application/json", "application/x-protobuf" }
)
default ResponseEntity<EventResponse> receiveEvent(
#Parameter(name = "event", description = "") #Valid #RequestBody(required = false) Event event
) {
getRequest().ifPresent(request -> {
for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
String exampleString = "{ \"statusCode\" : 0 }";
ApiUtil.setExampleResponse(request, "application/json", exampleString);
break;
}
if (mediaType.isCompatibleWith(MediaType.valueOf("application/x-protobuf"))) {
String exampleString = "Custom MIME type example not yet supported: application/x-protobuf";
ApiUtil.setExampleResponse(request, "application/x-protobuf", exampleString);
break;
}
}
});
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
}
On a separate note, but I doubt it will help: it looks like the protobuf is using stream (singular) (eg getStreamList()) and the swagger get uses streams (plural).
So, the issue turned out to be a bug in our companies internal implementation of the code generation module.
Workflow looks like this: api.yaml specified -> .proto file generated -> .java code generated.
To be explicit. Using the schema in question. Note I changed the list to have name myStreamArrayInput to illustrate the issue at hand.
Event:
properties:
eventCity:
type: "string"
myStreamArrayInput:
type: "array"
items:
$ref: "#/definitions/Stream"
This would generate a proto like this:
message Event {
optional string eventCity = 1;
/*
this name should be `myStreamArrayInput` or something
close to it. but it is using the name of the TYPE
instead of the name specified by the dev in api.yaml
*/
repeated StreamModel stream;
}
And then this would generate a java class like this
public final class Event {
public static final class EventModel extends
com.google.protobuf.GeneratedMessageV3 implements EventModelOrBuilder {
private EventModel() {
eventCity_ = "";
stream_ = "";
}
}
}
YMMV, since this is an bug in our own internal code generation engine
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.List;
#Path("/myresource")
public class MyResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getJson() {
ObjectMapper mapper = new ObjectMapper();
List<MyObject> myObjects = new ArrayList<>();
MyObject myObject = new MyObject("foo", new NestedObject("bar"));
myObjects.add(myObject);
String json;
try {
json = mapper.writeValueAsString(myObjects);
} catch (Exception e) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
}
return Response.ok(json, MediaType.APPLICATION_JSON).build();
}
private static class MyObject {
private String name;
private NestedObject nestedObject;
public MyObject(String name, NestedObject nestedObject) {
this.name = name;
this.nestedObject = nestedObject;
}
#JsonProperty("name")
public String getName() {
return name;
}
#JsonProperty("nested_object")
public NestedObject getNestedObject() {
return nestedObject;
}
}
private static class NestedObject {
private String value;
public NestedObject(String value) {
this.value = value;
}
#JsonProperty("value")
public String getValue() {
return value;
}
}
}

ERROR - Bad request; check the error message for details

I created API with spring. When I call that API then show error
Here is the Controller class.
#RequestMapping(value={"/dPIUsagePackageInfo"},method=RequestMethod.POST)
public ResponseEntity<DPIUsagePackageInfoRs> dPIUsagePackageInfo(#RequestBody List<DPIUsagePackageInfoRq> dPIUsagePackageInfoRq){
//
DPIUsagePackageInfoRs response = this.ccbsBusiness.dPIUsagePackageInfo(dPIUsagePackageInfoRq);
return new ResponseEntity(response, response.getStatus());
}
Here is the Request class
public class DPIUsagePackageInfoRq {
private List<String> srvName;
public List<String> getSrvName() {
return srvName;
}
public void setSrvName(List<String> srvName) {
this.srvName = srvName;
}
}
I passed this json body
{
"dPIUsagePackageInfoRq" : {
"srvName": ["xxx","rrr","rrrrr"]
}
}
But response like this
{
"resultCode": "000400",
"resultDesc": "ERROR - Bad request; check the error message for details."
}
Where is the wrong with my code.Thanks in advanced.
You are sending only one DPIUsagePackageInfoRq instance where you should be sending it in a list. Please try adding "[" and "]" to start and end of your body so that it becomes a list.
The request which you are sending should be as follows:
[
{
"srvName": ["xxx","rrr","rrrrr"]
}
]
and if you would like to send multiple DPIUsagePackageInfoRq objects, you can use increment the objects like this:
[
{
"srvName": ["xxx","rrr","rrrrr"]
},
{
"srvName": ["xxx","rrr","rrrtrr"]
}
]

Retrofit Put Request for Mlab

Hi I'm trying to send a PUT request using Retrofit that uses $addToSet to my Mlab Server. I can do this using Postman but I'm having trouble doing it using Retrofit.
The collection looks like:
[
{
"_id": {
"$oid": "5abe74bac2ef1603f4045686"
},
"email": "test#gmail.com",
"completedWalks": [
"South Leinster Way"
],
"favWalks": []
}
]
The post man request has the API key, Query, and then $addToSet is passed in the body as so.
And the response is:
I'm trying to do it like this in android.
Retrofit:
#PUT("databases/walks/collections/user")
Call<Update> addCompleted (#Query("apiKey") String apiKey,#Query("q") String Email, #Body Update Query);
My model:
public class Update {
#SerializedName("n")
private String n;
public String getN() {
return n;
}
public Update(String n) {
this.n = n;
}
}
Creating the update object:
String updateComplete = String.format("'$addToSet': {'completedWalks': '%s'}} ", TrailName);
final String query =String.format("{'email': '%s'}",email) ;
final Update queryComplete = new Update(updateComplete);
And the Request:
Call<Update> completeCall = apiService.addCompleted(mlabAPi, query, queryComplete);
completeCall.enqueue(new Callback<Update>() {
#Override
public void onResponse(Call<Update> call, Response<Update> response) {
Toast.makeText(getApplicationContext(),"Walk marked as Complete", Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(Call<Update> call, Throwable t) {
Log.e(TAG, t.getMessage());
}
});
But this only overwrites whats in the collection and I have:
[
{
"_id": {
"$oid": "5abe74bac2ef1603f4045686"
},
"n": "'$addToSet': {'completedWalks': 'Kerry Head Cycleway'}} "
}
]
Does anyone know where I'm going wrong, should I not be passing $addToSet as a model because it seems to be overwriting all, how do I pass it then?
Thank You.
#Body Update Query -- Retrofit will encode the object passed to this as JSON (assuming you are using the Gson converter, which it appears you are). That is where this "n": "'$addToSet': {'completedWalks': 'Kerry Head Cycleway'}} " is coming from. You need to structure you Java Object the same as your JSON object for gson to serialize it correctly.
I am not familiar with the mlab api, but from your postman, it looks like you want a request body something like this --
public class UpdateRequest {
#SerializedName("$addToSet")
Map<String, String> addToSet = new HashMap();
}
Update your interface to send this object as the body --
#PUT("databases/walks/collections/user")
Call<Update> addCompleted (#Query("apiKey") String apiKey,#Query("q") String Email, #Body UpdateRequest Query);
And create the request body --
UpdateRequest requestBody = new UpdateRequest();
requestBody.addToSet.put("completedWalks", Trailname);
and create the call --
Call<Update> completeCall = apiService.addCompleted(mlabAPi, query, requestBody);
For further debugging, you can see what is actually being sent in your logcat by adding HttpLoggingInterceptor to your retrofit instance.
See here for setup. Then you can compare what your app is sending vs postman and see where things might be going sideways.

Spring mvc parse object using jackson?

My bean class ,
import java.util.LinkedList;
public class Information {
private LinkedList<String> information ;
public LinkedList<String> getInformation() {
return information;
}
public void setInformation(LinkedList<String> information) {
this.information = information;
System.out.println("List is : "+information);
}
}
My controller ,
#RequestMapping(value="/registerDn.html",method=RequestMethod.POST)
public #ResponseBody Information registerDn( #RequestParam(value = "dn", required = false) String dn,
#RequestParam(value = "acd", required = false) String acd ){
System.out.println("DN is : "+dn+ " acd : "+acd);
WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(context);
UserOperation oper = (UserOperation)ctx.getBean("operation");
oper.registerDn(dn);
Information info = (Information) ctx.getBean("info");
return info;
}
My jquery will be ,
function registerDn(){
$.ajax({
type: "POST",
url: contexPath + "/registerDn.html",
data: "dn=" + dn + "&acd=" + acd,
success: function(response){
var userInfo = "<ol>";
for( i =0 ; i < response.information.length ; i++){
userInfo += "<br><li><b>Name</b> : " + response.information[i] ;
}
userInfo += "</ol>";
//Used to display the list
$('#getlist').html(dn + " is : " + userInfo);
},
error: function(e){
alert('Error: ' + e);
},
});
}
I get the Success in the jquery-ajax.But dont know how to parse it and show it in the view.
or How to get the list in the bean class using jquery-ajax when I click a button.
Good answers are definitely appreciated.
There is nothing in your sample code that says the response should be / is expected to be a JSON object.
Jackson jars have to be on the class path, so that the MappingJackson HttpMessageConverter is configured in your application's context
jQuery ajax config should have dataType defined as json or use the produces attribute of the #RequestMapping annotation and set the value as application/json
Basically, if Spring knows that the response content type should be json, it will use Jackson Mapping Converter to convert your pojo to JSON, and then your jQuery success callback will get a JSON object.
You should see exactly what is happening with FireBug though.

JavaScript - how to display error message from backend system based on Spring MVC

I have a web application with HTML / jQuery which ic connected with AJAX / JSON to a backend system with Java EE / Spring MVC.
In the frontend, a Person can be created by fill in the form fields and then it is submitted and this jQuery code executed:
var person = $(this).serializeObject();
$.postJSON("add/", person, function(data) {
alert("Person with ID "+data.person.id+"' added successfully");
});
In the best case, the Person is created and I'll get a Person object and I can access the values with data.person.*.
Now I want to validate the data which is sent to the backend system and in a case of an error, I want to display in the first step an alert error message.
I did this in the backend system:
#RequestMapping(value="add/", method=RequestMethod.POST)
public #ResponseBody Map<String, ? extends Object> addPerson(#RequestBody Person p, HttpServletResponse response) {
Set<ConstraintViolation<Person>> failures = validator.validate(p);
if (!failures.isEmpty()) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return validationMessages(failures);
} else {
Person person = this.personService.addPerson(p);
return Collections.singletonMap("person", new SerialPerson(person.getId(), person.getName(), ...));
}
}
// internal helpers
private Map<String, String> validationMessages(Set<ConstraintViolation<Person>> failures) {
Map<String, String> failureMessages = new HashMap<String, String>();
for (ConstraintViolation<Person> failure : failures) {
failureMessages.put(failure.getPropertyPath().toString(), failure.getMessage());
System.out.println(failure.getPropertyPath().toString()+" - "+failure.getMessage());
}
return failureMessages;
}
My Person object is annotated, and I get the System.out.println(failure.getPropertyPath().toString()+" - "+failure.getMessage()); on the console, that for example, "name - must be between 1-30 chars"
But how can create an alert message in jQuery in the frontend system?
Thank you in advance for your help & Best Regards.
Update: Link to the Spring MVC AJAX example, where I found the validationMessages method. But there is also no solution how to get the error message.
SOLUTION:
I have to call:
jQuery.ajax({
'type': 'POST',
'url': "add/",
'contentType': 'application/json',
'data': JSON.stringify(person),
'dataType': 'json',
'success': function(data) {alert("success");},
'error': function(xhr) {alert(xhr.responseText);}
});
You can do something like this:
var person = $(this).serializeObject();
$.postJSON("add/", person, function(data) {
if(data.person) {
alert("Person with ID "+data.person.id+"' added successfully");
}
else {
var errors = "";
for(var key in data) if(data.hasOwnProperty(key)) {
errors += data[key] + "\n";
}
alert(errors);
}
});
You shouldn't need to send back a bad request either. Is this what you want?
UPDATE
You can use the code shown in Spring Source, but you'd have to use jQuery.ajax
jQuery.ajax({
type: 'POST',
url: "add/",
data: person,
dataType: "json",
success: function(data) {
alert("Person with ID "+data.person.id+"' added successfully");
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
var errorJSON = JSON.parse(XMLHttpRequest.responseText); //if this is JSON otherwise just alerting XMLHttpRequest.responseText will do
var errors = "";
for(var key in errorJSON) if(errorJSON.hasOwnProperty(key)) {
errors += errorJSON[key] + "\n";
}
alert(errors);
}
});

Categories