I have an endpoint I created using spring.io. My GetMapping declaration can be seen below
#ApiOperation(
value = "Returns a pageable list of CustomerInvoiceProducts for an array of CustomerInvoices.",
notes = "Must be authenticated.")
#EmptyNotFound
#GetMapping({
"customers/{customerId}/getProductsForInvoices/{invoiceIds}"
})
public Page<CustomerInvoiceProduct> getProductsForInvoices(
#PathVariable(required = false) Long customerId,
#PathVariable String[] invoiceIds,
Pageable pageInfo) {
//Do something fun here
for (string i: invoiceIds){
//invoiceIds is always empty
}
}
Here is how I am calling the url from postman and passing the data.
http://localhost:8030/api/v1/customers/4499/getProductsForInvoices/invoiceIds/
{
"invoiceIds": [
"123456",
"234566",
"343939"
]
}
My string array for invoiceIds is always empty in the for loop Nothing gets passed to the array. What am I doing wrong?
The mapping you are using is this:
customers/{customerId}/getProductsForInvoices/{invoiceIds}
Both customerId and invoiceIds are Path variables here.
http://localhost:8030/api/v1/customers/4499/getProductsForInvoices/invoiceIds/
The call you are making contains customerId but no invoiceIds. Either you can pass the list in place of invoiceIds as String and read it as a String and then create a List by breaking up the List - which will be a bad practice.
Other way is to change your path variable - invoiceId to RequestBody.
Generally, Path Variables are used for single id or say navigating through some structured data. When you want to deal in a group of ids, the recommended practice would be to pass them as RequestBody in a Post method call rather than a Get method call.
Sample code snippet for REST API (post calls):
Here, say you are trying to pass Employee object to the POST call, the REST API will look like something below
#PostMapping("/employees")
Employee newEmployee(#RequestBody Employee newEmployee) {
//.. perform some operation on newEmployee
}
This link will give you a better understanding of using RequestBody and PathVariables -
https://javarevisited.blogspot.com/2017/10/differences-between-requestparam-and-pathvariable-annotations-spring-mvc.html
https://spring.io/guides/tutorials/rest/
Related
I have the below code as my restful service operation.
#GET
#UnitOfWork
#Timed(name = "get-requests")
#Path("/{referenceId}")
public Response get(#Auth #ApiParam(access = "internal") UserPrincipal user,
#ApiParam(name = "id", value = "reference ID", required = true)
#PathParam("referenceId") String id) {
return Response.ok(id).build();
}
However, I noticed if I pass in m1234;5678, I get only m1234 returned. I tried #Path("/{referenceId:.*}"), but it doesn't work.
I also tried use #Encode at the top of the method to make sure the url is not decoded and then try to replace %3B with ";" in the code. But it seems not working also.
Please note that I cannot use Spring framework. Thanks.
The ; denotes a matrix parameter. Use #MatrixParam to get its value.
See also the answers to this question: URL matrix parameters vs. request parameters
Edit: The key of the matrix parameter would be 5678, the value would be null.
There is a way to get achieve what you want by using PathSegment as the type of the parameter instead of String:
#PathParam("referenceId) PathSegment id
In the body of the method, you can use
String idValue = id.getPath();
to get m1234;5678.
I am trying to implement datatable editor with spring boot ,but the client to server data varies for create ,update and delete even not constant for create as well and depends on fields
I have implemented this till now
#RequestMapping(value="/datatabledata" , method=RequestMethod.POST)
#ResponseBody
public String datatabledata(HttpServletRequest request)
{
Enumeration<String> params = request.getParameterNames();
while(params.hasMoreElements()){
String paramName = params.nextElement();
System.out.println("Parameter Name - "+paramName+", Value - "+request.getParameter(paramName));
}
//System.out.println(data);
//System.out.println(request.);
//Map<String,String>ak=new HashMap<>();
//ak.put("data", "hello ");
return "done";
}
Above code prints following output on console for create
Parameter Name - action, Value - create
Parameter Name - data[0][username], Value - dddddd
Parameter Name - data[0][date], Value - 2018-11-28
Parameter Name - data[0][balance], Value - dddddddddd
and this for edit
Parameter Name - action, Value - edit
Parameter Name - data[5bfab595507af613f409c0c4][username], Value - four
Parameter Name - data[5bfab595507af613f409c0c4][date], Value - 2018-11-25
Parameter Name - data[5bfab595507af613f409c0c4][balance], Value - 9000.0
The only constant parameter here is action and so I can use
#RequestParam("action")
but how to get rest data ?? something like #RequestParam() String data
You can create a DTO class which can be mapped from the request and can be used further.
#RequestMapping(value="/datatabledata" , method=RequestMethod.POST)
#ResponseBody
public String datatabledata(HttpServletRequest request)
{
UserDTO object = new ObjectMapper().setDateFormat(simpleDateFormat).readValue(request.getReader(), UserDTO.class);
performYourOperation(object);
}
I see we should utilize REST in more richer way here.
So create three different controller method handlling create, update and delete and maps them to difference HTTP methods like below :
//For Create. Take the parameter as (#RequestBody List<User>)
#RequestMapping(value="/datatabledata" , method=RequestMethod.POST)
//For Update/Edit, Take the parameter as (#RequestBody List<User>)
#RequestMapping(value="/datatabledata" , method=RequestMethod.PUT)
//For Delete, Just take either list of ids or id to be delete. Nothiing else required
#RequestMapping(value="/datatabledata" , method=RequestMethod.DELETE)
Now you don't need action as parameter. Client just need to specify the correct http method.
You should use #RequestParam Map<String,String> allRequestParams in your endpoint:
#RequestMapping(value="/datatabledata" , method=RequestMethod.POST)
#ResponseBody
public String datatabledata(#RequestParam Map<String,String> allRequestParams) {
/ ... rest of your code
}
I have a form with following sections ( following example is for understanding purpose)
GeneralInformation - it 's an object with Cityname (String) and population(int)
Location Information: It's an object with locationCode (int) and Nearest HospitalName(String)
Companies: It's an object with company details. There is list of companies dynamically added
with Company as object. Basically List
Hospitals: it's like List
// generalInfo - populated from form
//locationInfo - populated from form
//companiesArr[] // this is dynamicallypopulated (each row each object) companies array
// hospitalsArr[] // // this is dynamicallypopulated (each row each object) Hospitals array
//Angular code starts..
controller('addGeneralController', function($scope, close,Service) {
$scope.companiesArr = [];
$scope.comapnyName='';
$scope.companyType='';
$scope.hospitalsArr = [];
$scope.hospitalName='';
$scope.locationCode='';
$scope.generalInfo = {};
$scope.locationInfo = {};
$scope.companies = {};
$scope.hospitals = {};
$scope.dataInfo = {};//this is to carry entire objects and arrays
//Following method calls after populating data from form and submit.
//companiesArr,hospitalsArr are populated from front end and passing as submission parameters
$scope.saveGeneral = functio(generalInfo,locationInfo,companiesArr,hospitalsArr){
$scope.companies = companiesArr;
$scope.hospitals = hospitalsArr;
//Create an empty array
//$scope.dataInfo = [];
$scope.dataInfo.push({'generalInfo' : generalInfo, 'locationInfo' : locationInfo,'companies' : $scope.companies,'hospitals' : $scope.hospitals});
$http.post("/addGeneralData",$scope.dataInfo);
});
//Angular code ends..
It's not reaching to the following Spring MVC method:
#RequestMapping(value = "/addGeneralData", method = RequestMethod.POST)
public #ResponseBody String addGeneralData(#RequestBody List<Data> dataInfo){
// not reaching here.With simple parametrs it's reaching here, so no other mapping issue apart from this complex data
// Data - is an object with generalInfo as object,locationInfo as object, //companies List ,hospitals List as it's attributes.
Data data = dataInfo.get(0);
GeneralInfo generalInfo = data.getgeneralInfo();
LocationInfo locationInfo = data.getLocationInfo();
List<Company> companies = data.getCompanies();
List<Hospital> hospitals = data.getHospitals();
}
Basically I want to know how can I transfer this complex data from angular controller to Spring MVC controller?
Please share the request sent from the browser to comment more
It certainly looks like you are sending DataInfo Object but receiving
List dataInfo in your controller. there is a mismatch.
Change the Signature of the handler method
to public #ResponseBody String addGeneralData(#RequestBody DataInfo dataInfo)
Thanks for your response.It's worked when I changed to array instead of list. I have changed all lists inside the Data objects also to array. In addition to that make sure that all data passing from input is as per the type mentioned in the concrete object. For example any data mentioned as int , make sure it's passing int only. If it is complex form and before input validation we are integrating front end with backend, make sure all data we passed exactly as the type mentioned in the mapping object.Is it good practice to use array as parameter in MVC controller?
Do you have any exception ?
It is very likely that you get Serialization Exception due to List interface passed as parameter to controller. Spring just can not initialize new instance of List. Try to use array instead of List. For example
#RequestMapping(value = "/addGeneralData", method = RequestMethod.POST)
public #ResponseBody String addGeneralData(#RequestBody Data[] dataInfo){
Data data = dataInfo[0];
GeneralInfo generalInfo = data.getgeneralInfo();
LocationInfo locationInfo = data.getLocationInfo();
Company[] companies = data.getCompanies();
Hospital[] hospitals = data.getHospitals();
}
Be sure that you use concrete implementations, not interfaces in your Data object.
Hope it helps
I have to write a REST Service method that accepts a list of objects as parameter to calculate something and return a result.
I have so far:
#RequestMapping(value = "generateBill/{id}/{rates}")
public String generateBill(#PathVariable Long id, #PathVariable Rate rates[]) {
// do things
return "OK";
}
But I am sure that #PathVariable Rate rates[] is wrong.
I have to write the client part too and I also have no idea how to do it. It is the first time I have to write such a REST Service method.
Edit: Rate looks like this:
public class Rate {
Long version;
Double amount;
Date validFrom;
Date validUntil;
}
You should put your objects in the body of a POST request instead of using the URL :
#RequestMapping(value = "generateBill/{id}", method = RequestMethod.POST)
public String generateBill(#PathVariable Long id, #RequestBody BillingRequest billingRequest) {
// do things
}
Also, mapping directly a collection in the payload is not evolvable (you cannot add new "fields" outside the array), it's generally a good practice to wrap your array in a JSON object :
public class BillingRequest {
List<Rate> rates;
// Here you could add other fields in the future
}
Your HTTP request to call your service would look like this :
POST / HTTP/1.1
{
"rates" : [
{
"version" : 1,
"amount" : 33.3,
"validFrom" : "2016-01-01",
"validUntil" : "2017-01-01"
},
{
"version" : 2,
"amount" : 10.0,
"validFrom" : "2016-02-01",
"validUntil" : "2016-10-01"
}
]
}
One last piece of advice about your model :
Use java.time.LocalDate (or jodatime) instead of java.util.Date. If you need date+time, use java.time.ZonedDateTime (DateTime if you use jodatime)
Use java.math.BigDecimal to represent exact numbers. Floating point numbers like Double can lose precision
EDIT : I would suggest using Instant rather than ZonedDateTime, which is a timestamp with UTC timezone. Unless of course your domain requires different timezones.
First solution:
#RequestMapping(value = "generateBill/{id}/{rates}", method=RequestMethod.GET)
public String generateBill(#PathVariable Long id, #PathVariable Rate[] rates) {
// do things
return "OK";
}
Or second one (more Java style;) ):
#RequestMapping(value = "generateBill/{id}/{rates}", method=RequestMethod.GET)
public String generateBill(#PathVariable Long id, #PathVariable List<Rate> rates) {
// do things
return "OK";
}
This one you can call like this:
GET: http://localhost:8080/public/generateBill/1/1,2,3,4
Where 1.2,3,4 replace with your values, it depends od that what exactly is Rate ;)
EDIT
After your update, it looks like that you want to have POST method (you are posting list of rates) and then here is already answered question.
receiving json and deserializing as List of object at spring mvc controller
Other solution is to use JSON String format as a parameter and parse it afterwards. Something like
[
{
"rates":1,
"name":"rate1"
},
{
"rates":2,
"name":"rate2"
},
{
"rates":3,
"name":"rate3"
}
]
and after that parse the json to your object.
With Deadbolt's module we can check the restrictedResource with a ressource name and parameters in the view.
For example in my view, I have it, and it works well:
#{deadbolt.restrictedResource resourceKeys:['Domain'] , resourceParameters:['domainid':domain.id]}
<li>${domain.title}</li>
#{/deadbolt.restrictedResource}
But in my controller, I just can check the ressource name but I don't find a way to check it in my RestrictedResourcesHandler passing the domainid with.
I am looking for a solution to do something like that:
#RestrictedResource(name = {"Domain"}, params = {domainid})
public static void showDomain(String domainid)
{
}
Thanks in advance
It's not possible to have dynamic information in an annotation, but you can use params to define the name of an incoming value in the request. However, this information isn't passed into the handler at the moment because it expects a map. While you can pass in a map of parameters from the restrictedResource tag, you can't do this from an annotation so an empty map is passed into the handler.
Your best approach here is to pull a well-known parameter name from the request object. I need to have a rethink about the best way to do this without breaking backwards compatibility.
Steve (author of Deadbolt)
I've found a way the solved the problem, not the best I think, but it is the Steve Chaloner's solution (Deadbolt's creator), and it works.
For example, if your Controller's method argument is named "id", and you want to check this id inside your checkAccess method :
// Controller's method :
#RestrictedResource(name = {"Domain"})
public static void showDomain(String id){}
Just check at the beginning of your checkAccess method the Map "resourceParameters" is empty, and use the request object to get the parameters:
public AccessResult checkAccess(List<String> resourceNames,
Map<String, String> resourceParameters)
{
Map<String, String> hashm = new HashMap<String,String>();
if(resourceParameters != null && !resourceParameters.isEmpty()){
hashm = resourceParameters;
}else if(Http.Request.current().routeArgs!= null && !Http.Request.current().routeArgs.isEmpty()){
hashm = Http.Request.current().routeArgs;
}
}
Then just have to foreach your hashmap inside your checkAccess method to get your Controller's method argument and check the Access as you wish.
for (Map.Entry<String,String> mp : hashm.entrySet())
{
// Get the id argument
if(mp.getKey().equals("id"))
{
// Do something with the value..
mp.getValue()
}
}