RESTEasy client: reconstructing an object - java

I'm playing with RESTEasy to consume REST services, and I'm trying it out with Twitter's search API.
So I create this interface:
public interface SimpleClient {
#GET
#Path("search.json")
#Produces("application/json")
ClientResponse<Set<String>> getSearchResults(
#QueryParam("q") String hashtag,
#QueryParam("result_type") String resultType
);
}
and called it with:
SimpleClient client =
ProxyFactory.create(SimpleClient.class,"http://search.twitter.com/");
ClientResponse<Set<String>> response =
client.getSearchResults("#wowodc","recent");
System.out.println(response.getEntity(Set.class));
But I'm getting:
ClientResponseFailure: Unable to find a MessageBodyReader of content-type application/json;charset="utf-8" and type interface java.util.Set
I have tried using a POJO instead of java.util.Set, but I'm getting the same kind of exception. The only thing that didn't throw an exception is using String instead of Set.
By reading some example code on the Web, I was thinking that Set or a POJO as the entity type would have work, but it doesn't for me. The query to Twitter did return valid results.

You need to make sure you include a RESTEasy provider that can unmarshal JSON responses. There's a one based on the Jackson parser library that you can use, it's described in the docs here.

Related

How to convert JSON in QueryParams into Object in Java Spring?

I have this query
http://localhost:8555/list/csv?search={}
Where search is a json object (omitted other params as they are irrelevant here).
How can i convert this into a nested object?
public record CsvParams<T>(
T search,
/* Other query params */ ) {}
Right now im getting error that string cannot be cast into object.
class java.lang.String cannot be cast to class classname
Is there anyway to do this? Old solution uses ObjectMapper to convert string into corresbonding object. I was hoping that maybe there is a way to do it more simpli and remove this boilerplate.
Any single value of a query param can't be automatically converted to a non-primitive type. You can convert multiple params to a class, but not one that happens to be a JSON AFAIK. But you can create a converter custom deserialiser and then use it in different controllers, but in the end you'd still use an ObjectMapper.
More info on how to do the latter here: https://www.baeldung.com/spring-mvc-send-json-parameters
If you have to work with query params than I don't think you can have it converted automatically by Spring boot. But if you work with POST or PUT methods and can pass your params as request params in request body your JSON params can be automatically converted to class instances by Spring boot and no effort required from you. However, if you have to work with query param (say you have to use method GET so you have no request body) than you can use Json-Jackson library or Gson library to parse your Json into class instance. If you use Jackson you will need to use class ObjectMapper. For Jackson lib info see this site, for ObjectMapper class see Javadoc here. However, I wrote my own JsonUtils that is very good for simple usecases like yours. It allows to to parse simple JSON into a class with a single method. It is very simple and strait forward. It is a thin wrapper over Jackson library. See the Javadoc for method readObjectFromJsonString. Class JsonUtils is part of Open Source MgntUtils library. You can get it as Maven artifact on Maven Central and as a jar (with source code and Javadoc) on Github

How to annotate a request body that is oneOf external models?

I'm working on documenting an API made with RESTeasy + Jackson in Java using Swagger/OpenAPI (version 1.5.18 - I did add in v3 OAS 2.0.1 to try oneOf/anyOf). One of the endpoints takes in a String as a request body, which is then transformed into one of several classes. The documentation needs to display each of these models so that users can see them. The models are defined in another project. Is there a way to do this through annotations? The closest thing I've found is adding #RequestBody(content=#Content(schema=#Schema(oneOf= {class1.class, class2.class}))) but haven't been able to get it to add the model using that. I also tried adding a dummy class with #ApiModel(subTypes={class1.class, class2.class}. I don't want to add additional endpoints for each object type due to code maintainability.
My question is: is it possible to add the models through annotations while leaving the input type as String?
Here is the relevant code:
#POST
#Path("/{filetype}/new")
#Consumes("application/json")
public Response writeFile(
#ApiParam(required=true, allowableValues = "class1, class2") #PathParam("filetype") String filetype,
#RequestBody(content=#Content(schema=#Schema(oneOf= {class1.class, class2.class}))) String inputFile
) {
return validateFileAndSaveToServer(filetype, inputFile);
}

RestEasy - Jax-rs - Sending custom Object in response body

How do I send my custom object in a response. I just want the values printed from my object.
Lets say I have an object of type Person. I am trying to send in REST response body like this.
ResponseBuilder response = Response.ok().entity(personObj);
return response.build();
But I get 500 error.
Tried this one too:
ResponseBuilder response = Response.status(Status.OK).entity(personObj);
return response.build();
Same error.
Tried setting content type as text/xml. No use.
What am I missing here? I tried googling. But not many examples out there, especially with the custom objects;
It returns fine, if I just pass a string to entity() method.
In order to return data from a Resteasy resource method you need to do several things depending on what you are trying to return.
You need to annotate your resource method with the #Produces
annotation to tell Resteasy what the return type of the method should
be.
For example, the method below returns XML and JSON depending on what the client asks for in their Accept header.
#GET
#Produces({MediaType.APPLICATION_JSON,
MediaType.APPLICATION_XML})
public Response foo()
{
PersonObj obj = new PersonObj();
//Do something...
return Response.ok().entity(obj).build();
}
Resteasy supports marshalling the following datatypes by default:
If the datatypes you wish to support are in this table then that
means they are supported by JAXB and all you need to do is annotate
your PersonObj class with JAXB annotations to tell it how to
marshall and unmarshall the object.
#XmlRootElement
#XmlType(propOrder = {"firstName", "lastName"})
public class PersonObj
{
private String firstName;
private String lastName;
//Getters and Setters Removed For Brevity
}
What if your content-type is not supported out of the box?
If you have a custom content-type that you would like to marshall then you need to create a MessageBodyWriter implementation that will tell Resteasy how to marshall the type.
Provider
#Produces({"application/x-mycustomtype"})
public class MyCustomTypeMessageBodyWriter implements MessageBodyWriter {
}
Just implement the interface and register it like any other Provider.
If you would like to read a custom content-type then you need to implement a custom MessageBodyReader to handle the incoming type and add it to the #Consumes annotation on your receiving method.

Jersey JAX-RS Client XML to java.util.List deserialization

I am trying to access a JAX-RS Service (jersey implementation) which is returning me a java.util.list of Employees in XML format.
The Service method signature looks like this:
#GET
#Path("/getEmployeeListXML")
#Consumes(MediaType.TEXT_PLAIN)
#Produces(MediaType.APPLICATION_XML)
public List<EmployeeXML> getEmployeeListXML(#QueryParam("id") String id){
//Some code which returns a List<EmployeeXML>
}
The format of the XML returned is like this:
<employeeXMLs>
<employeeXML>
<empId>1</empId>
<empName>John</empName>
<empAge>35</empAge>
<empSex>Male</empSex>
</employeeXML>
<employeeXML>
<empId>2</empId>
<empName>Lisa</empName>
<empAge>23</empAge>
<empSex>Female</empSex>
</employeeXML>
</employeeXMLs>
For accessing this from my jersey Client, I am using this code:
List<EmployeeXML> empListXML = (List<EmployeeXML>)service.path("rest").path("GetService").path("getEmployeeListXML").accept(MediaType.APPLICATION_XML).get(EmployeeXML.class);
This is not correct since the return type should be a list but in the get method, presently I am trying to retrieve a single object. I am not sure how to retrieve the List from the client here :(
I am getting this exception:
unexpected element (uri:"", local:"employeeXMLs"). Expected elements are <{}employeeListXML>,<{}employeeXML>
Please help me out to make this work.
Thanks,
You can use the GenericType class to fetch a list of objects:
List<EmployeeXML> empListXML = (List<EmployeeXML>)service.path("rest").path("GetService").path("getEmployeeListXML").accept(MediaType.APPLICATION_XML).get(new GenericType<List<EmployeeXML>>(){});
You need to use a 'supertype token' to define the return type in your client class:
List<EmployeeXML> empListXML = service
.path("rest")
.path("GetService")
.path("getEmployeeListXML")
.accept(MediaType.APPLICATION_XML)
.get(new GenericType<List<EmployeeXML>>() {});
The supertype token is required in order to 'retain' generic parameter information that Jersey will use when deserializing the server response.

Converting #RequestBody to an object

Guys, Well I have done enough research still I can't find the solution to this.
In a nutshell, I'm simply passing url encoded form data to the Controller method and trying to convert it as a domain object which has Date and integers.
#RequestMapping(value = "/savePassport", method = RequestMethod.POST)
public #ResponseBody
AjaxResponse savePassport(#RequestBody StaffPassport passport, HttpServletResponse response) {
// Some operations.
}
The Staff Passport looks like this:
import java.sql.Date;
public class StaffPassport {
private int staffId;
private String passportNumber;
private String placeOfIssue;
private Date issueDate;
private Date expiryDate;
private String spouseName;
private String oldPassportRef;
private String visaInfo;
private String description;
//gets/sets
}
When I invoke the /savePassport, I get unsupported media exception. I guess it's related to casting.
I can't this working right. Of course I can catch individual form data using #RequestParam and manually do the casting but that's not the point of a framework isn't it?
Where am I going wrong? And you are right. I'm a beginner in Spring, but I love it.
Looks like you're using the wrong annotation. #RequestBody is for taking a request that has arbitrary content in its body,such as JSON, some application defined XML, comma separated variables.. whatever. And using a marshaller that you configure in the dispatcher servlet to turn it into objects.
If all you want to do is ask Spring to bind a plain old form post onto the backing object for you, the correct annotation to put on the method parameter is #ModelAttribute.
If you are posting a JSON Object with jQuery and you want Spring to be able to process it with #RequestBody, use JSON.stringify(....) in your data. Here an example:
var data = { "id": 3, "name": "test" }
$.post("processJsonData.html",JSON.stringify(data), function(data){
...
}
);
If you don't use the JSON.stringify() then you will submit the data as form data and Spring will tell you that you have an unsupported media type.
First of all be sure that you have
<mvc:annotation-driven />
in your Spring configuration file. This is mandatory for working with JSOn in SPring MVC.
Second, I recommend you to test wether request to the server has application/json content type. I belive Fiddler2 will help you to do so.
Third, but I'm not sure about it, Try to change Date items in your POJO from SQL type to regular java type.
UPDATE:
just looked at the Form and it seems like your "Accept" HTTP Header should be also application/json. Please test this issue with Fiddler2 as well.
I assume that you are posting JSON and want Spring to convert to StaffPassport. If you are getting an Unsupported media exception, it is because Spring could not figure out an appropriate way to perform the conversion.
For Spring to convert JSON, it needs Jackson -- make sure you have the Jackson jars in your project. If this is a Maven based project you can add the jackson-mapper-asl artifact ID to your pom.xml. This should give you the jackson-mapper and jackson-core jars.
Edit: I should mention that this applies to Spring 3 (I recently ran into this problem). I'm not sure what else is required for previous versions of Spring.
Check into HttpMessageConverter interface and its implementations. You could write your own implementation of it to convert it to the domain model you want. By the time the control gets to your method, you can access it as if your domain model object is passed.
Ok, I think I should refine my answer. I do not have direct experience of using it in a spring-mvc project but spring-integration. I am pretty sure the applicable media type (application/x-url-form-encoded) is already handled and converted to MultiMap by Spring framework; so, retrieve the values from that just like any other map with the key value being your form variable and populate your business model.
HTH.

Categories

Resources