I have a serach form of 2 types:
With pre-defined parameters count.
With unprecise parameters count, meaning that for each different entry type in my DB i will have different searchForm(which will consists of textfields mostly).
If the form number 1 is not so hard to do, then form number 2 is quite confusing
P.S. Currently I understand that this kind of form might be implemented with some kind of AJAX request, where JS transform data into JSON, but how to do it with regular post?
You can use the params map in the Controller to retrieve the values. See the documentation. For example:
public static void search() {
String name = params.get("name");
String[] tags = params.getAll("tags");
// ... etc ...
}
Related
I have a single ID REST API that I need to extend to support multiple (up to 10Ks) IDs. Basically to run update on all relevant IDs instead of sending 10Ks request in network.
Current endpoint:
#POST
#Path("{id}/update")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public ResponseVO updateBlockReason(#PathParam("id") int id, List<RequestVo> requestVo) {
One option suggested is comma-delimited values as stackexchange's answers-by-ids
Usage of /answers/{ids} GET
{ids} can contain up to 100 semicolon delimited ids. To find ids programmatically look for answer_id on answer objects.
This is the case on similar answers
http://our.api.com/Product/<id1>,<id2> :as James suggested can be an option since what comes after the Product tag is a parameter
But it seems awkward to me and RequestVo will be same for all IDs (which is currently is fine, but later to add such support will be harder)
It seems I need to change from Path variable to add it inside RequestVO
Which means the Id will be a JSON key, e.g.
[{
"id" : "1",
"name": "myAttribute"
"toggle": true
},
{
"id" : "2",
"name": "mySecondAttribute"
"toggle": false
}
]
Is this the correct approach or am I missing something?
Thank you in advance for any comments\answers
Current request VO
#Data
#AllArgsConstructor
#NoArgsConstructor
public class RequestVO {
private String name;
private boolean toggle;
// will add now private int id
}
My concern is also if I want (one of the requirement) to update with same request (as name=doA, toggle=true) for 10Ks Ids I'll have to duplicate request VO instead of sending ID separately
The best way is to keep id in your RequestVO DTO itself and not in URL as you have already suggested because even 100 ids in URL can make your URL very big and you are talking about 10K ids.
And again in future, the bit length of a single id may increase or later on you might need to update 50k or even 100K objects.
According to maximum length of a URL, there is no general specification on URL length but extremely long URLs are usually a mistake and URLs over 2,000 characters will not work in the most popular web browsers.
So I think your second approach is best here and will be good for future purposes also.
You may also want to use a PUT request because it makes more sense for an update request. So your code will become like this:
#PUT
#Path("/update")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public ResponseVO updateBlockReason(List<RequestVo> requestVo) {
I find the path product/{id}/update questionable, because you could achieve similar behavior by mapping #Put-request to product/{id} itself. The READ, WRITE differentiation is already explicit by the Request-mapping.
Also, whether or not using verbs in restful urls is a topic for itself.
Assuming you could use plural endpoints, this could look like /products/{id}.
Because you want to batch/bulk update products, you could map #Put-requests to /products now, with a list of updated Products in the RequestBody. Keep in mind, that this somewhat complicates the Response, as you may have to return Http-207 for answering the correct status of the update for each element in the list.
I want 1 logical endpoint for update
You can have a logical service method for this, but not endpoints really.
You already mentioned the problem of /{id} in your path for bulk updates.
If you really, really need to, I would remove the #Put-mapping from /products/{id} and redirect to /products where the update content would be a single element list, or a little more sophisticated, distinguished by a mediaType (what again means two endpints, but a single url).
Edit:
I just happen to understand the VO-issue. You are not updating Products, but parts of it (the name RequestVO was misleading me).
This smells like a #Patch-mapping to me, where parts of a Product get updated.
So I still would use /products but with a #Patch-mapping.
When a client needs to replace an existing Resource entirely, they can use PUT. When they’re doing a partial update, they can use HTTP PATCH.
This brings up another issue, use #Post only if the id is unknown (usually before something is CREATED and gets an id assigned, for UPDATES use #Put and reuse the assigned id)
Using post is technically doable, but because of idempotece not advisable.
Why not just pass the list of your IDs in the body of your request as JSON array? the code would be:
#POST
#Path("/update/ids")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public ResponseVO updateBlockReason(#RequestBody List<Integer> ids, List<RequestVo> requestVo) {
...
}
Is it possible to map query parameters with dynamic names using Spring Boot? I would like to map parameters such as these:
/products?filter[name]=foo
/products?filter[length]=10
/products?filter[width]=5
I could do something like this, but it would involve having to know every possible filter, and I would like it to be dynamic:
#RestController
public class ProductsController {
#GetMapping("/products")
public String products(
#RequestParam(name = "filter[name]") String name,
#RequestParam(name = "filter[length]") String length,
#RequestParam(name = "filter[width]") String width
) {
//
}
}
If possible, I'm looking for something that will allow the user to define any number of possible filter values, and for those to be mapped as a HashMap by Spring Boot.
#RestController
public class ProductsController {
#GetMapping("/products")
public String products(
#RequestParam(name = "filter[*]") HashMap<String, String> filters
) {
filters.get("name");
filters.get("length");
filters.get("width");
}
}
An answer posted on this question suggests using #RequestParam Map<String, String> parameters, however this will capture all query parameters, not only those matching filter[*].
You can map multiple parameters without defining their names in #RequestParam using a map:
#GetMapping("/api/lala")
public String searchByQueryParams(#RequestParam Map<String,String> searchParams) {
...
}
Does matrix variables work for you? If I understand you correctly, can be like this:
// GET /products/filters;name=foo;length=100
#GetMapping("/products/filters")
public void products(
#MatrixVariable MultiValueMap matrixVars) {
// matrixVars: ["name" : "foo", "length" : 100]
}
This seems like a solvable problem. The solutions are not ideal far as I know, but there are ways.
A previous attempt seemed bent on finding a perfect solution where the entire composition of the filter was known in-transit.
Spring MVC populate
The entirety of the dynamic criteria that user defines can be transmitted with some basic scheme you define, as one key=value parameter from the client, then decomposed into its elements once it is received.
You could also send two parameters: "fields" and "values", where the lists of each are encoded in there respectively, with some cautious delimiter of your choosing (could be an encoded special character that the user cannot physically type, perhaps).
You still need, as with everything other approach where the client side is submitting criteria (like filter criteria), full protection from any malicious use of the parameters, just as the client trying to embed SQL criteria in them (SQL Injection).
But so long as the client code follows the agreed syntax, you can receive any number of dynamic parameters from them in one shot.
Client:
/products?filter=field1--value1||field2--value2||field3--value3...
That is a simplified example showing delimiters that are too easy to "break", but the idea is some simple, even fully readable (no harm in doing so) scheme just for the purpose of packing your field names and values together for easy transit.
Server:
#RequestMapping(value = "/products", method = RequestMethod.GET)
public String doTheStuff(#RequestParam(value = "filter") String encodedFilter) {
.. decompose the filter here, filter everything they've sent for disallowed characters etc.
I'm trying to implement a method in a dropwizard resource, that will service a call from a JS frontend (that uses DataTables).
The request has query parameters that look like this:
columns[0][data]=0&columns[0][name]=&columns[0][searchable]=false&columns[0][orderable]=false&columns[0][search][value]=&columns[0][search][regex]=false
columns[1][data]=iata&columns[1][name]=iata&columns[1][searchable]=true&columns[1][orderable]=true&columns[1][search][value]=&columns[1][search][regex]=false
The request comes from a JS frontend implemented with DataTables, and uses server-side processing. Info about how datatables sends the requests here:
https://datatables.net/manual/server-side
I'm having issues defining the data type for the above query parameters. With spring data, we can define it as:
List<Map<String, String>> columns
which can be wrapped in an object annotated with ModelAttribute and it will deserialize fine.
In my app I'm using an older version of dropwizard which depends on jersey 1.19.
I've tried annotating it as a QueryParam, but the app fails at startup.
Method:
#Path("/mappings")
#GET
#Timed
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response getMappings(#QueryParam("columns") List<Map<String, String>> columns) {
// processing here.
}
When I do this, I get:
ERROR [2016-11-07 14:16:13,061] com.sun.jersey.spi.inject.Errors: The
following errors and warnings have been detected with resource and/or
provider classes: SEVERE: Missing dependency for method public
javax.ws.rs.core.Response
com.ean.gds.proxy.ams.application.resource.gui.IataMappingGuiResource.getMappings(java.util.List)
at parameter at index 0 WARN [2016-11-07 14:16:13,070] /: unavailable
My question is: do I have any option other than writing a custom deserializer for it ?
Note: If I grab the request with #Context, I can see that the decodedQueryParams are a MultivaluedMap, which maps String keys like "columns[0][data]" to Lists of String values, which always have a single element, that is the value.
Update:
After some digging, I found the following JAX-RS specification (section 3.2) which explains why my approach isn't valid to begin with:
The following types are supported:
Primitive Types
Types that have a constructor that accepts a single String argument.
Types that have a static method named valueOf with a single String argument.
List, Set, or SortedSet where T satisfies 2 or 3 above.
Source: Handling Multiple Query Parameters in Jersey
So I've tried using just a List instead. This doesn't crash the app at startup, but when the request comes in, it deserializes into an empty list. So the question remains as to what approach is correct.
In fact, you're using such a very different structure from all the common ones we have mapped for Rest Web Services consummation. Also, because of this structural compliance problem, trying to use JSON to marshal/unmarshal the values won't suit, once we haven't object-based parameters being transported.
But, we have a couple of options to "work this situation around". Let's see:
Going with the #QueryParam strategy is not possible because of two main reasons:
As you noticed, there are some limitations on its use regarding Collections other than Lists, Sets, etc;
This annotation maps one (or a list) of param(s) by its(their) name(s), so you need every single parameter (separated by &) to have the same name. It's easier when we think about a form that submits (via GET) a list of checkboxes values: once they all have the same name property, they'll be sent in "name=value1&name=value2" format.
So, in order to get this requirement, you'd have to make something like:
#GET
public Response getMappings(#QueryParam("columns") List<String> columns) {
return Response.status(200).entity(columns).build();
}
// URL to be called (with same param names):
// /mappings?columns=columns[1][name]=0&columns=columns[0][searchable]=false
// Result: [columns[1][name]=0, columns[0][searchable]=false]
You can also try creating a Custom Java Type for Param Annotations, like you see here. That would avoid encoding problems, but in my tests it didn't work for the brackets issue. :(
You can use regex along with #Path annotation defining what is going to be accepted by a String parameter. Unfortunately, your URL would be composed by unvalid characteres (like the brackets []), which means your server is going to return a 500 error.
One alternative for this is if you "replace" this chars for valid ones (like underscore character, e.g.):
/mappings/columns_1_=0&columns_1__name_=
This way, the solution can be applied with no worries:
#GET
#Path("/{columns: .*}")
public Response getMappings(#PathParam("columns") String columns) {
return Response.status(200).entity(columns).build();
}
// Result: columns_1_=0&columns_1__name_=
A much better way to do this is through UriInfo object, as you may have tried. This is simpler because there's no need to change the URL and params. The object has a getQueryParameters() that returns a Map with the param values:
#GET
public Response getMappings(#Context UriInfo uriInfo) {
MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
// In case you want to get the whole generated string
String query = uriInfo.getRequestUri().getQuery();
String output = "QueryParams: " + queryParams
+ "<br> Keys: " + queryParams.keySet()
+ "<br> Values: " + queryParams.values()
+ "<br> Query: " + query;
return Response.status(200).entity(output).build();
}
// URL: /mappings?columns[1][name]=0&columns[0][searchable]=false
/* Result:
* QueryParams: {columns[0][searchable]=[false], columns[1][name]=[0]}
* Keys: [columns[0][searchable], columns[1][name]]
* Values: [[false], [0]]
* Query: columns[1][name]=0&columns[0][searchable]=false
*/
However, you must be aware that if you follow this approach (using a Map) you can't have duplicated keys, once the structure doesn't support it. That's why I include the getQuery() option where you get the whole string.
A last possibility is creating a InjectableProvider, but I can't see many diffs to the getQuery() strategy (since you can split it and create your own map of values).
I have an service that should receive a List<Object>, in my case, FaturamentoDTO... ex:
#GET
#Path(value="/teste")
#Produces(MediaType.APPLICATION_JSON)
public List<FaturamentoDTO> teste(#QueryParam("list") final List<FaturamentoDTO> listFatsDTO) {
for (FaturamentoDTO f : listFatsDTO) {
// do my stuff...
}
return listFatsDTO;
}
So my question is, how can I send and receive the values.
JAX-RS specification says:
The following types are supported:
1 Primitive Types
2 Types that have a constructor that accepts a single String argument.
3 Types that have a static method named valueOf with a single String argument.
4 List, Set, or SortedSet where T satisfies 2 or 3 above.
But even with the constructor I can't get the values.
If you are sending anything other than simple strings I would recommend using a POST with an appropriate request body. However it must be possible with a GET.
How does your client send the request?
Your client should send a request corresponding to:
GET http://example.com/services/teste?list=item1&list=item2&list=item3
Query parameters don't have any specific support for complex data structures, so you are going to need to implement that yourself. I was facing something similar, and ended up using JSON representations of the data as the query parameter values. For example (note that the JSON should be URL encoded, but I left that out to make it readable)
http://service?item={"foo" : "value1", "bar" : "value2"}&item={"foo" : "value3", "bar" : "value4"}
You could then write a ParamConverter<T> to unmarshal the JSON into your FaturamentoDTO. I am using JAXB/MOXy, but this could be done using your JSON handling library of choice.
EDIT: Changed question title and content. Upon reading the JSON plugin guide I realize the plugin might be expecting a JSON string instead of this query map, in which case I normally go with GSON instead. I guess the question becomes: how can Struts2 handle type conversion of a query string like this: sort[0][field]=status&sort[0][dir]=asc
I am using Kendo UI grid to interface with my Struts2 backend. The AJAX request being sent to the server follows the following format (GET query string):
take=5&skip=0&page=1&pageSize=5&sort%5B0%5D%5Bfield%5D=status&sort%5B0%5D%5Bdir%5D=asc
or (non-escaped):
take=5&skip=0&page=1&pageSize=5&sort[0][field]=status&sort[0][dir]=asc
Basically, Kendo UI grid is sending a flattened JSON object to the server. So I create a sort model object like so to take the input:
public class SortModel {
private String field;
private String dir;
}
and include this in my Struts2 action as a variable to be populated:
private SortModel[] sort;
However, this never gets populated by Struts2 when the AJAX request comes in. I also tried to add the JSON interceptor, but I think I misunderstood its deserialization process, as explained in the edit.
Anyway, has anyone managed to Struts2 type conversion working using the above query string or similar: sort[0][field]=status&sort[0][dir]=asc?
sort[0][field]=status&sort[0][dir]=asc
The above is not proper JSON, strings should be quoted. With that done the following will work.
In which case a field (or json parameter) in the form name[i]['s'] which has a value of String and where i is an integer and s is any string would be backed by:
private List<Map<String, String>> name = new ArrayList<Map<String, String>>();
//getter AND setter required
PS: With Struts2 you can index into lists of lists of lists... without issue.
Okay.
It turns out that vanilla Struts2 doesn't accept query strings in the format obj[idx][property] (feel free to correct me on this). I was expecting it to convert the query string to an array of that specific object.
What Struts2 does accept is the format obj[idx].property which it will correctly convert to private Object[] obj.
So I guess the possible solutions to this would be:
JSON.stringify(jsonObj) before passing it to the query string, a la &jsonData=[{property:'value'}] - which in this case, I can't do since Kendo UI grid doesn't seem to have an interceptor-like event to let me change the query parameters. Or,
Implement a custom type converter that handles this particular format. Or,
Intercept the AJAX request before it is being sent to the server and re-format the query string, using jQuery.ajaxSend e.g.
$(body).ajaxSend(function(event, req, settings){
console.log(settings.url); //contains the url string to replace
settings.url = settings.url.replace(some_regex, 'correct format');
});