Accessing Data from JSON body inside Apache Camel - java

I am working with an API which basically allows for the navigation of a file-system. I am trying to access data from within the returned JSON by the API in order to perform a function on it.
Below is the code I am using the access the API. I have tried to use unmarshal to
convert the JSON returned to a Map.
from("timer://foo?fixedRate=true&period=120000")
.log("Checking for files")
.setHeader("Authorization", simple(myHttp.getAuth()))
.setHeader("CamelHttpMethod", constant("GET"))
.to(myHttp.getFullRequest())
.unmarshal(new JacksonDataFormat(Map.class)).log("${body}");
Which returns this data to me:
{
objects=[
{
name=file1.csv,
type=file
},
{
name=dir1,
type=directory,
},
{
name=dir2,
type=directory
},
{
name=dir3,
type=directory
},
{
name=dir4,
type=directory
}]
}
I want to access the array under "objects" in order to check whether any files exist inside this directory. So far, I only tried to log the data under objects and therefore I have used this code:
.unmarshal(new JacksonDataFormat(Map.class)).log("${body.objects}");
Using ${body.objects}, I'm still unable to access the data inside the MAP. I expected something like this to be returned:
[{
name=file1.csv,
type=file
},
{
name=dir1,
type=directory,
},
{
name=dir2,
type=directory
},
{
name=dir3,
type=directory
},
{
name=dir4,
type=directory
}]
but instead I get this error:
Method with name: objects not found on bean: {objects=[{name=file1.csv,type=file},{name=dir1,type=directory,},{name=dir2,type=directory},{name=dir3,type=directory},{name=dir4,type=directory}]} of type: java.util.LinkedHashMap. Exchange[ID-IT1289-1529914067957-0-1]
Am I accessing the returned MAP after using unmarshall incorrectly? What is the correct syntax I must I use if so?
I have seen other examples of unmarshalling... but I cannot understand completely. I've noticed many examples use a class with the structure of the JSON. Is this necessary? If my body is currently of type: java.util.LinkedHashMap, I expect it shouldn't be a problem to access but I cannot seem to find the way.
Thanks in advance

The best way to do this is to create a class matching your Json Structure.
class RestResponse {
List<FileNameType> objects;
//Getters and Setters
}
class FileNameType {
String name;
String type;
//Getters and setters.
}
Then change your route like this
from("timer://foo?fixedRate=true&period=120000")
.log("Checking for files")
.setHeader("Authorization", simple(myHttp.getAuth()))
.setHeader("CamelHttpMethod", constant("GET"))
.to(myHttp.getFullRequest())
.unmarshal().json(JsonLibrary.Jackson, RestResponse.class)
.to(....);
The last part I have left it blank, here you can add a processor to verify your logic. You can get the RestResponse object from exchange.getIn().getBody(). Some thing like this will do
........
.unmarshal().json(JsonLibrary.Jackson, RestResponse.class)
.process(exchange -> {
RestResponse response = exchange.getIn().getBody(RestResponse.class);
// Do your logic here.
})
You might need to add this dependency
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jackson</artifactId>
<version>yourcamelversion</version>
</dependency>

Related

Why is my controller calling different API when there is no match API for the request?

I am working on a Spring boot project, it produces strange behaviors, for ex:
I have two APIs as follow
Controller file
#GetMapping("/list/employees")
public ResponseEntity<List<Employee>> getEmployees(){
List<Employee> list = employeeService.getAllEmployees();
return new ResponseEntity<List<Employee>>(list, new HttpHeaders(), HttpStatus.OK );
}
#GetMapping("employee/{id}")
public ResponseEntity<Employee> getEmployeeById(#PathVariable("id") long id) throws RuntimeException{
Employee employee = employeeService.getEmployee(id);
return new ResponseEntity<Employee>(employee,new HttpHeaders(),HttpStatus.OK);
}
Service file
/* return all employees */
public List<Employee> getAllEmployees(){
List<Employee> listEmployee = employeeRepo.findAll();
if(listEmployee.size()>0){
return listEmployee;
}else{
return new ArrayList<Employee>();
}
}
/*
RETURN SINGLE EMPLOYEE BY ID
*/
public Employee getEmployee(long id) throws RuntimeException{
Optional<Employee> employee = employeeRepo.findById(id);
if(employee.isPresent()){
return employee.get();
}else{
new RuntimeException("Record not found");
}
return null;
}
But running them in Postman gives weird output, for ex:
Correct behavior of second API returning single employee
http://127.0.0.1:8080/employee/3
{
"id": 3,
"firstName": "Caption",
"lastName": "America",
"email": "cap#marvel.com"
}
Incorrect behavior of the same API (I am typing the wrong path this time)
http://127.0.0.1:8080/employees/3
The API path is wrong (employees/3)
{
"firstName": "Caption",
"lastName": "America",
"email": "cap#marvel.com",
"_links": {
"self": {
"href": "http://127.0.0.1:8080/employees/3"
},
"employee": {
"href": "http://127.0.0.1:8080/employees/3"
}
}
}
same behavior with the root URI, I have not triggered any action with home URI but still gives output like in the above API.
what can be the reason for these unwanted API calls?
Looks like you have Spring Data Rest on your class path. It will automatically wire paths based on the repositories. That second response is a HATEOAS response.
A simple test would be to check maven/gradle. If you see spring-data-rest, comment it out and try again.
There is no unwanted API calls. That is the way HATEOS response is represented as stated in the documentation:
The fundamental idea of hypermedia is to enrich the representation of a resource with hypermedia elements. The simplest form of that are links. They indicate a client that it can navigate to a certain resource. The semantics of a related resource are defined in a so-called link relation.
As suggested above, try to look for spring boot hateos dependency and comment or remove that, then it should revert back to normal REST JSON response.
If you are using maven, look for :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
if you are using gradle, look for :
implementation 'org.springframework.boot:spring-boot-starter-hateoas'

Missing request param when it is included in body

I am posting a POJO where I get an error saying the field is not included.
Asset POJO
public class Asset {
private MultipartFile[] files;
private String name;
private String meta;
//Constructor/Getters n Setters
}
Resource Method
#PostMapping("asset")
public ResponseEntity uploadAsset(#RequestParam("asset") Asset asset) {
System.out.println(asset);
return new ResponseEntity(HttpStatus.ACCEPTED);
}
PostMan JSON Body
{
"asset" : {
"files": [
"#/home/Downloads/1.jpeg",
"#/home/Downloads/2.jpeg"
],
"name": "assetName",
"meta": "assetMeta"
}
}
PostMan JSON Response
{
"timestamp": "2019-10-29T20:46:19.536+0000",
"status": 400,
"error": "Bad Request",
"message": "Required Asset parameter 'asset' is not present",
"path": "/asset"
}
I don't understand why I get the Required Asset parameter 'asset' is not present message when I have it in the JSON body. Any ideas on this?
Use #RequestBody rather than #RequestParam
public ResponseEntity uploadAsset(#RequestBody Asset asset) {
Based on your payload, Spring is expecting an object that looks like this:
public class SomeClass {
private Asset asset;
}
Change your payload to look like this:
{
"files": [
"#/home/Downloads/1.jpeg",
"#/home/Downloads/2.jpeg"
],
"name": "assetName",
"meta": "assetMeta"
}
RequestParam
Annotation which indicates that a method parameter should be bound to a web request parameter.
RequestBody
Annotation indicating a method parameter should be bound to the body of the web request. The body of the request is passed through an HttpMessageConverter to resolve the method argument depending on the content type of the request. Optionally, automatic validation can be applied by annotating the argument with #Valid.
HttpMessageConverter
Strategy interface that specifies a converter that can convert from and to HTTP requests and responses.
You need to check converter dependency. because you using application/json.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
Q : Missing request param when it is included in body
A : Use #RequestBody annotation.
I tried #Jordans answer and the endpoint was called with all values set to null :(
Doing more research I came across this statement https://stackoverflow.com/a/51982230/2199102 and tried it out.
Combining #Jordans answer and then the annotation change, I was able to get the answer I wanted

Restservices call with params

I am working on an app written in java and angular. The request goes from angular with params and I need to read the params in the rest services. The angular piece is fixed (I cannot change it), but I can update the java code as needed.
My issue: I cannot read the params in my java services method. The value is always null.
angular code:
alert("params : "+JSON.stringify(params, null, 2));
return $http.get(url, {
params: params
}).success(function (data) {
// success criteria
});
The params looks like this based on the alert statement:
{
"start": 0,
"end": 100,
"type": "asdf",
"name": null,
"values": [],
"locs": [],
"test1": "Val"
}
My java code looks like this:
#GET
#Path("valuesList")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.TEXT_XML)
public String getValues(#RequestParam("type")String type) throws Exception {
System.out.println("type ================"+type);
}
The type is null. I want to read all the values, but for now I am just using the type for testing. I also tried various #Consumes values, but nothing helps.
I think I need to change the #RequestParam, but not sure to what though. Thanks for any pointers.
Just wanted to post the solution - I had to update the #RequestParam to #QueryParam. The updated method call is below
public String getValues(#QueryParam("type")String type) throws Exception {
System.out.println("type ================"+type);
}

Access RequestBody inside Spring Portlet Controller using #ResourceMapping and #ModelAttribute

My problem is similar to the following posts:
JSON ajax POST to Spring Portlet Controller #ResourceMapping conversion issue and #ResourceMapping that accepts JSON from Ajax request
I have tried the Tipps there, but without success.
I have the following Technologies in place:
liferay-portal 6.2 CE
custom portlet-plugin for liferay based on spring 3.0.7
kendo-ui for jsp
On the client-side I produce a stringified json-Object with the functionality of kendo-ui for jsp, which is submitted in the request body. Currently it contains just some filter-parameters (but it can also contain additional parameters for server-side paging, sorting, grouping,..).
In Firefox developer tools the request-body (payload) looks like following:
{
"filter" : {
"logic" : "and",
"filters" : [{
"field" : "name",
"value" : ""
}, {
"field" : "city",
"value" : ""
}, {
"field" : "zip",
"value" : ""
}, {
"field" : "country",
"value" : ""
}
]
}
}
On the server-side I have a POJO for that structure. I tested this in a Spring Web MVC Servlet enviroment with success. Using #RequestBody and Jackson the deserialization of the JSON Object works.
Working in liferay-portlet enviroment I cannot use #RequestBody and httpServletRequest.
The Controller looks like following:
#ResourceMapping(value = "test")
public void searchProviderTest(ResourceRequest request, ResourceResponse response,
#ModelAttribute("filter") DataSourceRequest dataSourceRequest) {
LOGGER.info(">>>>>> JsonOjekt per Parameter übergeben: " + request.getParameter("filter"));
LOGGER.info(">>>>>>>> DatasourceRequest: " + dataSourceRequest);
}
The DataRequestObject has no values. I see all the attributes, but they are empty. And there is no request parameter "filter" (as exspected)
Here is my DataSourceRequest-Object (abstract):
public class DataSourceRequest {
private int page;
private int pageSize;
private int take;
private int skip;
private List<SortDescriptor> sort;
private List<GroupDescriptor> group;
private List<AggregateDescriptor> aggregate;
private HashMap<String, Object> data;
private FilterDescriptor filter;
public DataSourceRequest() {
filter = new FilterDescriptor();
data = new HashMap<String, Object>();
}
...(getters and setters)
public static class FilterDescriptor {
private String logic;
private List<FilterDescriptor> filters;
private String field;
private Object value;
private String operator;
private boolean ignoreCase = true;
public FilterDescriptor() {
filters = new ArrayList<FilterDescriptor>();
}
...(getters and setters)
Im am searching for a solution since a few weecks, but I do not get the JSON-Object converted (deserialized?) to the DataSourceRequest-Object using the portlet-controller. I even do not have an idea how to access the JSON-String in the request-body (payload) from the portlet-controller.
Following the second mentioned post, the nested objects might be the problem. I contacted the kendo-ui support with the question, how I can submit the request to get the format described in the post. But they told me, that is not possible (e.g. using parameterMap-attribute of the datasource object)and I have to solve it on the server-side.
The first post describes a solution with #ModelAttribute, but then I get only the empty object-and when I try to get the JSON with #RequestParam I get an error, that the parameter is not in the request (I think because it is in the body)
I was thinking about setting up an additional RESTFul API, based on Spring Web MVC Servlet - I even tried it and it works- but I am not sure if that is really meaningful, because liferay already has a RESTFul -API.
Is there a way to convert the JSON Object to an JAVA Object inside the Portlet Controller ? or Do I need the additional API?
Any tips are welcome!!
I had the same problem while serializing and deserializing Json with Liferay. The solution for me was to send the json as a parameter in a form-data. That way a I was able to retrive the Json with the following method:
String paramJson = ParamUtil.getString(request, "myJson");
And then make use of Gson api to deserialize:
new Gson().fromJson(paramJson, MyPOJO.class);
You won't have so many troubles with Gson.
You can also use Gson to serialize objects in the return of your services, this will avoid problems with nested objects witch Liferay doesn't serialize properly.
This code show how to send the Json as request body:
The request will be processed by method 'serveResource' in a MVCPortlet.
var portletUrl = Liferay.PortletURL.createResourceURL();
portletUrl.setPortletId(<portletId>);
portletUrl.setResourceId('publicar'); // Any identifier
var formData = new FormData();
formData.append(<portlet-namespace> + 'myJson', JSON.stringify(object));
var xhr = new XMLHttpRequest();
xhr.addEventListener('load', callbackSuccess, false);
xhr.open('POST', urlPortlet);
xhr.send(formData);
To share my experience hera are the steps:
in JS set contentType to application/x-www-form-urlencoded. Thats the code for kendo-ui (uses jQuery Ajax in Background)
<kendo:dataSource-transport-parameterMap>
function parameterMap(options,type) {
if(type==="read"){
return "osdeFilter=" + encodeURIComponent(JSON.stringify(options));
} else {
return "osdeModels=" + encodeURIComponent(JSON.stringify(options.models));
}
}
</kendo:dataSource-transport-parameterMap>
Get the Parameter and in my case use Jackson manualy to deserialize the JSON String
#ResourceMapping(value = "test")
public void searchProviderTest(ResourceRequest request, ResourceResponse response)
throws JsonParseException, JsonMappingException, IOException {
String osdeFilter = URLDecoder.decode(request.getParameter("osdeFilter"),"UTF-8");
LOGGER.info(">>>>>> JsonOjekt per Parameter übergeben: " + request.getParameter("osdeFilter"));
ObjectMapper objectMapper = new ObjectMapper();
DataSourceRequest dataSourceRequest = objectMapper.readValue(osdeFilter, DataSourceRequest.class);
LOGGER.info(">>>>>>>> DatasourceRequest: " + dataSourceRequest);
}
Differ from #giovani I do not need to submit the portlet-namespace. To achieve that, you must add the following configuration to liferay-portlet.xml
<requires-namespaced-parameters>false</requires-namespaced-parameters>

Getting "malformed syntax" while hitting REST Controller POST method sending "Request Body"

Here is the Rest Controller Service signature
#RequestMapping(value = "/getFilteredReport", method = RequestMethod.POST)
public void getFilteredReport(#RequestBody FilteredReportVO filteredReportVO,HttpServletRequest request,HttpServletResponse response) {
Now below is the JSON structure I am sending
{
"filterAttributesFactory":{
"930000":{
"metaDataId":930000,
"displayText":"Select Category",
"attributeType":211009,
"userInputValue":null,
"dropDownoptions":null,
"isMandatory":false,
"isDropDown":false,
"defaultValue":null,
"isdefaultValue":false,
"constraintId":null
},
"930001":{
"metaDataId":930001,
"displayText":"Item Status",
"attributeType":211005,
"userInputValue":null,
"dropDownoptions":{
"157005":"FC - fake scrap",
"157006":"FH - firearm hold",
"157008":"IN - inventory"
},
"isMandatory":false,
"isDropDown":true,
"defaultValue":null,
"isdefaultValue":false,
"constraintId":213007
}
},
"reportId":132030,
"location":1202
}
Here is the FilteredReportVO POJO
public class FilteredReportVO {
private HashMap<Integer,FilterAttributeVO> filterAttributesFactory=new HashMap<Integer,FilterAttributeVO>();
private Integer reportId;
private Long location; .....GETTERS and setters below
FilterAttributeVO pojo structure is below..
public class FilterAttributeVO {
Integer metaDataId;
//String elementName;
String displayText;
Integer attributeType;
Object userInputValue;
Map<Integer,String> dropDownoptions;
Boolean isMandatory=false;;
Boolean isDropDown=false;
Object defaultValue;
Boolean isdefaultValue=false;
Integer constraintId=null;...Getters n setters..
I am hitting the service through POSTMAN plugin.
Getting error:
"The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repeat the request without modifications".
FYI in POSTMAN I am putting the JSON structure inside "Body", selected "raw", type as "JSON(application/json)".
Notice I am using 2 object type attributes userInputValue and defaultValue inside FilteredAttributeVO. R we allowed to keep object type?
Where is the problem here?
If you see the screen shot of your input JSON with JSONlint your json is not valid.Please fix your json object and validate it using jsonlint
you can try following code in your Test to Resolve this issue coz Spring internally uses Jackson library
ObjectMapper mapper = new ObjectMapper();
Staff obj = mapper.readValue(jsonInString, FilteredReportVO.class);
If this works fine then their should be issue with your Postman RequestBody preparation otherwise you will get detailed stacktrace :)
The problem was with FilteredReportVO POJO. I was setting values of the attributes "location" and "reportId" through a constructor. No setter methods were defined for these 2 attributes.
It was my bad, if I would have posted the complete POJO class u guys must have figured it out. Anyway thanks everyone for ur help

Categories

Resources