REST service that checks complex list - java

I want to write a REST service with JAX-RS that consumes a list of modules in the form
[
{
"name": "IRGENDWAS.TLK",
"version": "020",
"bibliothek": "asdf"
},
{
"name": "IRGENDWAS2.TLK",
"version": "030",
"bibliothek": "asdf"
},
{
"name": "XIRGENDWAS2.TLK",
"version": "030",
"bibliothek": "asdf"
}
]
and checks their existence, returning something like
[
{
"name": "IRGENDWAS.TLK",
"version": "020",
"bibliothek": "asdf",
"existence": true
},
{
"name": "IRGENDWAS2.TLK",
"version": "030",
"bibliothek": "asdf",
"existence": true
},
{
"name": "XIRGENDWAS2.TLK",
"version": "030",
"bibliothek": "asdf",
"existence": false
}
]
My best try so fat looks like this
#POST
#Path("/bs2-existence-check")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public List<Bs2ModulExistence> bs2ExistenceCheck(String modulListe)
{
#SuppressWarnings("unchecked")
List<String> nameList = JsonPath.parse(modulListe)
.read("$.[*].name", List.class);
#SuppressWarnings("unchecked")
List<String> versionList = JsonPath.parse(modulListe)
.read("$.[*].version", List.class);
#SuppressWarnings("unchecked")
List<String> bibliothekList = JsonPath.parse(modulListe)
.read("$.[*].bibliothek", List.class);
List<Bs2ModulExistence> backList = new ArrayList<>();
for (int i = 0; i < nameList.size(); i++)
{
String name = nameList.get(i);
String version = versionList.get(i);
String bibliothek = bibliothekList.get(i);
boolean existence = checkExistence(name, version, bibliothek);
Bs2ModulExistence bs2ModulExistence =
new Bs2ModulExistence(name, version, bibliothek, existence);
backList.add(bs2ModulExistence);
}
return backList;
}
I guess that my usage of JsonPath is unnecessary here and I can just map this somehow directly. Furthermore, I don't know whether this should be a POST request.
Probably someone can tell me how to do this correctly.

in order for your service to automatically marshal and unmarshal Java Objets to and from Json you have to specify a special parameter to your Jersey serlvet configuration (obviously this will be in the web.xml file). This parameter is com.sun.jersey.api.json.POJOMappingFeature and will basically integrate Jersey with Jackson.
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
Create a POJO Module with attributes "name","version","bibliothek", "existence"
and try change your method to :
#POST
#Path("/bs2-existence-check")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public List<Bs2ModulExistence> bs2ExistenceCheck(List<Module> modulListe)
after you can iterate through moduleListe to check an update existance
return moduleListe.stream().map(m -> {
boolean existence = checkExistence(m.name, m.version, m.bibliothek);
m.existance = existence;
return m;
}).collect(Collectors.toList());

Related

Openapi generator angular not generating multipart formdata endpoint correctly. (useForm is false by default)

This is the endpoint I have:
#PostMapping(value = "/file-upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public List<FileReference> handleFileUpload(
#RequestPart(value = "file", name = "file") MultipartFile[] file, #ApiIgnore HttpSession session) {
return service.handleFileUpload(
Arrays.stream(file).map(MultipartFileWithUUID::new).collect(Collectors.toList()),
session);
}
This is the generated endpoint in the swagger.json (swagger 2.0):
...
"post": {
"tags": [
"damage-report-controller"
],
"summary": "handleFileUpload",
"operationId": "handleFileUploadUsingPOST",
"consumes": [
"multipart/form-data"
],
"produces": [
"application/json"
],
"parameters": [
{
"name": "file",
"in": "formData",
"required": false,
"type": "array",
"items": {
"type": "file"
},
"collectionFormat": "multi"
}
],
...
And here is the generated function:
public handleFileUploadUsingPOST(file?: Array<Blob> ...) {
let headers = this.defaultHeaders;
header settings etc...
// to determine the Content-Type header
const consumes: string[] = [
'multipart/form-data'
];
const canConsumeForm = this.canConsumeForm(consumes);
let formParams: { append(param: string, value: any): any; };
let useForm = false;
...
if (useForm) {
formParams = new FormData();
} else {
formParams = new HttpParams({encoder: this.encoder});
}
...
}
The error I have is 415: Unsupported media type.
I don't know how it should be generated correctly, but I changed let useForm; to true and it works,
so my guess that let useForm = canConsumeForm(consumes) because canConsumeForm returns a boolean.
What should I change so it gets generated correctly?
In case anyone reads this, I haven't found the proper solution using swagger 2.0, so I updated to openapi 3.0 and that fixed the problem.
Apparently swagger 2.0 doesn't support uploading an array of files, even though the only problem was that the generated service didn't use existing functions properly.

Spring Boot Modify Default JSON response

I have a REST controller that returns a list of products like so:
Current output
[
{
"id":1,
"name":"Money market"
},
{
"id":2,
"name":"Certificate of Deposit"
},
{
"id":3,
"name":"Personal Savings"
}
]
In order to get things working with our JS grid library, I need the modify the response to look like:
Desired output
{ "data" :
[
{
"id":1,
"name":"Money market"
},
{
"id":2,
"name":"Certificate of Deposit"
},
{
"id":3,
"name":"Personal Savings"
}
]
}
Controller
#RequestMapping(value = "/api/products", method = RequestMethod.GET)
public ResponseEntity<?> getAllProducts() {
List<Product> result = productService.findAll();
return ResponseEntity.ok(result);
}
Is there an easy way to modify the JSON response using native Spring libraries?
You can put result object into a Map with key "data" and value as result.
map.put("data", result);
Then return the map object from the rest method.
return ResponseEntity.ok(map);
Using org.json library:
JSONObject json = new JSONObject();
json.put("data", result);
The put methods add or replace values in an object.

How to serialize java.util.Date to timespan with Jersey Mapper?

I'm developing a web service that relies on Jersey servlet, and uses Jersey json converter.
I have a PatientDTO that has a field java.util.Date birthDate
Here are my methods
#GET
#Path("/{param}")
#Produces(MediaType.APPLICATION_JSON)
public Response getPatient(#PathParam("param") String id) {
PatientDTO patientDTO;
PatientManager manager = new PatientManagerFacade();
patientDTO = manager.getPatientById(id);
if (patientDTO == null) {
return Response.status(204).build();
}
return Response.status(200).entity(patientDTO).build();
}
#POST
#Path("/add")
#Consumes(MediaType.APPLICATION_JSON)
public Response addPatient(PatientDTO patient) {
PatientManager manager = new PatientManagerFacade();
String id = manager.addPatient(patient);
return Response.status(200).entity(id).build();
}
The problem is the following
For my POST method I'm giving the following json, and it gets parsed just fine, the timespan gets casted to java.util.Date
{
"firstName": "John",
"lastName": "Smith",
"birthDate": 722725200000,
"gender": "MALE",
"age": 18
}
But when I'm calling the return method, it gives me the following json
{
"id": "NM-001",
"firstName": "John",
"lastName": "Smith",
"birthDate": "1992-11-26",
"gender": "MALE",
"age": 23
}
But I want the birthDate to be a timespan too. How do I achieve this? Any annotations telling actually how to parse the field, or something like that?
Thanks in advance

Reading HTTP headers in a Spring REST controller

I am trying to read HTTP headers in Spring based REST API. I followed this. But I am getting this error:
No message body reader has been found for class java.lang.String,
ContentType: application/octet-stream
I am new to Java and Spring so can't figure this out.
This is how my call looks like:
#WebService(serviceName = "common")
#Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
#Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public interface CommonApiService {
#GET
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
#Produces(MediaType.APPLICATION_JSON)
#Path("/data")
public ResponseEntity<Data> getData(#RequestHeader(value="User-Agent") String userAgent, #DefaultValue ("") #QueryParam("ID") String id);
}
I have tried #Context: HTTPHeader is null in this case.
How to get values from HTTP headers?
The error that you get does not seem to be related to the RequestHeader.
And you seem to be confusing Spring REST services with JAX-RS, your method signature should be something like:
#RequestMapping(produces = "application/json", method = RequestMethod.GET, value = "data")
#ResponseBody
public ResponseEntity<Data> getData(#RequestHeader(value="User-Agent") String userAgent, #RequestParam(value = "ID", defaultValue = "") String id) {
// your code goes here
}
And your REST class should have annotations like:
#Controller
#RequestMapping("/rest/")
Regarding the actual question, another way to get HTTP headers is to insert the HttpServletRequest into your method and then get the desired header from there.
Example:
#RequestMapping(produces = "application/json", method = RequestMethod.GET, value = "data")
#ResponseBody
public ResponseEntity<Data> getData(HttpServletRequest request, #RequestParam(value = "ID", defaultValue = "") String id) {
String userAgent = request.getHeader("user-agent");
}
Don't worry about the injection of the HttpServletRequest because Spring does that magic for you ;)
I'm going to give you an example of how I read REST headers for my controllers. My controllers only accept application/json as a request type if I have data that needs to be read. I suspect that your problem is that you have an application/octet-stream that Spring doesn't know how to handle.
Normally my controllers look like this:
#Controller
public class FooController {
#Autowired
private DataService dataService;
#RequestMapping(value="/foo/", method = RequestMethod.GET)
#ResponseBody
public ResponseEntity<Data> getData(#RequestHeader String dataId){
return ResponseEntity.newInstance(dataService.getData(dataId);
}
Now there is a lot of code doing stuff in the background here so I will break it down for you.
ResponseEntity is a custom object that every controller returns. It contains a static factory allowing the creation of new instances. My Data Service is a standard service class.
The magic happens behind the scenes, because you are working with JSON, you need to tell Spring to use Jackson to map HttpRequest objects so that it knows what you are dealing with.
You do this by specifying this inside your <mvc:annotation-driven> block of your config
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapper" />
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
ObjectMapper is simply an extension of com.fasterxml.jackson.databind.ObjectMapper and is what Jackson uses to actually map your request from JSON into an object.
I suspect you are getting your exception because you haven't specified a mapper that can read an Octet-Stream into an object, or something that Spring can handle. If you are trying to do a file upload, that is something else entirely.
So my request that gets sent to my controller looks something like this simply has an extra header called dataId.
If you wanted to change that to a request parameter and use #RequestParam String dataId to read the ID out of the request your request would look similar to this:
contactId : {"fooId"}
This request parameter can be as complex as you like. You can serialize an entire object into JSON, send it as a request parameter and Spring will serialize it (using Jackson) back into a Java Object ready for you to use.
Example In Controller:
#RequestMapping(value = "/penguin Details/", method = RequestMethod.GET)
#ResponseBody
public DataProcessingResponseDTO<Pengin> getPenguinDetailsFromList(
#RequestParam DataProcessingRequestDTO jsonPenguinRequestDTO)
Request Sent:
jsonPengiunRequestDTO: {
"draw": 1,
"columns": [
{
"data": {
"_": "toAddress",
"header": "toAddress"
},
"name": "toAddress",
"searchable": true,
"orderable": true,
"search": {
"value": "",
"regex": false
}
},
{
"data": {
"_": "fromAddress",
"header": "fromAddress"
},
"name": "fromAddress",
"searchable": true,
"orderable": true,
"search": {
"value": "",
"regex": false
}
},
{
"data": {
"_": "customerCampaignId",
"header": "customerCampaignId"
},
"name": "customerCampaignId",
"searchable": true,
"orderable": true,
"search": {
"value": "",
"regex": false
}
},
{
"data": {
"_": "penguinId",
"header": "penguinId"
},
"name": "penguinId",
"searchable": false,
"orderable": true,
"search": {
"value": "",
"regex": false
}
},
{
"data": {
"_": "validpenguin",
"header": "validpenguin"
},
"name": "validpenguin",
"searchable": true,
"orderable": true,
"search": {
"value": "",
"regex": false
}
},
{
"data": {
"_": "",
"header": ""
},
"name": "",
"searchable": false,
"orderable": false,
"search": {
"value": "",
"regex": false
}
}
],
"order": [
{
"column": 0,
"dir": "asc"
}
],
"start": 0,
"length": 10,
"search": {
"value": "",
"regex": false
},
"objectId": "30"
}
which gets automatically serialized back into an DataProcessingRequestDTO object before being given to the controller ready for me to use.
As you can see, this is quite powerful allowing you to serialize your data from JSON to an object without having to write a single line of code. You can do this for #RequestParam and #RequestBody which allows you to access JSON inside your parameters or request body respectively.
Now that you have a concrete example to go off, you shouldn't have any problems once you change your request type to application/json.
Instead of taking the HttpServletRequest object in every method, keep in controllers' context by auto-wiring via the constructor. Then you can access from all methods of the controller.
public class OAuth2ClientController {
#Autowired
private OAuth2ClientService oAuth2ClientService;
private HttpServletRequest request;
#Autowired
public OAuth2ClientController(HttpServletRequest request) {
this.request = request;
}
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<String> createClient(#RequestBody OAuth2Client client) {
System.out.println(request.getRequestURI());
System.out.println(request.getHeader("Content-Type"));
return ResponseEntity.ok();
}
}

How to set http header in Json response

I've a CXF RESTful service which returns both XML and Json format. I need to add a custom http header in the RESTful service. Here's a sample code snippet.
#GET
#Path("/test")
#Produces("application/xml")
public Response test(
#QueryParam("p") String var
{
TestRequest req = new TestRequest();
req.setVar(var);
TestResponse res = p.getData(req);
return Response.ok(res).header("Result", res.getResult()).build();
}
The above code shows the XML response which sets the custom http header "Result". I'm able to see the new http header in the response header. So far so good.
Now, here's the Json version which internally calls the testService() method to get the result, then use google Gson API to send the result back. This has been working well, till I decided to return the new header. Here's the code snippet.
#GET
#Path("/test/jsonp")
public String testJSONP(
#QueryParam("p") String var,
#QueryParam("cb") String callBack
{
Response resp = test(var);
XStream xs = new XStream(new JsonHierarchicalStreamDriver());
xs.setMode(XStream.NO_REFERENCES);
xs.alias("TestResponse", TestResponse.class);
StringBuilder sb = new StringBuilder();
sb.append(callBack);
sb.append("(");
GsonBuilder gb = new GsonBuilder();
gb.registerTypeAdapter(XMLGregorianCalendar.class, new XMLGregorianCalenderSerializer());
gb.setPrettyPrinting();
Gson gson = gb.create();
sb.append(gson.toJson(resp));
sb.append(")");
return sb.toString();
}
I'm not able to see the http header in Json response.
Any feedback will be highly appreciated.
-Thanks
UPDATE
I added the following code in Json method for my testing.
#GET
#Path("/test/jsonp")
public String testJSONP(
#QueryParam("p") String var,
#QueryParam("cb") String callBack
{
Response resp = test(var);
XStream xs = new XStream(new JsonHierarchicalStreamDriver());
xs.setMode(XStream.NO_REFERENCES);
xs.alias("TestResponse", TestResponse.class);
StringBuilder sb = new StringBuilder();
sb.append(callBack);
sb.append("(");
GsonBuilder gb = new GsonBuilder();
gb.registerTypeAdapter(XMLGregorianCalendar.class, new XMLGregorianCalenderSerializer());
gb.setPrettyPrinting();
Gson gson = gb.create();
sb.append(gson.toJson(resp));
sb.append(")");
return Response.ok(sb.toString(), MediaType.APPLICATION_JSON).header("Result", "50").build();
}
This sets the header value correctly,but the issue is the Json response format seems to have changed. Since this is an existing service, I'm not allowed to do that.
Here's the existing response format
null({
"status": "Completed",
"totalResult": "252",
"bin": [
{
"type": "source",
"value": "documentation",
"ndocs": "243"
},
{
"type": "source",
"value": "wikihelp",
"ndocs": "6"
},
"entries": {
"item": [
{
"url": "http://test.com/test.htm",
"title": "\u003cspan class\u003d\"vivbold qt0\"\u003eXREF\u003c/span\u003e",
"snippet": " Test data.",
"source": "documentation",
"type": "html",
"shortDescription": "Starts the TEST command.",
"category": [
"User"
],
"publishDate": "2012-02-05T12:00:00-0500",
"lastUpdateDate": "2012-03-14T12:00:00-0400",
"topicId": "GUID-7DD70C3C-B8AD-40F1-8A69-5D1EECEAB013"
}
]
}
})
Here's the response after adding this change
null({
"status": 200,
"entity": {
"status": "Completed",
"totalResult": "252",
"bin": [
{
"type": "source",
"value": "documentation",
"ndocs": "243"
},
{
"type": "source",
"value": "wikihelp",
"ndocs": "6"
}
],
"entries": {
"item": [
{
"url": "http://test.com/test.htm",
"title": "\u003cspan class\u003d\"vivbold qt0\"\u003eXREF\u003c/span\u003e",
"snippet": " Test data.",
"source": "documentation",
"type": "html",
"shortDescription": "Starts the TEST command.",
"category": [
"User"
],
"publishDate": "2012-02-05T12:00:00-0800",
"lastUpdateDate": "2012-03-14T12:00:00-0700",
"topicId": "GUID-7DD70C3C-B8AD-40F1-8A69-5D1EECEAB013"
}
]
}
},
"metadata": {
"Result": {
}
}
})
You need to change signature of your method, to return an instance of Response class, instead of a String, and then built the response manually.
From the CXF wiki page:
#Path("/example")
public ExampleResource {
#GET
public Response getSomething() {
return Response.ok(/* some entity */).header("CustomHeader", "CustomValue").build();
}
}
Update
You can also inject HttpServletResponse into your handler using #Context annotation like this:
#Path("/example")
public class Welcome {
#GET
public String getSomething(
#QueryParam("p1") String param1,
#QueryParam("p2") String param2,
#Context HttpServletResponse response) {
response.addHeader("CustomHeader", "CustomValue");
return "my awesome response";
}
}
Note, that there is a CXF-1498 bug in versions prior to 2.1 that causes HttpServletResponse not being injected, so you need a newer CXF version.

Categories

Resources